|
@@ -2,7 +2,7 @@ const { ethers } = require('hardhat');
|
|
const { expect } = require('chai');
|
|
const { expect } = require('chai');
|
|
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
|
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
|
|
|
|
|
-const { randomArray, generators } = require('../helpers/random');
|
|
|
|
|
|
+const { generators } = require('../helpers/random');
|
|
|
|
|
|
// See https://en.cppreference.com/w/cpp/algorithm/lower_bound
|
|
// See https://en.cppreference.com/w/cpp/algorithm/lower_bound
|
|
const lowerBound = (array, value) => {
|
|
const lowerBound = (array, value) => {
|
|
@@ -16,9 +16,7 @@ const upperBound = (array, value) => {
|
|
return i == -1 ? array.length : i;
|
|
return i == -1 ? array.length : i;
|
|
};
|
|
};
|
|
|
|
|
|
-// By default, js "sort" cast to string and then sort in alphabetical order. Use this to sort numbers.
|
|
|
|
-const compareNumbers = (a, b) => (a > b ? 1 : a < b ? -1 : 0);
|
|
|
|
-
|
|
|
|
|
|
+const bigintSign = x => (x > 0n ? 1 : x < 0n ? -1 : 0);
|
|
const hasDuplicates = array => array.some((v, i) => array.indexOf(v) != i);
|
|
const hasDuplicates = array => array.some((v, i) => array.indexOf(v) != i);
|
|
|
|
|
|
describe('Arrays', function () {
|
|
describe('Arrays', function () {
|
|
@@ -30,42 +28,6 @@ describe('Arrays', function () {
|
|
Object.assign(this, await loadFixture(fixture));
|
|
Object.assign(this, await loadFixture(fixture));
|
|
});
|
|
});
|
|
|
|
|
|
- describe('sort', function () {
|
|
|
|
- for (const length of [0, 1, 2, 8, 32, 128]) {
|
|
|
|
- it(`sort array of length ${length}`, async function () {
|
|
|
|
- this.elements = randomArray(generators.uint256, length);
|
|
|
|
- this.expected = Array.from(this.elements).sort(compareNumbers);
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- if (length > 1) {
|
|
|
|
- it(`sort array of length ${length} (identical elements)`, async function () {
|
|
|
|
- this.elements = Array(length).fill(generators.uint256());
|
|
|
|
- this.expected = this.elements;
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- it(`sort array of length ${length} (already sorted)`, async function () {
|
|
|
|
- this.elements = randomArray(generators.uint256, length).sort(compareNumbers);
|
|
|
|
- this.expected = this.elements;
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- it(`sort array of length ${length} (sorted in reverse order)`, async function () {
|
|
|
|
- this.elements = randomArray(generators.uint256, length).sort(compareNumbers).reverse();
|
|
|
|
- this.expected = Array.from(this.elements).reverse();
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- it(`sort array of length ${length} (almost sorted)`, async function () {
|
|
|
|
- this.elements = randomArray(generators.uint256, length).sort(compareNumbers);
|
|
|
|
- this.expected = Array.from(this.elements);
|
|
|
|
- // rotate (move the last element to the front) for an almost sorted effect
|
|
|
|
- this.elements.unshift(this.elements.pop());
|
|
|
|
- });
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- afterEach(async function () {
|
|
|
|
- expect(await this.mock.$sort(this.elements)).to.deep.equal(this.expected);
|
|
|
|
- });
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
describe('search', function () {
|
|
describe('search', function () {
|
|
for (const [title, { array, tests }] of Object.entries({
|
|
for (const [title, { array, tests }] of Object.entries({
|
|
'Even number of elements': {
|
|
'Even number of elements': {
|
|
@@ -154,22 +116,78 @@ describe('Arrays', function () {
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
|
|
- describe('unsafeAccess', function () {
|
|
|
|
- for (const [type, { artifact, elements }] of Object.entries({
|
|
|
|
- address: { artifact: 'AddressArraysMock', elements: randomArray(generators.address, 10) },
|
|
|
|
- bytes32: { artifact: 'Bytes32ArraysMock', elements: randomArray(generators.bytes32, 10) },
|
|
|
|
- uint256: { artifact: 'Uint256ArraysMock', elements: randomArray(generators.uint256, 10) },
|
|
|
|
- })) {
|
|
|
|
- describe(type, function () {
|
|
|
|
- describe('storage', function () {
|
|
|
|
- const fixture = async () => {
|
|
|
|
- return { instance: await ethers.deployContract(artifact, [elements]) };
|
|
|
|
- };
|
|
|
|
|
|
+ for (const [type, { artifact, elements, comp }] of Object.entries({
|
|
|
|
+ address: {
|
|
|
|
+ artifact: 'AddressArraysMock',
|
|
|
|
+ elements: Array.from({ length: 10 }, generators.address),
|
|
|
|
+ comp: (a, b) => bigintSign(ethers.toBigInt(a) - ethers.toBigInt(b)),
|
|
|
|
+ },
|
|
|
|
+ bytes32: {
|
|
|
|
+ artifact: 'Bytes32ArraysMock',
|
|
|
|
+ elements: Array.from({ length: 10 }, generators.bytes32),
|
|
|
|
+ comp: (a, b) => bigintSign(ethers.toBigInt(a) - ethers.toBigInt(b)),
|
|
|
|
+ },
|
|
|
|
+ uint256: {
|
|
|
|
+ artifact: 'Uint256ArraysMock',
|
|
|
|
+ elements: Array.from({ length: 10 }, generators.uint256),
|
|
|
|
+ comp: (a, b) => bigintSign(a - b),
|
|
|
|
+ },
|
|
|
|
+ })) {
|
|
|
|
+ describe(type, function () {
|
|
|
|
+ const fixture = async () => {
|
|
|
|
+ return { instance: await ethers.deployContract(artifact, [elements]) };
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ beforeEach(async function () {
|
|
|
|
+ Object.assign(this, await loadFixture(fixture));
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ describe('sort', function () {
|
|
|
|
+ for (const length of [0, 1, 2, 8, 32, 128]) {
|
|
|
|
+ describe(`${type}[] of length ${length}`, function () {
|
|
|
|
+ beforeEach(async function () {
|
|
|
|
+ this.elements = Array.from({ length }, generators[type]);
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ afterEach(async function () {
|
|
|
|
+ const expected = Array.from(this.elements).sort(comp);
|
|
|
|
+ const reversed = Array.from(expected).reverse();
|
|
|
|
+ expect(await this.instance.sort(this.elements)).to.deep.equal(expected);
|
|
|
|
+ expect(await this.instance.sortReverse(this.elements)).to.deep.equal(reversed);
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ it('sort array', async function () {
|
|
|
|
+ // nothing to do here, beforeEach and afterEach already take care of everything.
|
|
|
|
+ });
|
|
|
|
|
|
- beforeEach(async function () {
|
|
|
|
- Object.assign(this, await loadFixture(fixture));
|
|
|
|
|
|
+ if (length > 1) {
|
|
|
|
+ it('sort array for identical elements', async function () {
|
|
|
|
+ // duplicate the first value to all elements
|
|
|
|
+ this.elements.fill(this.elements.at(0));
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ it('sort already sorted array', async function () {
|
|
|
|
+ // pre-sort the elements
|
|
|
|
+ this.elements.sort(comp);
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ it('sort reversed array', async function () {
|
|
|
|
+ // pre-sort in reverse order
|
|
|
|
+ this.elements.sort(comp).reverse();
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ it('sort almost sorted array', async function () {
|
|
|
|
+ // pre-sort + rotate (move the last element to the front) for an almost sorted effect
|
|
|
|
+ this.elements.sort(comp);
|
|
|
|
+ this.elements.unshift(this.elements.pop());
|
|
|
|
+ });
|
|
|
|
+ }
|
|
});
|
|
});
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
|
|
|
|
+ describe('unsafeAccess', function () {
|
|
|
|
+ describe('storage', function () {
|
|
for (const i in elements) {
|
|
for (const i in elements) {
|
|
it(`unsafeAccess within bounds #${i}`, async function () {
|
|
it(`unsafeAccess within bounds #${i}`, async function () {
|
|
expect(await this.instance.unsafeAccess(i)).to.equal(elements[i]);
|
|
expect(await this.instance.unsafeAccess(i)).to.equal(elements[i]);
|
|
@@ -195,6 +213,6 @@ describe('Arrays', function () {
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
- }
|
|
|
|
- });
|
|
|
|
|
|
+ });
|
|
|
|
+ }
|
|
});
|
|
});
|