Add sort in memory to Arrays library (#4846)
Co-authored-by: RenanSouza2 <renan.rodrigues.souza1@gmail.com> Co-authored-by: Ernesto García <ernestognw@gmail.com>
This commit is contained in:
15
test/utils/Arrays.t.sol
Normal file
15
test/utils/Arrays.t.sol
Normal file
@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {Test} from "forge-std/Test.sol";
|
||||
import {Arrays} from "@openzeppelin/contracts/utils/Arrays.sol";
|
||||
|
||||
contract ArraysTest is Test {
|
||||
function testSort(uint256[] memory values) public {
|
||||
Arrays.sort(values);
|
||||
for (uint256 i = 1; i < values.length; ++i) {
|
||||
assertLe(values[i - 1], values[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -16,9 +16,56 @@ const upperBound = (array, value) => {
|
||||
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 hasDuplicates = array => array.some((v, i) => array.indexOf(v) != i);
|
||||
|
||||
describe('Arrays', function () {
|
||||
const fixture = async () => {
|
||||
return { mock: await ethers.deployContract('$Arrays') };
|
||||
};
|
||||
|
||||
beforeEach(async function () {
|
||||
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 () {
|
||||
for (const [title, { array, tests }] of Object.entries({
|
||||
'Even number of elements': {
|
||||
@ -74,7 +121,7 @@ describe('Arrays', function () {
|
||||
})) {
|
||||
describe(title, function () {
|
||||
const fixture = async () => {
|
||||
return { mock: await ethers.deployContract('Uint256ArraysMock', [array]) };
|
||||
return { instance: await ethers.deployContract('Uint256ArraysMock', [array]) };
|
||||
};
|
||||
|
||||
beforeEach(async function () {
|
||||
@ -86,20 +133,20 @@ describe('Arrays', function () {
|
||||
it('[deprecated] findUpperBound', async function () {
|
||||
// findUpperBound does not support duplicated
|
||||
if (hasDuplicates(array)) {
|
||||
expect(await this.mock.findUpperBound(input)).to.be.equal(upperBound(array, input) - 1);
|
||||
expect(await this.instance.findUpperBound(input)).to.equal(upperBound(array, input) - 1);
|
||||
} else {
|
||||
expect(await this.mock.findUpperBound(input)).to.be.equal(lowerBound(array, input));
|
||||
expect(await this.instance.findUpperBound(input)).to.equal(lowerBound(array, input));
|
||||
}
|
||||
});
|
||||
|
||||
it('lowerBound', async function () {
|
||||
expect(await this.mock.lowerBound(input)).to.be.equal(lowerBound(array, input));
|
||||
expect(await this.mock.lowerBoundMemory(array, input)).to.be.equal(lowerBound(array, input));
|
||||
expect(await this.instance.lowerBound(input)).to.equal(lowerBound(array, input));
|
||||
expect(await this.instance.lowerBoundMemory(array, input)).to.equal(lowerBound(array, input));
|
||||
});
|
||||
|
||||
it('upperBound', async function () {
|
||||
expect(await this.mock.upperBound(input)).to.be.equal(upperBound(array, input));
|
||||
expect(await this.mock.upperBoundMemory(array, input)).to.be.equal(upperBound(array, input));
|
||||
expect(await this.instance.upperBound(input)).to.equal(upperBound(array, input));
|
||||
expect(await this.instance.upperBoundMemory(array, input)).to.equal(upperBound(array, input));
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -108,28 +155,44 @@ describe('Arrays', function () {
|
||||
});
|
||||
|
||||
describe('unsafeAccess', function () {
|
||||
for (const [title, { artifact, elements }] of Object.entries({
|
||||
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(title, function () {
|
||||
const fixture = async () => {
|
||||
return { mock: await ethers.deployContract(artifact, [elements]) };
|
||||
};
|
||||
describe(type, function () {
|
||||
describe('storage', function () {
|
||||
const fixture = async () => {
|
||||
return { instance: await ethers.deployContract(artifact, [elements]) };
|
||||
};
|
||||
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
for (const i in elements) {
|
||||
it(`unsafeAccess within bounds #${i}`, async function () {
|
||||
expect(await this.instance.unsafeAccess(i)).to.equal(elements[i]);
|
||||
});
|
||||
}
|
||||
|
||||
it('unsafeAccess outside bounds', async function () {
|
||||
await expect(this.instance.unsafeAccess(elements.length)).to.not.be.rejected;
|
||||
});
|
||||
});
|
||||
|
||||
for (const i in elements) {
|
||||
it(`unsafeAccess within bounds #${i}`, async function () {
|
||||
expect(await this.mock.unsafeAccess(i)).to.equal(elements[i]);
|
||||
});
|
||||
}
|
||||
describe('memory', function () {
|
||||
const fragment = `$unsafeMemoryAccess(${type}[] arr, uint256 pos)`;
|
||||
|
||||
it('unsafeAccess outside bounds', async function () {
|
||||
await expect(this.mock.unsafeAccess(elements.length)).to.not.be.rejected;
|
||||
for (const i in elements) {
|
||||
it(`unsafeMemoryAccess within bounds #${i}`, async function () {
|
||||
expect(await this.mock[fragment](elements, i)).to.equal(elements[i]);
|
||||
});
|
||||
}
|
||||
|
||||
it('unsafeMemoryAccess outside bounds', async function () {
|
||||
await expect(this.mock[fragment](elements, elements.length)).to.not.be.rejected;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {Test} from "forge-std/Test.sol";
|
||||
|
||||
import {Base64} from "@openzeppelin/contracts/utils/Base64.sol";
|
||||
|
||||
contract Base64Test is Test {
|
||||
|
||||
@ -4,8 +4,8 @@
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {Test} from "forge-std/Test.sol";
|
||||
import {SafeCast} from "../../../contracts/utils/math/SafeCast.sol";
|
||||
import {Checkpoints} from "../../../contracts/utils/structs/Checkpoints.sol";
|
||||
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
|
||||
import {Checkpoints} from "@openzeppelin/contracts/utils/structs/Checkpoints.sol";
|
||||
|
||||
contract CheckpointsTrace224Test is Test {
|
||||
using Checkpoints for Checkpoints.Trace224;
|
||||
|
||||
Reference in New Issue
Block a user