Skip to content
Prev Previous commit
Next Next commit
include tests
  • Loading branch information
RenanSouza2 committed Jan 28, 2024
commit 414545ad2c8e7d4c1d2c189dfb2048ab702d4fe5
8 changes: 8 additions & 0 deletions contracts/mocks/ArraysMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ contract Uint256ArraysMock {
function unsafeAccess(uint256 pos) external view returns (uint256) {
return _array.unsafeAccess(pos).value;
}

function unsafeMemoryAccess(uint256[] memory array, uint256 pos) external pure returns (uint256) {
return array.unsafeMemoryAccess(pos);
}
}

contract AddressArraysMock {
Expand All @@ -34,6 +38,10 @@ contract AddressArraysMock {
function unsafeAccess(uint256 pos) external view returns (address) {
return _array.unsafeAccess(pos).value;
}

function unsafeMemoryAccess(address[] memory array, uint256 pos) external pure returns (address) {
return array.unsafeMemoryAccess(pos);
}
}

contract Bytes32ArraysMock {
Expand Down
63 changes: 32 additions & 31 deletions scripts/generate/templates/Arrays.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,44 +16,44 @@ import {Math} from "./math/Math.sol";
`;

const findUpperBound = `
using StorageSlot for bytes32;
using StorageSlot for bytes32;

/**
* @dev Searches a sorted \`array\` and returns the first index that contains
* a value greater or equal to \`element\`. If no such index exists (i.e. all
* values in the array are strictly less than \`element\`), the array length is
* returned. Time complexity O(log n).
*
* \`array\` is expected to be sorted in ascending order, and to contain no
* repeated elements.
*/
function findUpperBound(uint256[] storage array, uint256 element) internal view returns (uint256) {
uint256 low = 0;
uint256 high = array.length;
/**
* @dev Searches a sorted \`array\` and returns the first index that contains
* a value greater or equal to \`element\`. If no such index exists (i.e. all
* values in the array are strictly less than \`element\`), the array length is
* returned. Time complexity O(log n).
*
* \`array\` is expected to be sorted in ascending order, and to contain no
* repeated elements.
*/
function findUpperBound(uint256[] storage array, uint256 element) internal view returns (uint256) {
uint256 low = 0;
uint256 high = array.length;

if (high == 0) {
return 0;
}
if (high == 0) {
return 0;
}

while (low < high) {
uint256 mid = Math.average(low, high);
while (low < high) {
uint256 mid = Math.average(low, high);

// Note that mid will always be strictly less than high (i.e. it will be a valid array index)
// because Math.average rounds towards zero (it does integer division with truncation).
if (unsafeAccess(array, mid).value > element) {
high = mid;
} else {
low = mid + 1;
}
}

// At this point \`low\` is the exclusive upper bound. We will return the inclusive upper bound.
if (low > 0 && unsafeAccess(array, low - 1).value == element) {
return low - 1;
// Note that mid will always be strictly less than high (i.e. it will be a valid array index)
// because Math.average rounds towards zero (it does integer division with truncation).
if (unsafeAccess(array, mid).value > element) {
high = mid;
} else {
return low;
low = mid + 1;
}
}

// At this point \`low\` is the exclusive upper bound. We will return the inclusive upper bound.
if (low > 0 && unsafeAccess(array, low - 1).value == element) {
return low - 1;
} else {
return low;
}
}
`;

const unsafeAccessStorage = type => `
Expand Down Expand Up @@ -89,6 +89,7 @@ function unsafeMemoryAccess(${type}[] memory arr, uint256 pos) internal pure ret
}
}
`;

// GENERATE
module.exports = format(
header.trimEnd(),
Expand Down
37 changes: 33 additions & 4 deletions test/utils/Arrays.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,16 +93,45 @@ describe('Arrays', function () {
});

describe('unsafeAccess', function () {
describe('storage', function () {
const contractCases = {
address: { artifact: 'AddressArraysMock', elements: randomArray(generators.address, 10) },
bytes32: { artifact: 'Bytes32ArraysMock', elements: randomArray(generators.bytes32, 10) },
uint256: { artifact: 'Uint256ArraysMock', elements: randomArray(generators.uint256, 10) },
};

const fixture = async () => {
const contracts = {};
for (const [name, { artifact, elements }] of Object.entries(contractCases)) {
contracts[name] = await ethers.deployContract(artifact, [elements]);
}
return { contracts };
};

beforeEach(async function () {
Object.assign(this, await loadFixture(fixture));
});

for (const [name, { elements }] of Object.entries(contractCases)) {
it(name, async function () {
for (const i in elements) {
expect(await this.contracts[name].unsafeAccess(i)).to.equal(elements[i]);
}
});
}
});
});

describe('memory', function () {
const contractCases = {
address: { artifact: 'AddressArraysMock', elements: randomArray(generators.address, 10) },
bytes32: { artifact: 'Bytes32ArraysMock', elements: randomArray(generators.bytes32, 10) },
uint256: { artifact: 'Uint256ArraysMock', elements: randomArray(generators.uint256, 10) },
};

const fixture = async () => {
const contracts = {};
for (const [name, { artifact, elements }] of Object.entries(contractCases)) {
contracts[name] = await ethers.deployContract(artifact, [elements]);
for (const [name, { artifact }] of Object.entries(contractCases)) {
contracts[name] = await ethers.deployContract(artifact, [[]]);
}
return { contracts };
};
Expand All @@ -114,7 +143,7 @@ describe('Arrays', function () {
for (const [name, { elements }] of Object.entries(contractCases)) {
it(name, async function () {
for (const i in elements) {
expect(await this.contracts[name].unsafeAccess(i)).to.equal(elements[i]);
expect(await this.contracts[name].unsafeMemoryAccess(elements, i)).to.equal(elements[i]);
}
});
}
Expand Down