dexorder
This commit is contained in:
280
lib_openzeppelin_contracts/test/utils/Address.test.js
Normal file
280
lib_openzeppelin_contracts/test/utils/Address.test.js
Normal file
@@ -0,0 +1,280 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
const { PANIC_CODES } = require('@nomicfoundation/hardhat-chai-matchers/panic');
|
||||
|
||||
const coder = ethers.AbiCoder.defaultAbiCoder();
|
||||
|
||||
async function fixture() {
|
||||
const [recipient, other] = await ethers.getSigners();
|
||||
|
||||
const mock = await ethers.deployContract('$Address');
|
||||
const target = await ethers.deployContract('CallReceiverMock');
|
||||
const targetEther = await ethers.deployContract('EtherReceiverMock');
|
||||
|
||||
return { recipient, other, mock, target, targetEther };
|
||||
}
|
||||
|
||||
describe('Address', function () {
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
describe('sendValue', function () {
|
||||
describe('when sender contract has no funds', function () {
|
||||
it('sends 0 wei', async function () {
|
||||
await expect(this.mock.$sendValue(this.other, 0n)).to.changeEtherBalance(this.recipient, 0n);
|
||||
});
|
||||
|
||||
it('reverts when sending non-zero amounts', async function () {
|
||||
await expect(this.mock.$sendValue(this.other, 1n))
|
||||
.to.be.revertedWithCustomError(this.mock, 'InsufficientBalance')
|
||||
.withArgs(0n, 1n);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when sender contract has funds', function () {
|
||||
const funds = ethers.parseEther('1');
|
||||
|
||||
beforeEach(async function () {
|
||||
await this.other.sendTransaction({ to: this.mock, value: funds });
|
||||
});
|
||||
|
||||
describe('with EOA recipient', function () {
|
||||
it('sends 0 wei', async function () {
|
||||
await expect(this.mock.$sendValue(this.recipient, 0n)).to.changeEtherBalance(this.recipient, 0n);
|
||||
});
|
||||
|
||||
it('sends non-zero amounts', async function () {
|
||||
await expect(this.mock.$sendValue(this.recipient, funds - 1n)).to.changeEtherBalance(
|
||||
this.recipient,
|
||||
funds - 1n,
|
||||
);
|
||||
});
|
||||
|
||||
it('sends the whole balance', async function () {
|
||||
await expect(this.mock.$sendValue(this.recipient, funds)).to.changeEtherBalance(this.recipient, funds);
|
||||
expect(await ethers.provider.getBalance(this.mock)).to.equal(0n);
|
||||
});
|
||||
|
||||
it('reverts when sending more than the balance', async function () {
|
||||
await expect(this.mock.$sendValue(this.recipient, funds + 1n))
|
||||
.to.be.revertedWithCustomError(this.mock, 'InsufficientBalance')
|
||||
.withArgs(funds, funds + 1n);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with contract recipient', function () {
|
||||
it('sends funds', async function () {
|
||||
await this.targetEther.setAcceptEther(true);
|
||||
await expect(this.mock.$sendValue(this.targetEther, funds)).to.changeEtherBalance(this.targetEther, funds);
|
||||
});
|
||||
|
||||
it('reverts on recipient revert', async function () {
|
||||
await this.targetEther.setAcceptEther(false);
|
||||
await expect(this.mock.$sendValue(this.targetEther, funds)).to.be.revertedWithCustomError(
|
||||
this.mock,
|
||||
'FailedCall',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('functionCall', function () {
|
||||
describe('with valid contract receiver', function () {
|
||||
it('calls the requested function', async function () {
|
||||
const call = this.target.interface.encodeFunctionData('mockFunction');
|
||||
|
||||
await expect(this.mock.$functionCall(this.target, call))
|
||||
.to.emit(this.target, 'MockFunctionCalled')
|
||||
.to.emit(this.mock, 'return$functionCall')
|
||||
.withArgs(coder.encode(['string'], ['0x1234']));
|
||||
});
|
||||
|
||||
it('calls the requested empty return function', async function () {
|
||||
const call = this.target.interface.encodeFunctionData('mockFunctionEmptyReturn');
|
||||
|
||||
await expect(this.mock.$functionCall(this.target, call)).to.emit(this.target, 'MockFunctionCalled');
|
||||
});
|
||||
|
||||
it('reverts when the called function reverts with no reason', async function () {
|
||||
const call = this.target.interface.encodeFunctionData('mockFunctionRevertsNoReason');
|
||||
|
||||
await expect(this.mock.$functionCall(this.target, call)).to.be.revertedWithCustomError(this.mock, 'FailedCall');
|
||||
});
|
||||
|
||||
it('reverts when the called function reverts, bubbling up the revert reason', async function () {
|
||||
const call = this.target.interface.encodeFunctionData('mockFunctionRevertsReason');
|
||||
|
||||
await expect(this.mock.$functionCall(this.target, call)).to.be.revertedWith('CallReceiverMock: reverting');
|
||||
});
|
||||
|
||||
it('reverts when the called function runs out of gas', async function () {
|
||||
const call = this.target.interface.encodeFunctionData('mockFunctionOutOfGas');
|
||||
|
||||
await expect(this.mock.$functionCall(this.target, call, { gasLimit: 120_000n })).to.be.revertedWithCustomError(
|
||||
this.mock,
|
||||
'FailedCall',
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts when the called function throws', async function () {
|
||||
const call = this.target.interface.encodeFunctionData('mockFunctionThrows');
|
||||
|
||||
await expect(this.mock.$functionCall(this.target, call)).to.be.revertedWithPanic(PANIC_CODES.ASSERTION_ERROR);
|
||||
});
|
||||
|
||||
it('reverts when function does not exist', async function () {
|
||||
const interface = new ethers.Interface(['function mockFunctionDoesNotExist()']);
|
||||
const call = interface.encodeFunctionData('mockFunctionDoesNotExist');
|
||||
|
||||
await expect(this.mock.$functionCall(this.target, call)).to.be.revertedWithCustomError(this.mock, 'FailedCall');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with non-contract receiver', function () {
|
||||
it('reverts when address is not a contract', async function () {
|
||||
const call = this.target.interface.encodeFunctionData('mockFunction');
|
||||
|
||||
await expect(this.mock.$functionCall(this.recipient, call))
|
||||
.to.be.revertedWithCustomError(this.mock, 'AddressEmptyCode')
|
||||
.withArgs(this.recipient);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('functionCallWithValue', function () {
|
||||
describe('with zero value', function () {
|
||||
it('calls the requested function', async function () {
|
||||
const call = this.target.interface.encodeFunctionData('mockFunction');
|
||||
|
||||
await expect(this.mock.$functionCallWithValue(this.target, call, 0n))
|
||||
.to.emit(this.target, 'MockFunctionCalled')
|
||||
.to.emit(this.mock, 'return$functionCallWithValue')
|
||||
.withArgs(coder.encode(['string'], ['0x1234']));
|
||||
});
|
||||
});
|
||||
|
||||
describe('with non-zero value', function () {
|
||||
const value = ethers.parseEther('1.2');
|
||||
|
||||
it('reverts if insufficient sender balance', async function () {
|
||||
const call = this.target.interface.encodeFunctionData('mockFunction');
|
||||
|
||||
await expect(this.mock.$functionCallWithValue(this.target, call, value))
|
||||
.to.be.revertedWithCustomError(this.mock, 'InsufficientBalance')
|
||||
.withArgs(0n, value);
|
||||
});
|
||||
|
||||
it('calls the requested function with existing value', async function () {
|
||||
await this.other.sendTransaction({ to: this.mock, value });
|
||||
|
||||
const call = this.target.interface.encodeFunctionData('mockFunction');
|
||||
const tx = await this.mock.$functionCallWithValue(this.target, call, value);
|
||||
|
||||
await expect(tx).to.changeEtherBalance(this.target, value);
|
||||
|
||||
await expect(tx)
|
||||
.to.emit(this.target, 'MockFunctionCalled')
|
||||
.to.emit(this.mock, 'return$functionCallWithValue')
|
||||
.withArgs(coder.encode(['string'], ['0x1234']));
|
||||
});
|
||||
|
||||
it('calls the requested function with transaction funds', async function () {
|
||||
expect(await ethers.provider.getBalance(this.mock)).to.equal(0n);
|
||||
|
||||
const call = this.target.interface.encodeFunctionData('mockFunction');
|
||||
const tx = await this.mock.connect(this.other).$functionCallWithValue(this.target, call, value, { value });
|
||||
|
||||
await expect(tx).to.changeEtherBalance(this.target, value);
|
||||
await expect(tx)
|
||||
.to.emit(this.target, 'MockFunctionCalled')
|
||||
.to.emit(this.mock, 'return$functionCallWithValue')
|
||||
.withArgs(coder.encode(['string'], ['0x1234']));
|
||||
});
|
||||
|
||||
it('reverts when calling non-payable functions', async function () {
|
||||
await this.other.sendTransaction({ to: this.mock, value });
|
||||
|
||||
const call = this.target.interface.encodeFunctionData('mockFunctionNonPayable');
|
||||
|
||||
await expect(this.mock.$functionCallWithValue(this.target, call, value)).to.be.revertedWithCustomError(
|
||||
this.mock,
|
||||
'FailedCall',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('functionStaticCall', function () {
|
||||
it('calls the requested function', async function () {
|
||||
const call = this.target.interface.encodeFunctionData('mockStaticFunction');
|
||||
|
||||
expect(await this.mock.$functionStaticCall(this.target, call)).to.equal(coder.encode(['string'], ['0x1234']));
|
||||
});
|
||||
|
||||
it('reverts on a non-static function', async function () {
|
||||
const call = this.target.interface.encodeFunctionData('mockFunction');
|
||||
|
||||
await expect(this.mock.$functionStaticCall(this.target, call)).to.be.revertedWithCustomError(
|
||||
this.mock,
|
||||
'FailedCall',
|
||||
);
|
||||
});
|
||||
|
||||
it('bubbles up revert reason', async function () {
|
||||
const call = this.target.interface.encodeFunctionData('mockFunctionRevertsReason');
|
||||
|
||||
await expect(this.mock.$functionStaticCall(this.target, call)).to.be.revertedWith('CallReceiverMock: reverting');
|
||||
});
|
||||
|
||||
it('reverts when address is not a contract', async function () {
|
||||
const call = this.target.interface.encodeFunctionData('mockFunction');
|
||||
|
||||
await expect(this.mock.$functionStaticCall(this.recipient, call))
|
||||
.to.be.revertedWithCustomError(this.mock, 'AddressEmptyCode')
|
||||
.withArgs(this.recipient);
|
||||
});
|
||||
});
|
||||
|
||||
describe('functionDelegateCall', function () {
|
||||
it('delegate calls the requested function', async function () {
|
||||
const slot = ethers.hexlify(ethers.randomBytes(32));
|
||||
const value = ethers.hexlify(ethers.randomBytes(32));
|
||||
|
||||
const call = this.target.interface.encodeFunctionData('mockFunctionWritesStorage', [slot, value]);
|
||||
|
||||
expect(await ethers.provider.getStorage(this.mock, slot)).to.equal(ethers.ZeroHash);
|
||||
|
||||
await expect(await this.mock.$functionDelegateCall(this.target, call))
|
||||
.to.emit(this.mock, 'return$functionDelegateCall')
|
||||
.withArgs(coder.encode(['string'], ['0x1234']));
|
||||
|
||||
expect(await ethers.provider.getStorage(this.mock, slot)).to.equal(value);
|
||||
});
|
||||
|
||||
it('bubbles up revert reason', async function () {
|
||||
const call = this.target.interface.encodeFunctionData('mockFunctionRevertsReason');
|
||||
|
||||
await expect(this.mock.$functionDelegateCall(this.target, call)).to.be.revertedWith(
|
||||
'CallReceiverMock: reverting',
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts when address is not a contract', async function () {
|
||||
const call = this.target.interface.encodeFunctionData('mockFunction');
|
||||
|
||||
await expect(this.mock.$functionDelegateCall(this.recipient, call))
|
||||
.to.be.revertedWithCustomError(this.mock, 'AddressEmptyCode')
|
||||
.withArgs(this.recipient);
|
||||
});
|
||||
});
|
||||
|
||||
describe('verifyCallResult', function () {
|
||||
it('returns returndata on success', async function () {
|
||||
const returndata = '0x123abc';
|
||||
expect(await this.mock.$verifyCallResult(true, returndata)).to.equal(returndata);
|
||||
});
|
||||
});
|
||||
});
|
||||
15
lib_openzeppelin_contracts/test/utils/Arrays.t.sol
Normal file
15
lib_openzeppelin_contracts/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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
223
lib_openzeppelin_contracts/test/utils/Arrays.test.js
Normal file
223
lib_openzeppelin_contracts/test/utils/Arrays.test.js
Normal file
@@ -0,0 +1,223 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
const { generators } = require('../helpers/random');
|
||||
const { capitalize } = require('../../scripts/helpers');
|
||||
const { TYPES } = require('../../scripts/generate/templates/Arrays.opts');
|
||||
|
||||
// See https://en.cppreference.com/w/cpp/algorithm/lower_bound
|
||||
const lowerBound = (array, value) => {
|
||||
const i = array.findIndex(element => value <= element);
|
||||
return i == -1 ? array.length : i;
|
||||
};
|
||||
|
||||
// See https://en.cppreference.com/w/cpp/algorithm/upper_bound
|
||||
const upperBound = (array, value) => {
|
||||
const i = array.findIndex(element => value < element);
|
||||
return i == -1 ? array.length : i;
|
||||
};
|
||||
|
||||
const bigintSign = x => (x > 0n ? 1 : x < 0n ? -1 : 0);
|
||||
const comparator = (a, b) => bigintSign(ethers.toBigInt(a) - ethers.toBigInt(b));
|
||||
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('search', function () {
|
||||
for (const [title, { array, tests }] of Object.entries({
|
||||
'Even number of elements': {
|
||||
array: [11n, 12n, 13n, 14n, 15n, 16n, 17n, 18n, 19n, 20n],
|
||||
tests: {
|
||||
'basic case': 16n,
|
||||
'first element': 11n,
|
||||
'last element': 20n,
|
||||
'searched value is over the upper boundary': 32n,
|
||||
'searched value is under the lower boundary': 2n,
|
||||
},
|
||||
},
|
||||
'Odd number of elements': {
|
||||
array: [11n, 12n, 13n, 14n, 15n, 16n, 17n, 18n, 19n, 20n, 21n],
|
||||
tests: {
|
||||
'basic case': 16n,
|
||||
'first element': 11n,
|
||||
'last element': 21n,
|
||||
'searched value is over the upper boundary': 32n,
|
||||
'searched value is under the lower boundary': 2n,
|
||||
},
|
||||
},
|
||||
'Array with gap': {
|
||||
array: [11n, 12n, 13n, 14n, 15n, 20n, 21n, 22n, 23n, 24n],
|
||||
tests: {
|
||||
'search value in gap': 17n,
|
||||
},
|
||||
},
|
||||
'Array with duplicated elements': {
|
||||
array: [0n, 10n, 10n, 10n, 10n, 10n, 10n, 10n, 20n],
|
||||
tests: {
|
||||
'search value is duplicated': 10n,
|
||||
},
|
||||
},
|
||||
'Array with duplicated first element': {
|
||||
array: [10n, 10n, 10n, 10n, 10n, 10n, 10n, 20n],
|
||||
tests: {
|
||||
'search value is duplicated first element': 10n,
|
||||
},
|
||||
},
|
||||
'Array with duplicated last element': {
|
||||
array: [0n, 10n, 10n, 10n, 10n, 10n, 10n, 10n],
|
||||
tests: {
|
||||
'search value is duplicated last element': 10n,
|
||||
},
|
||||
},
|
||||
'Empty array': {
|
||||
array: [],
|
||||
tests: {
|
||||
'always returns 0 for empty array': 10n,
|
||||
},
|
||||
},
|
||||
})) {
|
||||
describe(title, function () {
|
||||
const fixture = async () => {
|
||||
return { instance: await ethers.deployContract('Uint256ArraysMock', [array]) };
|
||||
};
|
||||
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
for (const [name, input] of Object.entries(tests)) {
|
||||
describe(name, function () {
|
||||
it('[deprecated] findUpperBound', async function () {
|
||||
// findUpperBound does not support duplicated
|
||||
if (hasDuplicates(array)) {
|
||||
expect(await this.instance.findUpperBound(input)).to.equal(upperBound(array, input) - 1);
|
||||
} else {
|
||||
expect(await this.instance.findUpperBound(input)).to.equal(lowerBound(array, input));
|
||||
}
|
||||
});
|
||||
|
||||
it('lowerBound', async function () {
|
||||
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.instance.upperBound(input)).to.equal(upperBound(array, input));
|
||||
expect(await this.instance.upperBoundMemory(array, input)).to.equal(upperBound(array, input));
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
for (const type of TYPES) {
|
||||
const elements = Array.from({ length: 10 }, generators[type]);
|
||||
|
||||
describe(type, function () {
|
||||
const fixture = async () => {
|
||||
return { instance: await ethers.deployContract(`${capitalize(type)}ArraysMock`, [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.array = Array.from({ length }, generators[type]);
|
||||
});
|
||||
|
||||
afterEach(async function () {
|
||||
const expected = Array.from(this.array).sort(comparator);
|
||||
const reversed = Array.from(expected).reverse();
|
||||
expect(await this.instance.sort(this.array)).to.deep.equal(expected);
|
||||
expect(await this.instance.sortReverse(this.array)).to.deep.equal(reversed);
|
||||
});
|
||||
|
||||
it('sort array', async function () {
|
||||
// nothing to do here, beforeEach and afterEach already take care of everything.
|
||||
});
|
||||
|
||||
if (length > 1) {
|
||||
it('sort array for identical elements', async function () {
|
||||
// duplicate the first value to all elements
|
||||
this.array.fill(this.array.at(0));
|
||||
});
|
||||
|
||||
it('sort already sorted array', async function () {
|
||||
// pre-sort the elements
|
||||
this.array.sort(comparator);
|
||||
});
|
||||
|
||||
it('sort reversed array', async function () {
|
||||
// pre-sort in reverse order
|
||||
this.array.sort(comparator).reverse();
|
||||
});
|
||||
|
||||
it('sort almost sorted array', async function () {
|
||||
// pre-sort + rotate (move the last element to the front) for an almost sorted effect
|
||||
this.array.sort(comparator);
|
||||
this.array.unshift(this.array.pop());
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('unsafeAccess', function () {
|
||||
describe('storage', function () {
|
||||
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;
|
||||
});
|
||||
|
||||
it('unsafeSetLength changes the length or the array', async function () {
|
||||
const newLength = generators.uint256();
|
||||
|
||||
expect(await this.instance.length()).to.equal(elements.length);
|
||||
await expect(this.instance.unsafeSetLength(newLength)).to.not.be.rejected;
|
||||
expect(await this.instance.length()).to.equal(newLength);
|
||||
});
|
||||
});
|
||||
|
||||
describe('memory', function () {
|
||||
const fragment = `$unsafeMemoryAccess(${type}[] arr, uint256 pos)`;
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
it('unsafeMemoryAccess loop around', async function () {
|
||||
for (let i = 251n; i < 256n; ++i) {
|
||||
expect(await this.mock[fragment](elements, 2n ** i - 1n)).to.equal(BigInt(elements.length));
|
||||
expect(await this.mock[fragment](elements, 2n ** i + 0n)).to.equal(elements[0]);
|
||||
expect(await this.mock[fragment](elements, 2n ** i + 1n)).to.equal(elements[1]);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
34
lib_openzeppelin_contracts/test/utils/Base64.t.sol
Normal file
34
lib_openzeppelin_contracts/test/utils/Base64.t.sol
Normal file
@@ -0,0 +1,34 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {Test} from "@forge-std/Test.sol";
|
||||
import {Base64} from "@openzeppelin/contracts/utils/Base64.sol";
|
||||
|
||||
contract Base64Test is Test {
|
||||
function testEncode(bytes memory input) external {
|
||||
assertEq(Base64.encode(input), vm.toBase64(input));
|
||||
}
|
||||
|
||||
function testEncodeURL(bytes memory input) external {
|
||||
assertEq(Base64.encodeURL(input), _removePadding(vm.toBase64URL(input)));
|
||||
}
|
||||
|
||||
function _removePadding(string memory inputStr) internal pure returns (string memory) {
|
||||
bytes memory input = bytes(inputStr);
|
||||
bytes memory output;
|
||||
|
||||
for (uint256 i = 0; i < input.length; ++i) {
|
||||
if (input[input.length - i - 1] != 0x3d) {
|
||||
output = new bytes(input.length - i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint256 i = 0; i < output.length; ++i) {
|
||||
output[i] = input[i];
|
||||
}
|
||||
|
||||
return string(output);
|
||||
}
|
||||
}
|
||||
59
lib_openzeppelin_contracts/test/utils/Base64.test.js
Normal file
59
lib_openzeppelin_contracts/test/utils/Base64.test.js
Normal file
@@ -0,0 +1,59 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
// Replace "+/" with "-_" in the char table, and remove the padding
|
||||
// see https://datatracker.ietf.org/doc/html/rfc4648#section-5
|
||||
const base64toBase64Url = str => str.replaceAll('+', '-').replaceAll('/', '_').replaceAll('=', '');
|
||||
|
||||
async function fixture() {
|
||||
const mock = await ethers.deployContract('$Base64');
|
||||
return { mock };
|
||||
}
|
||||
|
||||
describe('Strings', function () {
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
describe('base64', function () {
|
||||
for (const { title, input, expected } of [
|
||||
{ title: 'converts to base64 encoded string with double padding', input: 'test', expected: 'dGVzdA==' },
|
||||
{ title: 'converts to base64 encoded string with single padding', input: 'test1', expected: 'dGVzdDE=' },
|
||||
{ title: 'converts to base64 encoded string without padding', input: 'test12', expected: 'dGVzdDEy' },
|
||||
{ title: 'converts to base64 encoded string (/ case)', input: 'où', expected: 'b/k=' },
|
||||
{ title: 'converts to base64 encoded string (+ case)', input: 'zs~1t8', expected: 'enN+MXQ4' },
|
||||
{ title: 'empty bytes', input: '', expected: '' },
|
||||
])
|
||||
it(title, async function () {
|
||||
const buffer = Buffer.from(input, 'ascii');
|
||||
expect(await this.mock.$encode(buffer)).to.equal(ethers.encodeBase64(buffer));
|
||||
expect(await this.mock.$encode(buffer)).to.equal(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('base64url', function () {
|
||||
for (const { title, input, expected } of [
|
||||
{ title: 'converts to base64url encoded string with double padding', input: 'test', expected: 'dGVzdA' },
|
||||
{ title: 'converts to base64url encoded string with single padding', input: 'test1', expected: 'dGVzdDE' },
|
||||
{ title: 'converts to base64url encoded string without padding', input: 'test12', expected: 'dGVzdDEy' },
|
||||
{ title: 'converts to base64url encoded string (_ case)', input: 'où', expected: 'b_k' },
|
||||
{ title: 'converts to base64url encoded string (- case)', input: 'zs~1t8', expected: 'enN-MXQ4' },
|
||||
{ title: 'empty bytes', input: '', expected: '' },
|
||||
])
|
||||
it(title, async function () {
|
||||
const buffer = Buffer.from(input, 'ascii');
|
||||
expect(await this.mock.$encodeURL(buffer)).to.equal(base64toBase64Url(ethers.encodeBase64(buffer)));
|
||||
expect(await this.mock.$encodeURL(buffer)).to.equal(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('Encode reads beyond the input buffer into dirty memory', async function () {
|
||||
const mock = await ethers.deployContract('Base64Dirty');
|
||||
const buffer32 = ethers.id('example');
|
||||
const buffer31 = buffer32.slice(0, -2);
|
||||
|
||||
expect(await mock.encode(buffer31)).to.equal(ethers.encodeBase64(buffer31));
|
||||
expect(await mock.encode(buffer32)).to.equal(ethers.encodeBase64(buffer32));
|
||||
});
|
||||
});
|
||||
48
lib_openzeppelin_contracts/test/utils/Context.behavior.js
Normal file
48
lib_openzeppelin_contracts/test/utils/Context.behavior.js
Normal file
@@ -0,0 +1,48 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
async function fixture() {
|
||||
return { contextHelper: await ethers.deployContract('ContextMockCaller', []) };
|
||||
}
|
||||
function shouldBehaveLikeRegularContext() {
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
describe('msgSender', function () {
|
||||
it('returns the transaction sender when called from an EOA', async function () {
|
||||
await expect(this.context.connect(this.sender).msgSender()).to.emit(this.context, 'Sender').withArgs(this.sender);
|
||||
});
|
||||
|
||||
it('returns the transaction sender when called from another contract', async function () {
|
||||
await expect(this.contextHelper.connect(this.sender).callSender(this.context))
|
||||
.to.emit(this.context, 'Sender')
|
||||
.withArgs(this.contextHelper);
|
||||
});
|
||||
});
|
||||
|
||||
describe('msgData', function () {
|
||||
const args = [42n, 'OpenZeppelin'];
|
||||
|
||||
it('returns the transaction data when called from an EOA', async function () {
|
||||
const callData = this.context.interface.encodeFunctionData('msgData', args);
|
||||
|
||||
await expect(this.context.msgData(...args))
|
||||
.to.emit(this.context, 'Data')
|
||||
.withArgs(callData, ...args);
|
||||
});
|
||||
|
||||
it('returns the transaction sender when from another contract', async function () {
|
||||
const callData = this.context.interface.encodeFunctionData('msgData', args);
|
||||
|
||||
await expect(this.contextHelper.callData(this.context, ...args))
|
||||
.to.emit(this.context, 'Data')
|
||||
.withArgs(callData, ...args);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
shouldBehaveLikeRegularContext,
|
||||
};
|
||||
18
lib_openzeppelin_contracts/test/utils/Context.test.js
Normal file
18
lib_openzeppelin_contracts/test/utils/Context.test.js
Normal file
@@ -0,0 +1,18 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
const { shouldBehaveLikeRegularContext } = require('./Context.behavior');
|
||||
|
||||
async function fixture() {
|
||||
const [sender] = await ethers.getSigners();
|
||||
const context = await ethers.deployContract('ContextMock', []);
|
||||
return { sender, context };
|
||||
}
|
||||
|
||||
describe('Context', function () {
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
shouldBehaveLikeRegularContext();
|
||||
});
|
||||
18
lib_openzeppelin_contracts/test/utils/Create2.t.sol
Normal file
18
lib_openzeppelin_contracts/test/utils/Create2.t.sol
Normal file
@@ -0,0 +1,18 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {Test} from "@forge-std/Test.sol";
|
||||
import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";
|
||||
|
||||
contract Create2Test is Test {
|
||||
function testComputeAddressSpillage(bytes32 salt, bytes32 bytecodeHash, address deployer) public {
|
||||
address predicted = Create2.computeAddress(salt, bytecodeHash, deployer);
|
||||
bytes32 spillage;
|
||||
/// @solidity memory-safe-assembly
|
||||
assembly {
|
||||
spillage := and(predicted, 0xffffffffffffffffffffffff0000000000000000000000000000000000000000)
|
||||
}
|
||||
assertEq(spillage, bytes32(0));
|
||||
}
|
||||
}
|
||||
134
lib_openzeppelin_contracts/test/utils/Create2.test.js
Normal file
134
lib_openzeppelin_contracts/test/utils/Create2.test.js
Normal file
@@ -0,0 +1,134 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
async function fixture() {
|
||||
const [deployer, other] = await ethers.getSigners();
|
||||
|
||||
const factory = await ethers.deployContract('$Create2');
|
||||
|
||||
// Bytecode for deploying a contract that includes a constructor.
|
||||
// We use a vesting wallet, with 3 constructor arguments.
|
||||
const constructorByteCode = await ethers
|
||||
.getContractFactory('VestingWallet')
|
||||
.then(({ bytecode, interface }) => ethers.concat([bytecode, interface.encodeDeploy([other.address, 0n, 0n])]));
|
||||
|
||||
// Bytecode for deploying a contract that has no constructor log.
|
||||
// Here we use the Create2 helper factory.
|
||||
const constructorLessBytecode = await ethers
|
||||
.getContractFactory('$Create2')
|
||||
.then(({ bytecode, interface }) => ethers.concat([bytecode, interface.encodeDeploy([])]));
|
||||
|
||||
return { deployer, other, factory, constructorByteCode, constructorLessBytecode };
|
||||
}
|
||||
|
||||
describe('Create2', function () {
|
||||
const salt = 'salt message';
|
||||
const saltHex = ethers.id(salt);
|
||||
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
describe('computeAddress', function () {
|
||||
it('computes the correct contract address', async function () {
|
||||
const onChainComputed = await this.factory.$computeAddress(saltHex, ethers.keccak256(this.constructorByteCode));
|
||||
const offChainComputed = ethers.getCreate2Address(
|
||||
this.factory.target,
|
||||
saltHex,
|
||||
ethers.keccak256(this.constructorByteCode),
|
||||
);
|
||||
expect(onChainComputed).to.equal(offChainComputed);
|
||||
});
|
||||
|
||||
it('computes the correct contract address with deployer', async function () {
|
||||
const onChainComputed = await this.factory.$computeAddress(
|
||||
saltHex,
|
||||
ethers.keccak256(this.constructorByteCode),
|
||||
ethers.Typed.address(this.deployer),
|
||||
);
|
||||
const offChainComputed = ethers.getCreate2Address(
|
||||
this.deployer.address,
|
||||
saltHex,
|
||||
ethers.keccak256(this.constructorByteCode),
|
||||
);
|
||||
expect(onChainComputed).to.equal(offChainComputed);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deploy', function () {
|
||||
it('deploys a contract without constructor', async function () {
|
||||
const offChainComputed = ethers.getCreate2Address(
|
||||
this.factory.target,
|
||||
saltHex,
|
||||
ethers.keccak256(this.constructorLessBytecode),
|
||||
);
|
||||
|
||||
await expect(this.factory.$deploy(0n, saltHex, this.constructorLessBytecode))
|
||||
.to.emit(this.factory, 'return$deploy')
|
||||
.withArgs(offChainComputed);
|
||||
|
||||
expect(this.constructorLessBytecode).to.include((await ethers.provider.getCode(offChainComputed)).slice(2));
|
||||
});
|
||||
|
||||
it('deploys a contract with constructor arguments', async function () {
|
||||
const offChainComputed = ethers.getCreate2Address(
|
||||
this.factory.target,
|
||||
saltHex,
|
||||
ethers.keccak256(this.constructorByteCode),
|
||||
);
|
||||
|
||||
await expect(this.factory.$deploy(0n, saltHex, this.constructorByteCode))
|
||||
.to.emit(this.factory, 'return$deploy')
|
||||
.withArgs(offChainComputed);
|
||||
|
||||
const instance = await ethers.getContractAt('VestingWallet', offChainComputed);
|
||||
|
||||
expect(await instance.owner()).to.equal(this.other);
|
||||
});
|
||||
|
||||
it('deploys a contract with funds deposited in the factory', async function () {
|
||||
const value = 10n;
|
||||
|
||||
await this.deployer.sendTransaction({ to: this.factory, value });
|
||||
|
||||
const offChainComputed = ethers.getCreate2Address(
|
||||
this.factory.target,
|
||||
saltHex,
|
||||
ethers.keccak256(this.constructorByteCode),
|
||||
);
|
||||
|
||||
expect(await ethers.provider.getBalance(this.factory)).to.equal(value);
|
||||
expect(await ethers.provider.getBalance(offChainComputed)).to.equal(0n);
|
||||
|
||||
await expect(this.factory.$deploy(value, saltHex, this.constructorByteCode))
|
||||
.to.emit(this.factory, 'return$deploy')
|
||||
.withArgs(offChainComputed);
|
||||
|
||||
expect(await ethers.provider.getBalance(this.factory)).to.equal(0n);
|
||||
expect(await ethers.provider.getBalance(offChainComputed)).to.equal(value);
|
||||
});
|
||||
|
||||
it('fails deploying a contract in an existent address', async function () {
|
||||
await expect(this.factory.$deploy(0n, saltHex, this.constructorByteCode)).to.emit(this.factory, 'return$deploy');
|
||||
|
||||
await expect(this.factory.$deploy(0n, saltHex, this.constructorByteCode)).to.be.revertedWithCustomError(
|
||||
this.factory,
|
||||
'FailedDeployment',
|
||||
);
|
||||
});
|
||||
|
||||
it('fails deploying a contract if the bytecode length is zero', async function () {
|
||||
await expect(this.factory.$deploy(0n, saltHex, '0x')).to.be.revertedWithCustomError(
|
||||
this.factory,
|
||||
'Create2EmptyBytecode',
|
||||
);
|
||||
});
|
||||
|
||||
it('fails deploying a contract if factory contract does not have sufficient balance', async function () {
|
||||
await expect(this.factory.$deploy(1n, saltHex, this.constructorByteCode))
|
||||
.to.be.revertedWithCustomError(this.factory, 'InsufficientBalance')
|
||||
.withArgs(0n, 1n);
|
||||
});
|
||||
});
|
||||
});
|
||||
72
lib_openzeppelin_contracts/test/utils/Multicall.test.js
Normal file
72
lib_openzeppelin_contracts/test/utils/Multicall.test.js
Normal file
@@ -0,0 +1,72 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
async function fixture() {
|
||||
const [holder, alice, bruce] = await ethers.getSigners();
|
||||
|
||||
const amount = 12_000n;
|
||||
const helper = await ethers.deployContract('MulticallHelper');
|
||||
const mock = await ethers.deployContract('$ERC20MulticallMock', ['name', 'symbol']);
|
||||
await mock.$_mint(holder, amount);
|
||||
|
||||
return { holder, alice, bruce, amount, mock, helper };
|
||||
}
|
||||
|
||||
describe('Multicall', function () {
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
it('batches function calls', async function () {
|
||||
expect(await this.mock.balanceOf(this.alice)).to.equal(0n);
|
||||
expect(await this.mock.balanceOf(this.bruce)).to.equal(0n);
|
||||
|
||||
await expect(
|
||||
this.mock.multicall([
|
||||
this.mock.interface.encodeFunctionData('transfer', [this.alice.address, this.amount / 2n]),
|
||||
this.mock.interface.encodeFunctionData('transfer', [this.bruce.address, this.amount / 3n]),
|
||||
]),
|
||||
)
|
||||
.to.emit(this.mock, 'Transfer')
|
||||
.withArgs(this.holder, this.alice, this.amount / 2n)
|
||||
.to.emit(this.mock, 'Transfer')
|
||||
.withArgs(this.holder, this.bruce, this.amount / 3n);
|
||||
|
||||
expect(await this.mock.balanceOf(this.alice)).to.equal(this.amount / 2n);
|
||||
expect(await this.mock.balanceOf(this.bruce)).to.equal(this.amount / 3n);
|
||||
});
|
||||
|
||||
it('returns an array with the result of each call', async function () {
|
||||
await this.mock.transfer(this.helper, this.amount);
|
||||
expect(await this.mock.balanceOf(this.helper)).to.equal(this.amount);
|
||||
|
||||
await this.helper.checkReturnValues(this.mock, [this.alice, this.bruce], [this.amount / 2n, this.amount / 3n]);
|
||||
});
|
||||
|
||||
it('reverts previous calls', async function () {
|
||||
expect(await this.mock.balanceOf(this.alice)).to.equal(0n);
|
||||
|
||||
await expect(
|
||||
this.mock.multicall([
|
||||
this.mock.interface.encodeFunctionData('transfer', [this.alice.address, this.amount]),
|
||||
this.mock.interface.encodeFunctionData('transfer', [this.bruce.address, this.amount]),
|
||||
]),
|
||||
)
|
||||
.to.be.revertedWithCustomError(this.mock, 'ERC20InsufficientBalance')
|
||||
.withArgs(this.holder, 0, this.amount);
|
||||
|
||||
expect(await this.mock.balanceOf(this.alice)).to.equal(0n);
|
||||
});
|
||||
|
||||
it('bubbles up revert reasons', async function () {
|
||||
await expect(
|
||||
this.mock.multicall([
|
||||
this.mock.interface.encodeFunctionData('transfer', [this.alice.address, this.amount]),
|
||||
this.mock.interface.encodeFunctionData('transfer', [this.bruce.address, this.amount]),
|
||||
]),
|
||||
)
|
||||
.to.be.revertedWithCustomError(this.mock, 'ERC20InsufficientBalance')
|
||||
.withArgs(this.holder, 0, this.amount);
|
||||
});
|
||||
});
|
||||
75
lib_openzeppelin_contracts/test/utils/Nonces.test.js
Normal file
75
lib_openzeppelin_contracts/test/utils/Nonces.test.js
Normal file
@@ -0,0 +1,75 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
async function fixture() {
|
||||
const [sender, other] = await ethers.getSigners();
|
||||
|
||||
const mock = await ethers.deployContract('$Nonces');
|
||||
|
||||
return { sender, other, mock };
|
||||
}
|
||||
|
||||
describe('Nonces', function () {
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
it('gets a nonce', async function () {
|
||||
expect(await this.mock.nonces(this.sender)).to.equal(0n);
|
||||
});
|
||||
|
||||
describe('_useNonce', function () {
|
||||
it('increments a nonce', async function () {
|
||||
expect(await this.mock.nonces(this.sender)).to.equal(0n);
|
||||
|
||||
await expect(await this.mock.$_useNonce(this.sender))
|
||||
.to.emit(this.mock, 'return$_useNonce')
|
||||
.withArgs(0n);
|
||||
|
||||
expect(await this.mock.nonces(this.sender)).to.equal(1n);
|
||||
});
|
||||
|
||||
it("increments only sender's nonce", async function () {
|
||||
expect(await this.mock.nonces(this.sender)).to.equal(0n);
|
||||
expect(await this.mock.nonces(this.other)).to.equal(0n);
|
||||
|
||||
await this.mock.$_useNonce(this.sender);
|
||||
|
||||
expect(await this.mock.nonces(this.sender)).to.equal(1n);
|
||||
expect(await this.mock.nonces(this.other)).to.equal(0n);
|
||||
});
|
||||
});
|
||||
|
||||
describe('_useCheckedNonce', function () {
|
||||
it('increments a nonce', async function () {
|
||||
const currentNonce = await this.mock.nonces(this.sender);
|
||||
|
||||
expect(currentNonce).to.equal(0n);
|
||||
|
||||
await this.mock.$_useCheckedNonce(this.sender, currentNonce);
|
||||
|
||||
expect(await this.mock.nonces(this.sender)).to.equal(1n);
|
||||
});
|
||||
|
||||
it("increments only sender's nonce", async function () {
|
||||
const currentNonce = await this.mock.nonces(this.sender);
|
||||
|
||||
expect(currentNonce).to.equal(0n);
|
||||
expect(await this.mock.nonces(this.other)).to.equal(0n);
|
||||
|
||||
await this.mock.$_useCheckedNonce(this.sender, currentNonce);
|
||||
|
||||
expect(await this.mock.nonces(this.sender)).to.equal(1n);
|
||||
expect(await this.mock.nonces(this.other)).to.equal(0n);
|
||||
});
|
||||
|
||||
it('reverts when nonce is not the expected', async function () {
|
||||
const currentNonce = await this.mock.nonces(this.sender);
|
||||
|
||||
await expect(this.mock.$_useCheckedNonce(this.sender, currentNonce + 1n))
|
||||
.to.be.revertedWithCustomError(this.mock, 'InvalidAccountNonce')
|
||||
.withArgs(this.sender, currentNonce);
|
||||
});
|
||||
});
|
||||
});
|
||||
27
lib_openzeppelin_contracts/test/utils/Packing.t.sol
Normal file
27
lib_openzeppelin_contracts/test/utils/Packing.t.sol
Normal file
@@ -0,0 +1,27 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {Test} from "@forge-std/Test.sol";
|
||||
import {Packing} from "@openzeppelin/contracts/utils/Packing.sol";
|
||||
|
||||
contract PackingTest is Test {
|
||||
using Packing for *;
|
||||
|
||||
// Pack a pair of arbitrary uint128, and check that split recovers the correct values
|
||||
function testUint128x2(uint128 first, uint128 second) external {
|
||||
Packing.Uint128x2 packed = Packing.pack(first, second);
|
||||
assertEq(packed.first(), first);
|
||||
assertEq(packed.second(), second);
|
||||
|
||||
(uint128 recoveredFirst, uint128 recoveredSecond) = packed.split();
|
||||
assertEq(recoveredFirst, first);
|
||||
assertEq(recoveredSecond, second);
|
||||
}
|
||||
|
||||
// split an arbitrary bytes32 into a pair of uint128, and check that repack matches the input
|
||||
function testUint128x2(bytes32 input) external {
|
||||
(uint128 first, uint128 second) = input.asUint128x2().split();
|
||||
assertEq(Packing.pack(first, second).asBytes32(), input);
|
||||
}
|
||||
}
|
||||
27
lib_openzeppelin_contracts/test/utils/Packing.test.js
Normal file
27
lib_openzeppelin_contracts/test/utils/Packing.test.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
const { generators } = require('../helpers/random');
|
||||
|
||||
async function fixture() {
|
||||
return { mock: await ethers.deployContract('$Packing') };
|
||||
}
|
||||
|
||||
describe('Packing', function () {
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
it('Uint128x2', async function () {
|
||||
const first = generators.uint256() % 2n ** 128n;
|
||||
const second = generators.uint256() % 2n ** 128n;
|
||||
const packed = ethers.hexlify(ethers.toBeArray((first << 128n) | second));
|
||||
|
||||
expect(await this.mock.$asUint128x2(packed)).to.equal(packed);
|
||||
expect(await this.mock.$asBytes32(packed)).to.equal(packed);
|
||||
expect(await this.mock.$pack(first, second)).to.equal(packed);
|
||||
expect(await this.mock.$split(packed)).to.deep.equal([first, second]);
|
||||
expect(await this.mock.$first(packed)).to.equal(first);
|
||||
expect(await this.mock.$second(packed)).to.equal(second);
|
||||
});
|
||||
});
|
||||
37
lib_openzeppelin_contracts/test/utils/Panic.test.js
Normal file
37
lib_openzeppelin_contracts/test/utils/Panic.test.js
Normal file
@@ -0,0 +1,37 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
const { PANIC_CODES } = require('@nomicfoundation/hardhat-chai-matchers/panic');
|
||||
|
||||
async function fixture() {
|
||||
return { mock: await ethers.deployContract('$Panic') };
|
||||
}
|
||||
|
||||
describe('Panic', function () {
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
for (const [name, code] of Object.entries({
|
||||
GENERIC: 0x0,
|
||||
ASSERT: PANIC_CODES.ASSERTION_ERROR,
|
||||
UNDER_OVERFLOW: PANIC_CODES.ARITHMETIC_OVERFLOW,
|
||||
DIVISION_BY_ZERO: PANIC_CODES.DIVISION_BY_ZERO,
|
||||
ENUM_CONVERSION_ERROR: PANIC_CODES.ENUM_CONVERSION_OUT_OF_BOUNDS,
|
||||
STORAGE_ENCODING_ERROR: PANIC_CODES.INCORRECTLY_ENCODED_STORAGE_BYTE_ARRAY,
|
||||
EMPTY_ARRAY_POP: PANIC_CODES.POP_ON_EMPTY_ARRAY,
|
||||
ARRAY_OUT_OF_BOUNDS: PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS,
|
||||
RESOURCE_ERROR: PANIC_CODES.TOO_MUCH_MEMORY_ALLOCATED,
|
||||
INVALID_INTERNAL_FUNCTION: PANIC_CODES.ZERO_INITIALIZED_VARIABLE,
|
||||
})) {
|
||||
describe(`${name} (${ethers.toBeHex(code)})`, function () {
|
||||
it('exposes panic code as constant', async function () {
|
||||
expect(await this.mock.getFunction(`$${name}`)()).to.equal(code);
|
||||
});
|
||||
|
||||
it('reverts with panic when called', async function () {
|
||||
await expect(this.mock.$panic(code)).to.be.revertedWithPanic(code);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
90
lib_openzeppelin_contracts/test/utils/Pausable.test.js
Normal file
90
lib_openzeppelin_contracts/test/utils/Pausable.test.js
Normal file
@@ -0,0 +1,90 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
async function fixture() {
|
||||
const [pauser] = await ethers.getSigners();
|
||||
|
||||
const mock = await ethers.deployContract('PausableMock');
|
||||
|
||||
return { pauser, mock };
|
||||
}
|
||||
|
||||
describe('Pausable', function () {
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
describe('when unpaused', function () {
|
||||
beforeEach(async function () {
|
||||
expect(await this.mock.paused()).to.be.false;
|
||||
});
|
||||
|
||||
it('can perform normal process in non-pause', async function () {
|
||||
expect(await this.mock.count()).to.equal(0n);
|
||||
|
||||
await this.mock.normalProcess();
|
||||
expect(await this.mock.count()).to.equal(1n);
|
||||
});
|
||||
|
||||
it('cannot take drastic measure in non-pause', async function () {
|
||||
await expect(this.mock.drasticMeasure()).to.be.revertedWithCustomError(this.mock, 'ExpectedPause');
|
||||
|
||||
expect(await this.mock.drasticMeasureTaken()).to.be.false;
|
||||
});
|
||||
|
||||
describe('when paused', function () {
|
||||
beforeEach(async function () {
|
||||
this.tx = await this.mock.pause();
|
||||
});
|
||||
|
||||
it('emits a Paused event', async function () {
|
||||
await expect(this.tx).to.emit(this.mock, 'Paused').withArgs(this.pauser);
|
||||
});
|
||||
|
||||
it('cannot perform normal process in pause', async function () {
|
||||
await expect(this.mock.normalProcess()).to.be.revertedWithCustomError(this.mock, 'EnforcedPause');
|
||||
});
|
||||
|
||||
it('can take a drastic measure in a pause', async function () {
|
||||
await this.mock.drasticMeasure();
|
||||
expect(await this.mock.drasticMeasureTaken()).to.be.true;
|
||||
});
|
||||
|
||||
it('reverts when re-pausing', async function () {
|
||||
await expect(this.mock.pause()).to.be.revertedWithCustomError(this.mock, 'EnforcedPause');
|
||||
});
|
||||
|
||||
describe('unpausing', function () {
|
||||
it('is unpausable by the pauser', async function () {
|
||||
await this.mock.unpause();
|
||||
expect(await this.mock.paused()).to.be.false;
|
||||
});
|
||||
|
||||
describe('when unpaused', function () {
|
||||
beforeEach(async function () {
|
||||
this.tx = await this.mock.unpause();
|
||||
});
|
||||
|
||||
it('emits an Unpaused event', async function () {
|
||||
await expect(this.tx).to.emit(this.mock, 'Unpaused').withArgs(this.pauser);
|
||||
});
|
||||
|
||||
it('should resume allowing normal process', async function () {
|
||||
expect(await this.mock.count()).to.equal(0n);
|
||||
await this.mock.normalProcess();
|
||||
expect(await this.mock.count()).to.equal(1n);
|
||||
});
|
||||
|
||||
it('should prevent drastic measure', async function () {
|
||||
await expect(this.mock.drasticMeasure()).to.be.revertedWithCustomError(this.mock, 'ExpectedPause');
|
||||
});
|
||||
|
||||
it('reverts when re-unpausing', async function () {
|
||||
await expect(this.mock.unpause()).to.be.revertedWithCustomError(this.mock, 'ExpectedPause');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
for (const variant of ['', 'Transient']) {
|
||||
describe(`Reentrancy${variant}Guard`, function () {
|
||||
async function fixture() {
|
||||
const name = `Reentrancy${variant}Mock`;
|
||||
const mock = await ethers.deployContract(name);
|
||||
return { name, mock };
|
||||
}
|
||||
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
it('nonReentrant function can be called', async function () {
|
||||
expect(await this.mock.counter()).to.equal(0n);
|
||||
await this.mock.callback();
|
||||
expect(await this.mock.counter()).to.equal(1n);
|
||||
});
|
||||
|
||||
it('does not allow remote callback', async function () {
|
||||
const attacker = await ethers.deployContract('ReentrancyAttack');
|
||||
await expect(this.mock.countAndCall(attacker)).to.be.revertedWith('ReentrancyAttack: failed call');
|
||||
});
|
||||
|
||||
it('_reentrancyGuardEntered should be true when guarded', async function () {
|
||||
await this.mock.guardedCheckEntered();
|
||||
});
|
||||
|
||||
it('_reentrancyGuardEntered should be false when unguarded', async function () {
|
||||
await this.mock.unguardedCheckNotEntered();
|
||||
});
|
||||
|
||||
// The following are more side-effects than intended behavior:
|
||||
// I put them here as documentation, and to monitor any changes
|
||||
// in the side-effects.
|
||||
it('does not allow local recursion', async function () {
|
||||
await expect(this.mock.countLocalRecursive(10n)).to.be.revertedWithCustomError(
|
||||
this.mock,
|
||||
'ReentrancyGuardReentrantCall',
|
||||
);
|
||||
});
|
||||
|
||||
it('does not allow indirect local recursion', async function () {
|
||||
await expect(this.mock.countThisRecursive(10n)).to.be.revertedWith(`${this.name}: failed call`);
|
||||
});
|
||||
});
|
||||
}
|
||||
55
lib_openzeppelin_contracts/test/utils/ShortStrings.t.sol
Normal file
55
lib_openzeppelin_contracts/test/utils/ShortStrings.t.sol
Normal file
@@ -0,0 +1,55 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {Test} from "@forge-std/Test.sol";
|
||||
|
||||
import {ShortStrings, ShortString} from "@openzeppelin/contracts/utils/ShortStrings.sol";
|
||||
|
||||
contract ShortStringsTest is Test {
|
||||
string _fallback;
|
||||
|
||||
function testRoundtripShort(string memory input) external {
|
||||
vm.assume(_isShort(input));
|
||||
ShortString short = ShortStrings.toShortString(input);
|
||||
string memory output = ShortStrings.toString(short);
|
||||
assertEq(input, output);
|
||||
}
|
||||
|
||||
function testRoundtripWithFallback(string memory input, string memory fallbackInitial) external {
|
||||
_fallback = fallbackInitial; // Make sure that the initial value has no effect
|
||||
ShortString short = ShortStrings.toShortStringWithFallback(input, _fallback);
|
||||
string memory output = ShortStrings.toStringWithFallback(short, _fallback);
|
||||
assertEq(input, output);
|
||||
}
|
||||
|
||||
function testRevertLong(string memory input) external {
|
||||
vm.assume(!_isShort(input));
|
||||
vm.expectRevert(abi.encodeWithSelector(ShortStrings.StringTooLong.selector, input));
|
||||
this.toShortString(input);
|
||||
}
|
||||
|
||||
function testLengthShort(string memory input) external {
|
||||
vm.assume(_isShort(input));
|
||||
uint256 inputLength = bytes(input).length;
|
||||
ShortString short = ShortStrings.toShortString(input);
|
||||
uint256 shortLength = ShortStrings.byteLength(short);
|
||||
assertEq(inputLength, shortLength);
|
||||
}
|
||||
|
||||
function testLengthWithFallback(string memory input, string memory fallbackInitial) external {
|
||||
_fallback = fallbackInitial;
|
||||
uint256 inputLength = bytes(input).length;
|
||||
ShortString short = ShortStrings.toShortStringWithFallback(input, _fallback);
|
||||
uint256 shortLength = ShortStrings.byteLengthWithFallback(short, _fallback);
|
||||
assertEq(inputLength, shortLength);
|
||||
}
|
||||
|
||||
function toShortString(string memory input) external pure returns (ShortString) {
|
||||
return ShortStrings.toShortString(input);
|
||||
}
|
||||
|
||||
function _isShort(string memory input) internal pure returns (bool) {
|
||||
return bytes(input).length < 32;
|
||||
}
|
||||
}
|
||||
64
lib_openzeppelin_contracts/test/utils/ShortStrings.test.js
Normal file
64
lib_openzeppelin_contracts/test/utils/ShortStrings.test.js
Normal file
@@ -0,0 +1,64 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
const FALLBACK_SENTINEL = ethers.zeroPadValue('0xFF', 32);
|
||||
|
||||
const length = sstr => parseInt(sstr.slice(64), 16);
|
||||
const decode = sstr => ethers.toUtf8String(sstr).slice(0, length(sstr));
|
||||
const encode = str =>
|
||||
str.length < 32
|
||||
? ethers.concat([
|
||||
ethers.encodeBytes32String(str).slice(0, -2),
|
||||
ethers.zeroPadValue(ethers.toBeArray(str.length), 1),
|
||||
])
|
||||
: FALLBACK_SENTINEL;
|
||||
|
||||
async function fixture() {
|
||||
const mock = await ethers.deployContract('$ShortStrings');
|
||||
return { mock };
|
||||
}
|
||||
|
||||
describe('ShortStrings', function () {
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
for (const str of [0, 1, 16, 31, 32, 64, 1024].map(length => 'a'.repeat(length))) {
|
||||
describe(`with string length ${str.length}`, function () {
|
||||
it('encode / decode', async function () {
|
||||
if (str.length < 32) {
|
||||
const encoded = await this.mock.$toShortString(str);
|
||||
expect(encoded).to.equal(encode(str));
|
||||
expect(decode(encoded)).to.equal(str);
|
||||
|
||||
expect(await this.mock.$byteLength(encoded)).to.equal(str.length);
|
||||
expect(await this.mock.$toString(encoded)).to.equal(str);
|
||||
} else {
|
||||
await expect(this.mock.$toShortString(str))
|
||||
.to.be.revertedWithCustomError(this.mock, 'StringTooLong')
|
||||
.withArgs(str);
|
||||
}
|
||||
});
|
||||
|
||||
it('set / get with fallback', async function () {
|
||||
const short = await this.mock
|
||||
.$toShortStringWithFallback(str, 0)
|
||||
.then(tx => tx.wait())
|
||||
.then(receipt => receipt.logs.find(ev => ev.fragment.name == 'return$toShortStringWithFallback').args[0]);
|
||||
|
||||
expect(short).to.equal(encode(str));
|
||||
|
||||
const promise = this.mock.$toString(short);
|
||||
if (str.length < 32) {
|
||||
expect(await promise).to.equal(str);
|
||||
} else {
|
||||
await expect(promise).to.be.revertedWithCustomError(this.mock, 'InvalidShortString');
|
||||
}
|
||||
|
||||
expect(await this.mock.$byteLengthWithFallback(short, 0)).to.equal(str.length);
|
||||
expect(await this.mock.$toStringWithFallback(short, 0)).to.equal(str);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
203
lib_openzeppelin_contracts/test/utils/SlotDerivation.t.sol
Normal file
203
lib_openzeppelin_contracts/test/utils/SlotDerivation.t.sol
Normal file
@@ -0,0 +1,203 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// This file was procedurally generated from scripts/generate/templates/SlotDerivation.t.js.
|
||||
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {Test} from "@forge-std/Test.sol";
|
||||
|
||||
import {SlotDerivation} from "@openzeppelin/contracts/utils/SlotDerivation.sol";
|
||||
|
||||
contract SlotDerivationTest is Test {
|
||||
using SlotDerivation for bytes32;
|
||||
|
||||
bytes[] private _array;
|
||||
|
||||
function testDeriveArray(uint256 length, uint256 offset) public {
|
||||
length = bound(length, 1, type(uint256).max);
|
||||
offset = bound(offset, 0, length - 1);
|
||||
|
||||
bytes32 baseSlot;
|
||||
assembly {
|
||||
baseSlot := _array.slot
|
||||
sstore(baseSlot, length) // store length so solidity access does not revert
|
||||
}
|
||||
|
||||
bytes storage derived = _array[offset];
|
||||
bytes32 derivedSlot;
|
||||
assembly {
|
||||
derivedSlot := derived.slot
|
||||
}
|
||||
|
||||
assertEq(baseSlot.deriveArray().offset(offset), derivedSlot);
|
||||
}
|
||||
|
||||
mapping(address => bytes) private _addressMapping;
|
||||
|
||||
function testDeriveMappingAddress(address key) public {
|
||||
bytes32 baseSlot;
|
||||
assembly {
|
||||
baseSlot := _addressMapping.slot
|
||||
}
|
||||
|
||||
bytes storage derived = _addressMapping[key];
|
||||
bytes32 derivedSlot;
|
||||
assembly {
|
||||
derivedSlot := derived.slot
|
||||
}
|
||||
|
||||
assertEq(baseSlot.deriveMapping(key), derivedSlot);
|
||||
}
|
||||
|
||||
mapping(bool => bytes) private _boolMapping;
|
||||
|
||||
function testDeriveMappingBoolean(bool key) public {
|
||||
bytes32 baseSlot;
|
||||
assembly {
|
||||
baseSlot := _boolMapping.slot
|
||||
}
|
||||
|
||||
bytes storage derived = _boolMapping[key];
|
||||
bytes32 derivedSlot;
|
||||
assembly {
|
||||
derivedSlot := derived.slot
|
||||
}
|
||||
|
||||
assertEq(baseSlot.deriveMapping(key), derivedSlot);
|
||||
}
|
||||
|
||||
mapping(bytes32 => bytes) private _bytes32Mapping;
|
||||
|
||||
function testDeriveMappingBytes32(bytes32 key) public {
|
||||
bytes32 baseSlot;
|
||||
assembly {
|
||||
baseSlot := _bytes32Mapping.slot
|
||||
}
|
||||
|
||||
bytes storage derived = _bytes32Mapping[key];
|
||||
bytes32 derivedSlot;
|
||||
assembly {
|
||||
derivedSlot := derived.slot
|
||||
}
|
||||
|
||||
assertEq(baseSlot.deriveMapping(key), derivedSlot);
|
||||
}
|
||||
|
||||
mapping(bytes4 => bytes) private _bytes4Mapping;
|
||||
|
||||
function testDeriveMappingBytes4(bytes4 key) public {
|
||||
bytes32 baseSlot;
|
||||
assembly {
|
||||
baseSlot := _bytes4Mapping.slot
|
||||
}
|
||||
|
||||
bytes storage derived = _bytes4Mapping[key];
|
||||
bytes32 derivedSlot;
|
||||
assembly {
|
||||
derivedSlot := derived.slot
|
||||
}
|
||||
|
||||
assertEq(baseSlot.deriveMapping(key), derivedSlot);
|
||||
}
|
||||
|
||||
mapping(uint256 => bytes) private _uint256Mapping;
|
||||
|
||||
function testDeriveMappingUint256(uint256 key) public {
|
||||
bytes32 baseSlot;
|
||||
assembly {
|
||||
baseSlot := _uint256Mapping.slot
|
||||
}
|
||||
|
||||
bytes storage derived = _uint256Mapping[key];
|
||||
bytes32 derivedSlot;
|
||||
assembly {
|
||||
derivedSlot := derived.slot
|
||||
}
|
||||
|
||||
assertEq(baseSlot.deriveMapping(key), derivedSlot);
|
||||
}
|
||||
|
||||
mapping(uint32 => bytes) private _uint32Mapping;
|
||||
|
||||
function testDeriveMappingUint32(uint32 key) public {
|
||||
bytes32 baseSlot;
|
||||
assembly {
|
||||
baseSlot := _uint32Mapping.slot
|
||||
}
|
||||
|
||||
bytes storage derived = _uint32Mapping[key];
|
||||
bytes32 derivedSlot;
|
||||
assembly {
|
||||
derivedSlot := derived.slot
|
||||
}
|
||||
|
||||
assertEq(baseSlot.deriveMapping(key), derivedSlot);
|
||||
}
|
||||
|
||||
mapping(int256 => bytes) private _int256Mapping;
|
||||
|
||||
function testDeriveMappingInt256(int256 key) public {
|
||||
bytes32 baseSlot;
|
||||
assembly {
|
||||
baseSlot := _int256Mapping.slot
|
||||
}
|
||||
|
||||
bytes storage derived = _int256Mapping[key];
|
||||
bytes32 derivedSlot;
|
||||
assembly {
|
||||
derivedSlot := derived.slot
|
||||
}
|
||||
|
||||
assertEq(baseSlot.deriveMapping(key), derivedSlot);
|
||||
}
|
||||
|
||||
mapping(int32 => bytes) private _int32Mapping;
|
||||
|
||||
function testDeriveMappingInt32(int32 key) public {
|
||||
bytes32 baseSlot;
|
||||
assembly {
|
||||
baseSlot := _int32Mapping.slot
|
||||
}
|
||||
|
||||
bytes storage derived = _int32Mapping[key];
|
||||
bytes32 derivedSlot;
|
||||
assembly {
|
||||
derivedSlot := derived.slot
|
||||
}
|
||||
|
||||
assertEq(baseSlot.deriveMapping(key), derivedSlot);
|
||||
}
|
||||
|
||||
mapping(string => bytes) private _stringMapping;
|
||||
|
||||
function testDeriveMappingString(string memory key) public {
|
||||
bytes32 baseSlot;
|
||||
assembly {
|
||||
baseSlot := _stringMapping.slot
|
||||
}
|
||||
|
||||
bytes storage derived = _stringMapping[key];
|
||||
bytes32 derivedSlot;
|
||||
assembly {
|
||||
derivedSlot := derived.slot
|
||||
}
|
||||
|
||||
assertEq(baseSlot.deriveMapping(key), derivedSlot);
|
||||
}
|
||||
|
||||
mapping(bytes => bytes) private _bytesMapping;
|
||||
|
||||
function testDeriveMappingBytes(bytes memory key) public {
|
||||
bytes32 baseSlot;
|
||||
assembly {
|
||||
baseSlot := _bytesMapping.slot
|
||||
}
|
||||
|
||||
bytes storage derived = _bytesMapping[key];
|
||||
bytes32 derivedSlot;
|
||||
assembly {
|
||||
derivedSlot := derived.slot
|
||||
}
|
||||
|
||||
assertEq(baseSlot.deriveMapping(key), derivedSlot);
|
||||
}
|
||||
}
|
||||
58
lib_openzeppelin_contracts/test/utils/SlotDerivation.test.js
Normal file
58
lib_openzeppelin_contracts/test/utils/SlotDerivation.test.js
Normal file
@@ -0,0 +1,58 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
const { erc7201Slot } = require('../helpers/storage');
|
||||
const { generators } = require('../helpers/random');
|
||||
|
||||
async function fixture() {
|
||||
const [account] = await ethers.getSigners();
|
||||
const mock = await ethers.deployContract('$SlotDerivation');
|
||||
return { mock, account };
|
||||
}
|
||||
|
||||
describe('SlotDerivation', function () {
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
describe('namespaces', function () {
|
||||
const namespace = 'example.main';
|
||||
|
||||
it('erc-7201', async function () {
|
||||
expect(await this.mock.$erc7201Slot(namespace)).to.equal(erc7201Slot(namespace));
|
||||
});
|
||||
});
|
||||
|
||||
describe('derivation', function () {
|
||||
it('offset', async function () {
|
||||
const base = generators.bytes32();
|
||||
const offset = generators.uint256();
|
||||
expect(await this.mock.$offset(base, offset)).to.equal((ethers.toBigInt(base) + offset) & ethers.MaxUint256);
|
||||
});
|
||||
|
||||
it('array', async function () {
|
||||
const base = generators.bytes32();
|
||||
expect(await this.mock.$deriveArray(base)).to.equal(ethers.keccak256(base));
|
||||
});
|
||||
|
||||
describe('mapping', function () {
|
||||
for (const { type, key, isValueType } of [
|
||||
{ type: 'bool', key: true, isValueType: true },
|
||||
{ type: 'address', key: generators.address(), isValueType: true },
|
||||
{ type: 'bytes32', key: generators.bytes32(), isValueType: true },
|
||||
{ type: 'uint256', key: generators.uint256(), isValueType: true },
|
||||
{ type: 'int256', key: generators.int256(), isValueType: true },
|
||||
{ type: 'bytes', key: generators.hexBytes(128), isValueType: false },
|
||||
{ type: 'string', key: 'lorem ipsum', isValueType: false },
|
||||
]) {
|
||||
it(type, async function () {
|
||||
const base = generators.bytes32();
|
||||
const expected = isValueType
|
||||
? ethers.keccak256(ethers.AbiCoder.defaultAbiCoder().encode([type, 'bytes32'], [key, base]))
|
||||
: ethers.solidityPackedKeccak256([type, 'bytes32'], [key, base]);
|
||||
expect(await this.mock[`$deriveMapping(bytes32,${type})`](base, key)).to.equal(expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
106
lib_openzeppelin_contracts/test/utils/StorageSlot.test.js
Normal file
106
lib_openzeppelin_contracts/test/utils/StorageSlot.test.js
Normal file
@@ -0,0 +1,106 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
const { generators } = require('../helpers/random');
|
||||
|
||||
const slot = ethers.id('some.storage.slot');
|
||||
const otherSlot = ethers.id('some.other.storage.slot');
|
||||
|
||||
const TYPES = [
|
||||
{ name: 'Boolean', type: 'bool', value: true, isValueType: true, zero: false },
|
||||
{ name: 'Address', type: 'address', value: generators.address(), isValueType: true, zero: generators.address.zero },
|
||||
{ name: 'Bytes32', type: 'bytes32', value: generators.bytes32(), isValueType: true, zero: generators.bytes32.zero },
|
||||
{ name: 'Uint256', type: 'uint256', value: generators.uint256(), isValueType: true, zero: generators.uint256.zero },
|
||||
{ name: 'Int256', type: 'int256', value: generators.int256(), isValueType: true, zero: generators.int256.zero },
|
||||
{ name: 'Bytes', type: 'bytes', value: generators.hexBytes(128), isValueType: false, zero: generators.hexBytes.zero },
|
||||
{ name: 'String', type: 'string', value: 'lorem ipsum', isValueType: false, zero: '' },
|
||||
];
|
||||
|
||||
async function fixture() {
|
||||
return { mock: await ethers.deployContract('StorageSlotMock') };
|
||||
}
|
||||
|
||||
describe('StorageSlot', function () {
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
for (const { name, type, value, zero } of TYPES) {
|
||||
describe(`${type} storage slot`, function () {
|
||||
it('set', async function () {
|
||||
await this.mock.getFunction(`set${name}Slot`)(slot, value);
|
||||
});
|
||||
|
||||
describe('get', function () {
|
||||
beforeEach(async function () {
|
||||
await this.mock.getFunction(`set${name}Slot`)(slot, value);
|
||||
});
|
||||
|
||||
it('from right slot', async function () {
|
||||
expect(await this.mock.getFunction(`get${name}Slot`)(slot)).to.equal(value);
|
||||
});
|
||||
|
||||
it('from other slot', async function () {
|
||||
expect(await this.mock.getFunction(`get${name}Slot`)(otherSlot)).to.equal(zero);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
for (const { name, type, value, zero } of TYPES.filter(type => !type.isValueType)) {
|
||||
describe(`${type} storage pointer`, function () {
|
||||
it('set', async function () {
|
||||
await this.mock.getFunction(`set${name}Storage`)(slot, value);
|
||||
});
|
||||
|
||||
describe('get', function () {
|
||||
beforeEach(async function () {
|
||||
await this.mock.getFunction(`set${name}Storage`)(slot, value);
|
||||
});
|
||||
|
||||
it('from right slot', async function () {
|
||||
expect(await this.mock.getFunction(`${type}Map`)(slot)).to.equal(value);
|
||||
expect(await this.mock.getFunction(`get${name}Storage`)(slot)).to.equal(value);
|
||||
});
|
||||
|
||||
it('from other slot', async function () {
|
||||
expect(await this.mock.getFunction(`${type}Map`)(otherSlot)).to.equal(zero);
|
||||
expect(await this.mock.getFunction(`get${name}Storage`)(otherSlot)).to.equal(zero);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
for (const { name, type, value, zero } of TYPES.filter(type => type.isValueType)) {
|
||||
describe(`${type} transient slot`, function () {
|
||||
const load = `tload${name}(bytes32)`;
|
||||
const store = `tstore(bytes32,${type})`;
|
||||
const event = `${name}Value`;
|
||||
|
||||
it('load', async function () {
|
||||
await expect(this.mock[load](slot)).to.emit(this.mock, event).withArgs(slot, zero);
|
||||
});
|
||||
|
||||
it('store and load (2 txs)', async function () {
|
||||
await this.mock[store](slot, value);
|
||||
await expect(this.mock[load](slot)).to.emit(this.mock, event).withArgs(slot, zero);
|
||||
});
|
||||
|
||||
it('store and load (batched)', async function () {
|
||||
await expect(
|
||||
this.mock.multicall([
|
||||
this.mock.interface.encodeFunctionData(store, [slot, value]),
|
||||
this.mock.interface.encodeFunctionData(load, [slot]),
|
||||
this.mock.interface.encodeFunctionData(load, [otherSlot]),
|
||||
]),
|
||||
)
|
||||
.to.emit(this.mock, event)
|
||||
.withArgs(slot, value)
|
||||
.to.emit(this.mock, event)
|
||||
.withArgs(otherSlot, zero);
|
||||
|
||||
await expect(this.mock[load](slot)).to.emit(this.mock, event).withArgs(slot, zero);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
153
lib_openzeppelin_contracts/test/utils/Strings.test.js
Normal file
153
lib_openzeppelin_contracts/test/utils/Strings.test.js
Normal file
@@ -0,0 +1,153 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
async function fixture() {
|
||||
const mock = await ethers.deployContract('$Strings');
|
||||
return { mock };
|
||||
}
|
||||
|
||||
describe('Strings', function () {
|
||||
before(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
describe('toString', function () {
|
||||
const values = [
|
||||
0n,
|
||||
7n,
|
||||
10n,
|
||||
99n,
|
||||
100n,
|
||||
101n,
|
||||
123n,
|
||||
4132n,
|
||||
12345n,
|
||||
1234567n,
|
||||
1234567890n,
|
||||
123456789012345n,
|
||||
12345678901234567890n,
|
||||
123456789012345678901234567890n,
|
||||
1234567890123456789012345678901234567890n,
|
||||
12345678901234567890123456789012345678901234567890n,
|
||||
123456789012345678901234567890123456789012345678901234567890n,
|
||||
1234567890123456789012345678901234567890123456789012345678901234567890n,
|
||||
];
|
||||
|
||||
describe('uint256', function () {
|
||||
it('converts MAX_UINT256', async function () {
|
||||
const value = ethers.MaxUint256;
|
||||
expect(await this.mock.$toString(value)).to.equal(value.toString(10));
|
||||
});
|
||||
|
||||
for (const value of values) {
|
||||
it(`converts ${value}`, async function () {
|
||||
expect(await this.mock.$toString(value)).to.equal(value);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('int256', function () {
|
||||
it('converts MAX_INT256', async function () {
|
||||
const value = ethers.MaxInt256;
|
||||
expect(await this.mock.$toStringSigned(value)).to.equal(value.toString(10));
|
||||
});
|
||||
|
||||
it('converts MIN_INT256', async function () {
|
||||
const value = ethers.MinInt256;
|
||||
expect(await this.mock.$toStringSigned(value)).to.equal(value.toString(10));
|
||||
});
|
||||
|
||||
for (const value of values) {
|
||||
it(`convert ${value}`, async function () {
|
||||
expect(await this.mock.$toStringSigned(value)).to.equal(value);
|
||||
});
|
||||
|
||||
it(`convert negative ${value}`, async function () {
|
||||
const negated = -value;
|
||||
expect(await this.mock.$toStringSigned(negated)).to.equal(negated.toString(10));
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('toHexString', function () {
|
||||
it('converts 0', async function () {
|
||||
expect(await this.mock.getFunction('$toHexString(uint256)')(0n)).to.equal('0x00');
|
||||
});
|
||||
|
||||
it('converts a positive number', async function () {
|
||||
expect(await this.mock.getFunction('$toHexString(uint256)')(0x4132n)).to.equal('0x4132');
|
||||
});
|
||||
|
||||
it('converts MAX_UINT256', async function () {
|
||||
expect(await this.mock.getFunction('$toHexString(uint256)')(ethers.MaxUint256)).to.equal(
|
||||
`0x${ethers.MaxUint256.toString(16)}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toHexString fixed', function () {
|
||||
it('converts a positive number (long)', async function () {
|
||||
expect(await this.mock.getFunction('$toHexString(uint256,uint256)')(0x4132n, 32n)).to.equal(
|
||||
'0x0000000000000000000000000000000000000000000000000000000000004132',
|
||||
);
|
||||
});
|
||||
|
||||
it('converts a positive number (short)', async function () {
|
||||
const length = 1n;
|
||||
await expect(this.mock.getFunction('$toHexString(uint256,uint256)')(0x4132n, length))
|
||||
.to.be.revertedWithCustomError(this.mock, `StringsInsufficientHexLength`)
|
||||
.withArgs(0x4132, length);
|
||||
});
|
||||
|
||||
it('converts MAX_UINT256', async function () {
|
||||
expect(await this.mock.getFunction('$toHexString(uint256,uint256)')(ethers.MaxUint256, 32n)).to.equal(
|
||||
`0x${ethers.MaxUint256.toString(16)}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toHexString address', function () {
|
||||
it('converts a random address', async function () {
|
||||
const addr = '0xa9036907dccae6a1e0033479b12e837e5cf5a02f';
|
||||
expect(await this.mock.getFunction('$toHexString(address)')(addr)).to.equal(addr);
|
||||
});
|
||||
|
||||
it('converts an address with leading zeros', async function () {
|
||||
const addr = '0x0000e0ca771e21bd00057f54a68c30d400000000';
|
||||
expect(await this.mock.getFunction('$toHexString(address)')(addr)).to.equal(addr);
|
||||
});
|
||||
});
|
||||
|
||||
describe('equal', function () {
|
||||
it('compares two empty strings', async function () {
|
||||
expect(await this.mock.$equal('', '')).to.be.true;
|
||||
});
|
||||
|
||||
it('compares two equal strings', async function () {
|
||||
expect(await this.mock.$equal('a', 'a')).to.be.true;
|
||||
});
|
||||
|
||||
it('compares two different strings', async function () {
|
||||
expect(await this.mock.$equal('a', 'b')).to.be.false;
|
||||
});
|
||||
|
||||
it('compares two different strings of different lengths', async function () {
|
||||
expect(await this.mock.$equal('a', 'aa')).to.be.false;
|
||||
expect(await this.mock.$equal('aa', 'a')).to.be.false;
|
||||
});
|
||||
|
||||
it('compares two different large strings', async function () {
|
||||
const str1 = 'a'.repeat(201);
|
||||
const str2 = 'a'.repeat(200) + 'b';
|
||||
expect(await this.mock.$equal(str1, str2)).to.be.false;
|
||||
});
|
||||
|
||||
it('compares two equal large strings', async function () {
|
||||
const str1 = 'a'.repeat(201);
|
||||
const str2 = 'a'.repeat(201);
|
||||
expect(await this.mock.$equal(str1, str2)).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
213
lib_openzeppelin_contracts/test/utils/cryptography/ECDSA.test.js
Normal file
213
lib_openzeppelin_contracts/test/utils/cryptography/ECDSA.test.js
Normal file
@@ -0,0 +1,213 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
const TEST_MESSAGE = ethers.id('OpenZeppelin');
|
||||
const WRONG_MESSAGE = ethers.id('Nope');
|
||||
const NON_HASH_MESSAGE = '0xabcd';
|
||||
|
||||
async function fixture() {
|
||||
const [signer] = await ethers.getSigners();
|
||||
const mock = await ethers.deployContract('$ECDSA');
|
||||
return { signer, mock };
|
||||
}
|
||||
|
||||
describe('ECDSA', function () {
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
describe('recover with invalid signature', function () {
|
||||
it('with short signature', async function () {
|
||||
await expect(this.mock.$recover(TEST_MESSAGE, '0x1234'))
|
||||
.to.be.revertedWithCustomError(this.mock, 'ECDSAInvalidSignatureLength')
|
||||
.withArgs(2);
|
||||
});
|
||||
|
||||
it('with long signature', async function () {
|
||||
await expect(
|
||||
// eslint-disable-next-line max-len
|
||||
this.mock.$recover(
|
||||
TEST_MESSAGE,
|
||||
'0x01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789',
|
||||
),
|
||||
)
|
||||
.to.be.revertedWithCustomError(this.mock, 'ECDSAInvalidSignatureLength')
|
||||
.withArgs(85);
|
||||
});
|
||||
});
|
||||
|
||||
describe('recover with valid signature', function () {
|
||||
describe('using <signer>.sign', function () {
|
||||
it('returns signer address with correct signature', async function () {
|
||||
// Create the signature
|
||||
const signature = await this.signer.signMessage(TEST_MESSAGE);
|
||||
|
||||
// Recover the signer address from the generated message and signature.
|
||||
expect(await this.mock.$recover(ethers.hashMessage(TEST_MESSAGE), signature)).to.equal(this.signer);
|
||||
});
|
||||
|
||||
it('returns signer address with correct signature for arbitrary length message', async function () {
|
||||
// Create the signature
|
||||
const signature = await this.signer.signMessage(NON_HASH_MESSAGE);
|
||||
|
||||
// Recover the signer address from the generated message and signature.
|
||||
expect(await this.mock.$recover(ethers.hashMessage(NON_HASH_MESSAGE), signature)).to.equal(this.signer);
|
||||
});
|
||||
|
||||
it('returns a different address', async function () {
|
||||
const signature = await this.signer.signMessage(TEST_MESSAGE);
|
||||
expect(await this.mock.$recover(WRONG_MESSAGE, signature)).to.not.be.equal(this.signer);
|
||||
});
|
||||
|
||||
it('reverts with invalid signature', async function () {
|
||||
// eslint-disable-next-line max-len
|
||||
const signature =
|
||||
'0x332ce75a821c982f9127538858900d87d3ec1f9f737338ad67cad133fa48feff48e6fa0c18abc62e42820f05943e47af3e9fbe306ce74d64094bdf1691ee53e01c';
|
||||
await expect(this.mock.$recover(TEST_MESSAGE, signature)).to.be.revertedWithCustomError(
|
||||
this.mock,
|
||||
'ECDSAInvalidSignature',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with v=27 signature', function () {
|
||||
const signer = '0x2cc1166f6212628A0deEf2B33BEFB2187D35b86c';
|
||||
// eslint-disable-next-line max-len
|
||||
const signatureWithoutV =
|
||||
'0x5d99b6f7f6d1f73d1a26497f2b1c89b24c0993913f86e9a2d02cd69887d9c94f3c880358579d811b21dd1b7fd9bb01c1d81d10e69f0384e675c32b39643be892';
|
||||
|
||||
it('works with correct v value', async function () {
|
||||
const v = '0x1b'; // 27 = 1b.
|
||||
const signature = ethers.concat([signatureWithoutV, v]);
|
||||
expect(await this.mock.$recover(TEST_MESSAGE, signature)).to.equal(signer);
|
||||
|
||||
const { r, s, yParityAndS: vs } = ethers.Signature.from(signature);
|
||||
expect(await this.mock.getFunction('$recover(bytes32,uint8,bytes32,bytes32)')(TEST_MESSAGE, v, r, s)).to.equal(
|
||||
signer,
|
||||
);
|
||||
|
||||
expect(await this.mock.getFunction('$recover(bytes32,bytes32,bytes32)')(TEST_MESSAGE, r, vs)).to.equal(signer);
|
||||
});
|
||||
|
||||
it('rejects incorrect v value', async function () {
|
||||
const v = '0x1c'; // 28 = 1c.
|
||||
const signature = ethers.concat([signatureWithoutV, v]);
|
||||
expect(await this.mock.$recover(TEST_MESSAGE, signature)).to.not.equal(signer);
|
||||
|
||||
const { r, s, yParityAndS: vs } = ethers.Signature.from(signature);
|
||||
expect(
|
||||
await this.mock.getFunction('$recover(bytes32,uint8,bytes32,bytes32)')(TEST_MESSAGE, v, r, s),
|
||||
).to.not.equal(signer);
|
||||
|
||||
expect(await this.mock.getFunction('$recover(bytes32,bytes32,bytes32)')(TEST_MESSAGE, r, vs)).to.not.equal(
|
||||
signer,
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts wrong v values', async function () {
|
||||
for (const v of ['0x00', '0x01']) {
|
||||
const signature = ethers.concat([signatureWithoutV, v]);
|
||||
await expect(this.mock.$recover(TEST_MESSAGE, signature)).to.be.revertedWithCustomError(
|
||||
this.mock,
|
||||
'ECDSAInvalidSignature',
|
||||
);
|
||||
|
||||
const { r, s } = ethers.Signature.from(signature);
|
||||
await expect(
|
||||
this.mock.getFunction('$recover(bytes32,uint8,bytes32,bytes32)')(TEST_MESSAGE, v, r, s),
|
||||
).to.be.revertedWithCustomError(this.mock, 'ECDSAInvalidSignature');
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects short EIP2098 format', async function () {
|
||||
const v = '0x1b'; // 27 = 1b.
|
||||
const signature = ethers.concat([signatureWithoutV, v]);
|
||||
|
||||
const { compactSerialized } = ethers.Signature.from(signature);
|
||||
await expect(this.mock.$recover(TEST_MESSAGE, compactSerialized))
|
||||
.to.be.revertedWithCustomError(this.mock, 'ECDSAInvalidSignatureLength')
|
||||
.withArgs(64);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with v=28 signature', function () {
|
||||
const signer = '0x1E318623aB09Fe6de3C9b8672098464Aeda9100E';
|
||||
// eslint-disable-next-line max-len
|
||||
const signatureWithoutV =
|
||||
'0x331fe75a821c982f9127538858900d87d3ec1f9f737338ad67cad133fa48feff48e6fa0c18abc62e42820f05943e47af3e9fbe306ce74d64094bdf1691ee53e0';
|
||||
|
||||
it('works with correct v value', async function () {
|
||||
const v = '0x1c'; // 28 = 1c.
|
||||
const signature = ethers.concat([signatureWithoutV, v]);
|
||||
expect(await this.mock.$recover(TEST_MESSAGE, signature)).to.equal(signer);
|
||||
|
||||
const { r, s, yParityAndS: vs } = ethers.Signature.from(signature);
|
||||
expect(await this.mock.getFunction('$recover(bytes32,uint8,bytes32,bytes32)')(TEST_MESSAGE, v, r, s)).to.equal(
|
||||
signer,
|
||||
);
|
||||
|
||||
expect(await this.mock.getFunction('$recover(bytes32,bytes32,bytes32)')(TEST_MESSAGE, r, vs)).to.equal(signer);
|
||||
});
|
||||
|
||||
it('rejects incorrect v value', async function () {
|
||||
const v = '0x1b'; // 27 = 1b.
|
||||
const signature = ethers.concat([signatureWithoutV, v]);
|
||||
expect(await this.mock.$recover(TEST_MESSAGE, signature)).to.not.equal(signer);
|
||||
|
||||
const { r, s, yParityAndS: vs } = ethers.Signature.from(signature);
|
||||
expect(
|
||||
await this.mock.getFunction('$recover(bytes32,uint8,bytes32,bytes32)')(TEST_MESSAGE, v, r, s),
|
||||
).to.not.equal(signer);
|
||||
|
||||
expect(await this.mock.getFunction('$recover(bytes32,bytes32,bytes32)')(TEST_MESSAGE, r, vs)).to.not.equal(
|
||||
signer,
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts invalid v values', async function () {
|
||||
for (const v of ['0x00', '0x01']) {
|
||||
const signature = ethers.concat([signatureWithoutV, v]);
|
||||
await expect(this.mock.$recover(TEST_MESSAGE, signature)).to.be.revertedWithCustomError(
|
||||
this.mock,
|
||||
'ECDSAInvalidSignature',
|
||||
);
|
||||
|
||||
const { r, s } = ethers.Signature.from(signature);
|
||||
await expect(
|
||||
this.mock.getFunction('$recover(bytes32,uint8,bytes32,bytes32)')(TEST_MESSAGE, v, r, s),
|
||||
).to.be.revertedWithCustomError(this.mock, 'ECDSAInvalidSignature');
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects short EIP2098 format', async function () {
|
||||
const v = '0x1b'; // 28 = 1b.
|
||||
const signature = ethers.concat([signatureWithoutV, v]);
|
||||
|
||||
const { compactSerialized } = ethers.Signature.from(signature);
|
||||
await expect(this.mock.$recover(TEST_MESSAGE, compactSerialized))
|
||||
.to.be.revertedWithCustomError(this.mock, 'ECDSAInvalidSignatureLength')
|
||||
.withArgs(64);
|
||||
});
|
||||
});
|
||||
|
||||
it('reverts with high-s value signature', async function () {
|
||||
const message = '0xb94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9';
|
||||
// eslint-disable-next-line max-len
|
||||
const highSSignature =
|
||||
'0xe742ff452d41413616a5bf43fe15dd88294e983d3d36206c2712f39083d638bde0a0fc89be718fbc1033e1d30d78be1c68081562ed2e97af876f286f3453231d1b';
|
||||
|
||||
const r = ethers.dataSlice(highSSignature, 0, 32);
|
||||
const s = ethers.dataSlice(highSSignature, 32, 64);
|
||||
const v = ethers.dataSlice(highSSignature, 64, 65);
|
||||
|
||||
await expect(this.mock.$recover(message, highSSignature))
|
||||
.to.be.revertedWithCustomError(this.mock, 'ECDSAInvalidSignatureS')
|
||||
.withArgs(s);
|
||||
await expect(this.mock.getFunction('$recover(bytes32,uint8,bytes32,bytes32)')(TEST_MESSAGE, v, r, s))
|
||||
.to.be.revertedWithCustomError(this.mock, 'ECDSAInvalidSignatureS')
|
||||
.withArgs(s);
|
||||
expect(() => ethers.Signature.from(highSSignature)).to.throw('non-canonical s');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,105 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
const { getDomain, domainSeparator, hashTypedData } = require('../../helpers/eip712');
|
||||
const { formatType } = require('../../helpers/eip712-types');
|
||||
|
||||
const LENGTHS = {
|
||||
short: ['A Name', '1'],
|
||||
long: ['A'.repeat(40), 'B'.repeat(40)],
|
||||
};
|
||||
|
||||
const fixture = async () => {
|
||||
const [from, to] = await ethers.getSigners();
|
||||
|
||||
const lengths = {};
|
||||
for (const [shortOrLong, [name, version]] of Object.entries(LENGTHS)) {
|
||||
lengths[shortOrLong] = { name, version };
|
||||
lengths[shortOrLong].eip712 = await ethers.deployContract('$EIP712Verifier', [name, version]);
|
||||
lengths[shortOrLong].domain = {
|
||||
name,
|
||||
version,
|
||||
chainId: await ethers.provider.getNetwork().then(({ chainId }) => chainId),
|
||||
verifyingContract: lengths[shortOrLong].eip712.target,
|
||||
};
|
||||
}
|
||||
|
||||
return { from, to, lengths };
|
||||
};
|
||||
|
||||
describe('EIP712', function () {
|
||||
for (const [shortOrLong, [name, version]] of Object.entries(LENGTHS)) {
|
||||
describe(`with ${shortOrLong} name and version`, function () {
|
||||
beforeEach('deploying', async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
Object.assign(this, this.lengths[shortOrLong]);
|
||||
});
|
||||
|
||||
describe('domain separator', function () {
|
||||
it('is internally available', async function () {
|
||||
const expected = await domainSeparator(this.domain);
|
||||
|
||||
expect(await this.eip712.$_domainSeparatorV4()).to.equal(expected);
|
||||
});
|
||||
|
||||
it("can be rebuilt using EIP-5267's eip712Domain", async function () {
|
||||
const rebuildDomain = await getDomain(this.eip712);
|
||||
expect(rebuildDomain).to.be.deep.equal(this.domain);
|
||||
});
|
||||
|
||||
if (shortOrLong === 'short') {
|
||||
// Long strings are in storage, and the proxy will not be properly initialized unless
|
||||
// the upgradeable contract variant is used and the initializer is invoked.
|
||||
|
||||
it('adjusts when behind proxy', async function () {
|
||||
const factory = await ethers.deployContract('$Clones');
|
||||
|
||||
const clone = await factory
|
||||
.$clone(this.eip712)
|
||||
.then(tx => tx.wait())
|
||||
.then(receipt => receipt.logs.find(ev => ev.fragment.name == 'return$clone_address').args.instance)
|
||||
.then(address => ethers.getContractAt('$EIP712Verifier', address));
|
||||
|
||||
const expectedDomain = { ...this.domain, verifyingContract: clone.target };
|
||||
expect(await getDomain(clone)).to.be.deep.equal(expectedDomain);
|
||||
|
||||
const expectedSeparator = await domainSeparator(expectedDomain);
|
||||
expect(await clone.$_domainSeparatorV4()).to.equal(expectedSeparator);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('hash digest', async function () {
|
||||
const structhash = ethers.hexlify(ethers.randomBytes(32));
|
||||
expect(await this.eip712.$_hashTypedDataV4(structhash)).to.equal(hashTypedData(this.domain, structhash));
|
||||
});
|
||||
|
||||
it('digest', async function () {
|
||||
const types = {
|
||||
Mail: formatType({
|
||||
to: 'address',
|
||||
contents: 'string',
|
||||
}),
|
||||
};
|
||||
|
||||
const message = {
|
||||
to: this.to.address,
|
||||
contents: 'very interesting',
|
||||
};
|
||||
|
||||
const signature = await this.from.signTypedData(this.domain, types, message);
|
||||
|
||||
await expect(this.eip712.verify(signature, this.from.address, message.to, message.contents)).to.not.be.reverted;
|
||||
});
|
||||
|
||||
it('name', async function () {
|
||||
expect(await this.eip712.$_EIP712Name()).to.equal(name);
|
||||
});
|
||||
|
||||
it('version', async function () {
|
||||
expect(await this.eip712.$_EIP712Version()).to.equal(version);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,173 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
const { StandardMerkleTree } = require('@openzeppelin/merkle-tree');
|
||||
|
||||
const toElements = str => str.split('').map(e => [e]);
|
||||
const hashPair = (a, b) => ethers.keccak256(Buffer.concat([a, b].sort(Buffer.compare)));
|
||||
|
||||
async function fixture() {
|
||||
const mock = await ethers.deployContract('$MerkleProof');
|
||||
return { mock };
|
||||
}
|
||||
|
||||
describe('MerkleProof', function () {
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
describe('verify', function () {
|
||||
it('returns true for a valid Merkle proof', async function () {
|
||||
const merkleTree = StandardMerkleTree.of(
|
||||
toElements('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='),
|
||||
['string'],
|
||||
);
|
||||
|
||||
const root = merkleTree.root;
|
||||
const hash = merkleTree.leafHash(['A']);
|
||||
const proof = merkleTree.getProof(['A']);
|
||||
|
||||
expect(await this.mock.$verify(proof, root, hash)).to.be.true;
|
||||
expect(await this.mock.$verifyCalldata(proof, root, hash)).to.be.true;
|
||||
|
||||
// For demonstration, it is also possible to create valid proofs for certain 64-byte values *not* in elements:
|
||||
const noSuchLeaf = hashPair(
|
||||
ethers.toBeArray(merkleTree.leafHash(['A'])),
|
||||
ethers.toBeArray(merkleTree.leafHash(['B'])),
|
||||
);
|
||||
expect(await this.mock.$verify(proof.slice(1), root, noSuchLeaf)).to.be.true;
|
||||
expect(await this.mock.$verifyCalldata(proof.slice(1), root, noSuchLeaf)).to.be.true;
|
||||
});
|
||||
|
||||
it('returns false for an invalid Merkle proof', async function () {
|
||||
const correctMerkleTree = StandardMerkleTree.of(toElements('abc'), ['string']);
|
||||
const otherMerkleTree = StandardMerkleTree.of(toElements('def'), ['string']);
|
||||
|
||||
const root = correctMerkleTree.root;
|
||||
const hash = correctMerkleTree.leafHash(['a']);
|
||||
const proof = otherMerkleTree.getProof(['d']);
|
||||
|
||||
expect(await this.mock.$verify(proof, root, hash)).to.be.false;
|
||||
expect(await this.mock.$verifyCalldata(proof, root, hash)).to.be.false;
|
||||
});
|
||||
|
||||
it('returns false for a Merkle proof of invalid length', async function () {
|
||||
const merkleTree = StandardMerkleTree.of(toElements('abc'), ['string']);
|
||||
|
||||
const root = merkleTree.root;
|
||||
const hash = merkleTree.leafHash(['a']);
|
||||
const proof = merkleTree.getProof(['a']);
|
||||
const badProof = proof.slice(0, -1);
|
||||
|
||||
expect(await this.mock.$verify(badProof, root, hash)).to.be.false;
|
||||
expect(await this.mock.$verifyCalldata(badProof, root, hash)).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe('multiProofVerify', function () {
|
||||
it('returns true for a valid Merkle multi proof', async function () {
|
||||
const merkleTree = StandardMerkleTree.of(toElements('abcdef'), ['string']);
|
||||
|
||||
const root = merkleTree.root;
|
||||
const { proof, proofFlags, leaves } = merkleTree.getMultiProof(toElements('bdf'));
|
||||
const hashes = leaves.map(e => merkleTree.leafHash(e));
|
||||
|
||||
expect(await this.mock.$multiProofVerify(proof, proofFlags, root, hashes)).to.be.true;
|
||||
expect(await this.mock.$multiProofVerifyCalldata(proof, proofFlags, root, hashes)).to.be.true;
|
||||
});
|
||||
|
||||
it('returns false for an invalid Merkle multi proof', async function () {
|
||||
const merkleTree = StandardMerkleTree.of(toElements('abcdef'), ['string']);
|
||||
const otherMerkleTree = StandardMerkleTree.of(toElements('ghi'), ['string']);
|
||||
|
||||
const root = merkleTree.root;
|
||||
const { proof, proofFlags, leaves } = otherMerkleTree.getMultiProof(toElements('ghi'));
|
||||
const hashes = leaves.map(e => merkleTree.leafHash(e));
|
||||
|
||||
expect(await this.mock.$multiProofVerify(proof, proofFlags, root, hashes)).to.be.false;
|
||||
expect(await this.mock.$multiProofVerifyCalldata(proof, proofFlags, root, hashes)).to.be.false;
|
||||
});
|
||||
|
||||
it('revert with invalid multi proof #1', async function () {
|
||||
const merkleTree = StandardMerkleTree.of(toElements('abcd'), ['string']);
|
||||
|
||||
const root = merkleTree.root;
|
||||
const hashA = merkleTree.leafHash(['a']);
|
||||
const hashB = merkleTree.leafHash(['b']);
|
||||
const hashCD = hashPair(
|
||||
ethers.toBeArray(merkleTree.leafHash(['c'])),
|
||||
ethers.toBeArray(merkleTree.leafHash(['d'])),
|
||||
);
|
||||
const hashE = merkleTree.leafHash(['e']); // incorrect (not part of the tree)
|
||||
const fill = ethers.randomBytes(32);
|
||||
|
||||
await expect(
|
||||
this.mock.$multiProofVerify([hashB, fill, hashCD], [false, false, false], root, [hashA, hashE]),
|
||||
).to.be.revertedWithCustomError(this.mock, 'MerkleProofInvalidMultiproof');
|
||||
|
||||
await expect(
|
||||
this.mock.$multiProofVerifyCalldata([hashB, fill, hashCD], [false, false, false], root, [hashA, hashE]),
|
||||
).to.be.revertedWithCustomError(this.mock, 'MerkleProofInvalidMultiproof');
|
||||
});
|
||||
|
||||
it('revert with invalid multi proof #2', async function () {
|
||||
const merkleTree = StandardMerkleTree.of(toElements('abcd'), ['string']);
|
||||
|
||||
const root = merkleTree.root;
|
||||
const hashA = merkleTree.leafHash(['a']);
|
||||
const hashB = merkleTree.leafHash(['b']);
|
||||
const hashCD = hashPair(
|
||||
ethers.toBeArray(merkleTree.leafHash(['c'])),
|
||||
ethers.toBeArray(merkleTree.leafHash(['d'])),
|
||||
);
|
||||
const hashE = merkleTree.leafHash(['e']); // incorrect (not part of the tree)
|
||||
const fill = ethers.randomBytes(32);
|
||||
|
||||
await expect(
|
||||
this.mock.$multiProofVerify([hashB, fill, hashCD], [false, false, false, false], root, [hashE, hashA]),
|
||||
).to.be.revertedWithPanic(0x32);
|
||||
|
||||
await expect(
|
||||
this.mock.$multiProofVerifyCalldata([hashB, fill, hashCD], [false, false, false, false], root, [hashE, hashA]),
|
||||
).to.be.revertedWithPanic(0x32);
|
||||
});
|
||||
|
||||
it('limit case: works for tree containing a single leaf', async function () {
|
||||
const merkleTree = StandardMerkleTree.of(toElements('a'), ['string']);
|
||||
|
||||
const root = merkleTree.root;
|
||||
const { proof, proofFlags, leaves } = merkleTree.getMultiProof(toElements('a'));
|
||||
const hashes = leaves.map(e => merkleTree.leafHash(e));
|
||||
|
||||
expect(await this.mock.$multiProofVerify(proof, proofFlags, root, hashes)).to.be.true;
|
||||
expect(await this.mock.$multiProofVerifyCalldata(proof, proofFlags, root, hashes)).to.be.true;
|
||||
});
|
||||
|
||||
it('limit case: can prove empty leaves', async function () {
|
||||
const merkleTree = StandardMerkleTree.of(toElements('abcd'), ['string']);
|
||||
|
||||
const root = merkleTree.root;
|
||||
expect(await this.mock.$multiProofVerify([root], [], root, [])).to.be.true;
|
||||
expect(await this.mock.$multiProofVerifyCalldata([root], [], root, [])).to.be.true;
|
||||
});
|
||||
|
||||
it('reverts processing manipulated proofs with a zero-value node at depth 1', async function () {
|
||||
// Create a merkle tree that contains a zero leaf at depth 1
|
||||
const leave = ethers.id('real leaf');
|
||||
const root = hashPair(ethers.toBeArray(leave), Buffer.alloc(32, 0));
|
||||
|
||||
// Now we can pass any **malicious** fake leaves as valid!
|
||||
const maliciousLeaves = ['malicious', 'leaves'].map(ethers.id).map(ethers.toBeArray).sort(Buffer.compare);
|
||||
const maliciousProof = [leave, leave];
|
||||
const maliciousProofFlags = [true, true, false];
|
||||
|
||||
await expect(
|
||||
this.mock.$multiProofVerify(maliciousProof, maliciousProofFlags, root, maliciousLeaves),
|
||||
).to.be.revertedWithCustomError(this.mock, 'MerkleProofInvalidMultiproof');
|
||||
|
||||
await expect(
|
||||
this.mock.$multiProofVerifyCalldata(maliciousProof, maliciousProofFlags, root, maliciousLeaves),
|
||||
).to.be.revertedWithCustomError(this.mock, 'MerkleProofInvalidMultiproof');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
const { domainSeparator, hashTypedData } = require('../../helpers/eip712');
|
||||
|
||||
async function fixture() {
|
||||
const mock = await ethers.deployContract('$MessageHashUtils');
|
||||
return { mock };
|
||||
}
|
||||
|
||||
describe('MessageHashUtils', function () {
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
describe('toEthSignedMessageHash', function () {
|
||||
it('prefixes bytes32 data correctly', async function () {
|
||||
const message = ethers.randomBytes(32);
|
||||
const expectedHash = ethers.hashMessage(message);
|
||||
|
||||
expect(await this.mock.getFunction('$toEthSignedMessageHash(bytes32)')(message)).to.equal(expectedHash);
|
||||
});
|
||||
|
||||
it('prefixes dynamic length data correctly', async function () {
|
||||
const message = ethers.randomBytes(128);
|
||||
const expectedHash = ethers.hashMessage(message);
|
||||
|
||||
expect(await this.mock.getFunction('$toEthSignedMessageHash(bytes)')(message)).to.equal(expectedHash);
|
||||
});
|
||||
|
||||
it('version match for bytes32', async function () {
|
||||
const message = ethers.randomBytes(32);
|
||||
const fixed = await this.mock.getFunction('$toEthSignedMessageHash(bytes32)')(message);
|
||||
const dynamic = await this.mock.getFunction('$toEthSignedMessageHash(bytes)')(message);
|
||||
|
||||
expect(fixed).to.equal(dynamic);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toDataWithIntendedValidatorHash', function () {
|
||||
it('returns the digest correctly', async function () {
|
||||
const verifier = ethers.Wallet.createRandom().address;
|
||||
const message = ethers.randomBytes(128);
|
||||
const expectedHash = ethers.solidityPackedKeccak256(
|
||||
['string', 'address', 'bytes'],
|
||||
['\x19\x00', verifier, message],
|
||||
);
|
||||
|
||||
expect(await this.mock.$toDataWithIntendedValidatorHash(verifier, message)).to.equal(expectedHash);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toTypedDataHash', function () {
|
||||
it('returns the digest correctly', async function () {
|
||||
const domain = {
|
||||
name: 'Test',
|
||||
version: '1',
|
||||
chainId: 1n,
|
||||
verifyingContract: ethers.Wallet.createRandom().address,
|
||||
};
|
||||
const structhash = ethers.randomBytes(32);
|
||||
const expectedHash = hashTypedData(domain, structhash);
|
||||
|
||||
expect(await this.mock.$toTypedDataHash(domainSeparator(domain), structhash)).to.equal(expectedHash);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
const TEST_MESSAGE = ethers.id('OpenZeppelin');
|
||||
const TEST_MESSAGE_HASH = ethers.hashMessage(TEST_MESSAGE);
|
||||
|
||||
const WRONG_MESSAGE = ethers.id('Nope');
|
||||
const WRONG_MESSAGE_HASH = ethers.hashMessage(WRONG_MESSAGE);
|
||||
|
||||
async function fixture() {
|
||||
const [signer, other] = await ethers.getSigners();
|
||||
const mock = await ethers.deployContract('$SignatureChecker');
|
||||
const wallet = await ethers.deployContract('ERC1271WalletMock', [signer]);
|
||||
const malicious = await ethers.deployContract('ERC1271MaliciousMock');
|
||||
const signature = await signer.signMessage(TEST_MESSAGE);
|
||||
|
||||
return { signer, other, mock, wallet, malicious, signature };
|
||||
}
|
||||
|
||||
describe('SignatureChecker (ERC1271)', function () {
|
||||
before('deploying', async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
describe('EOA account', function () {
|
||||
it('with matching signer and signature', async function () {
|
||||
expect(await this.mock.$isValidSignatureNow(this.signer, TEST_MESSAGE_HASH, this.signature)).to.be.true;
|
||||
});
|
||||
|
||||
it('with invalid signer', async function () {
|
||||
expect(await this.mock.$isValidSignatureNow(this.other, TEST_MESSAGE_HASH, this.signature)).to.be.false;
|
||||
});
|
||||
|
||||
it('with invalid signature', async function () {
|
||||
expect(await this.mock.$isValidSignatureNow(this.signer, WRONG_MESSAGE_HASH, this.signature)).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe('ERC1271 wallet', function () {
|
||||
for (const fn of ['isValidERC1271SignatureNow', 'isValidSignatureNow']) {
|
||||
describe(fn, function () {
|
||||
it('with matching signer and signature', async function () {
|
||||
expect(await this.mock.getFunction(`$${fn}`)(this.wallet, TEST_MESSAGE_HASH, this.signature)).to.be.true;
|
||||
});
|
||||
|
||||
it('with invalid signer', async function () {
|
||||
expect(await this.mock.getFunction(`$${fn}`)(this.mock, TEST_MESSAGE_HASH, this.signature)).to.be.false;
|
||||
});
|
||||
|
||||
it('with invalid signature', async function () {
|
||||
expect(await this.mock.getFunction(`$${fn}`)(this.wallet, WRONG_MESSAGE_HASH, this.signature)).to.be.false;
|
||||
});
|
||||
|
||||
it('with malicious wallet', async function () {
|
||||
expect(await this.mock.getFunction(`$${fn}`)(this.malicious, TEST_MESSAGE_HASH, this.signature)).to.be.false;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
const { shouldSupportInterfaces } = require('./SupportsInterface.behavior');
|
||||
|
||||
async function fixture() {
|
||||
return {
|
||||
mock: await ethers.deployContract('$ERC165'),
|
||||
};
|
||||
}
|
||||
|
||||
describe('ERC165', function () {
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
shouldSupportInterfaces();
|
||||
});
|
||||
@@ -0,0 +1,245 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
const DUMMY_ID = '0xdeadbeef';
|
||||
const DUMMY_ID_2 = '0xcafebabe';
|
||||
const DUMMY_ID_3 = '0xdecafbad';
|
||||
const DUMMY_UNSUPPORTED_ID = '0xbaddcafe';
|
||||
const DUMMY_UNSUPPORTED_ID_2 = '0xbaadcafe';
|
||||
const DUMMY_ACCOUNT = '0x1111111111111111111111111111111111111111';
|
||||
|
||||
async function fixture() {
|
||||
return { mock: await ethers.deployContract('$ERC165Checker') };
|
||||
}
|
||||
|
||||
describe('ERC165Checker', function () {
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
describe('ERC165 missing return data', function () {
|
||||
before(async function () {
|
||||
this.target = await ethers.deployContract('ERC165MissingData');
|
||||
});
|
||||
|
||||
it('does not support ERC165', async function () {
|
||||
expect(await this.mock.$supportsERC165(this.target)).to.be.false;
|
||||
});
|
||||
|
||||
it('does not support mock interface via supportsInterface', async function () {
|
||||
expect(await this.mock.$supportsInterface(this.target, DUMMY_ID)).to.be.false;
|
||||
});
|
||||
|
||||
it('does not support mock interface via supportsAllInterfaces', async function () {
|
||||
expect(await this.mock.$supportsAllInterfaces(this.target, [DUMMY_ID])).to.be.false;
|
||||
});
|
||||
|
||||
it('does not support mock interface via getSupportedInterfaces', async function () {
|
||||
expect(await this.mock.$getSupportedInterfaces(this.target, [DUMMY_ID])).to.deep.equal([false]);
|
||||
});
|
||||
|
||||
it('does not support mock interface via supportsERC165InterfaceUnchecked', async function () {
|
||||
expect(await this.mock.$supportsERC165InterfaceUnchecked(this.target, DUMMY_ID)).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe('ERC165 malicious return data', function () {
|
||||
beforeEach(async function () {
|
||||
this.target = await ethers.deployContract('ERC165MaliciousData');
|
||||
});
|
||||
|
||||
it('does not support ERC165', async function () {
|
||||
expect(await this.mock.$supportsERC165(this.target)).to.be.false;
|
||||
});
|
||||
|
||||
it('does not support mock interface via supportsInterface', async function () {
|
||||
expect(await this.mock.$supportsInterface(this.target, DUMMY_ID)).to.be.false;
|
||||
});
|
||||
|
||||
it('does not support mock interface via supportsAllInterfaces', async function () {
|
||||
expect(await this.mock.$supportsAllInterfaces(this.target, [DUMMY_ID])).to.be.false;
|
||||
});
|
||||
|
||||
it('does not support mock interface via getSupportedInterfaces', async function () {
|
||||
expect(await this.mock.$getSupportedInterfaces(this.target, [DUMMY_ID])).to.deep.equal([false]);
|
||||
});
|
||||
|
||||
it('does not support mock interface via supportsERC165InterfaceUnchecked', async function () {
|
||||
expect(await this.mock.$supportsERC165InterfaceUnchecked(this.target, DUMMY_ID)).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('ERC165 not supported', function () {
|
||||
beforeEach(async function () {
|
||||
this.target = await ethers.deployContract('ERC165NotSupported');
|
||||
});
|
||||
|
||||
it('does not support ERC165', async function () {
|
||||
expect(await this.mock.$supportsERC165(this.target)).to.be.false;
|
||||
});
|
||||
|
||||
it('does not support mock interface via supportsInterface', async function () {
|
||||
expect(await this.mock.$supportsInterface(this.target, DUMMY_ID)).to.be.false;
|
||||
});
|
||||
|
||||
it('does not support mock interface via supportsAllInterfaces', async function () {
|
||||
expect(await this.mock.$supportsAllInterfaces(this.target, [DUMMY_ID])).to.be.false;
|
||||
});
|
||||
|
||||
it('does not support mock interface via getSupportedInterfaces', async function () {
|
||||
expect(await this.mock.$getSupportedInterfaces(this.target, [DUMMY_ID])).to.deep.equal([false]);
|
||||
});
|
||||
|
||||
it('does not support mock interface via supportsERC165InterfaceUnchecked', async function () {
|
||||
expect(await this.mock.$supportsERC165InterfaceUnchecked(this.target, DUMMY_ID)).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe('ERC165 supported', function () {
|
||||
beforeEach(async function () {
|
||||
this.target = await ethers.deployContract('ERC165InterfacesSupported', [[]]);
|
||||
});
|
||||
|
||||
it('supports ERC165', async function () {
|
||||
expect(await this.mock.$supportsERC165(this.target)).to.be.true;
|
||||
});
|
||||
|
||||
it('does not support mock interface via supportsInterface', async function () {
|
||||
expect(await this.mock.$supportsInterface(this.target, DUMMY_ID)).to.be.false;
|
||||
});
|
||||
|
||||
it('does not support mock interface via supportsAllInterfaces', async function () {
|
||||
expect(await this.mock.$supportsAllInterfaces(this.target, [DUMMY_ID])).to.be.false;
|
||||
});
|
||||
|
||||
it('does not support mock interface via getSupportedInterfaces', async function () {
|
||||
expect(await this.mock.$getSupportedInterfaces(this.target, [DUMMY_ID])).to.deep.equal([false]);
|
||||
});
|
||||
|
||||
it('does not support mock interface via supportsERC165InterfaceUnchecked', async function () {
|
||||
expect(await this.mock.$supportsERC165InterfaceUnchecked(this.target, DUMMY_ID)).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe('ERC165 and single interface supported', function () {
|
||||
beforeEach(async function () {
|
||||
this.target = await ethers.deployContract('ERC165InterfacesSupported', [[DUMMY_ID]]);
|
||||
});
|
||||
|
||||
it('supports ERC165', async function () {
|
||||
expect(await this.mock.$supportsERC165(this.target)).to.be.true;
|
||||
});
|
||||
|
||||
it('supports mock interface via supportsInterface', async function () {
|
||||
expect(await this.mock.$supportsInterface(this.target, DUMMY_ID)).to.be.true;
|
||||
});
|
||||
|
||||
it('supports mock interface via supportsAllInterfaces', async function () {
|
||||
expect(await this.mock.$supportsAllInterfaces(this.target, [DUMMY_ID])).to.be.true;
|
||||
});
|
||||
|
||||
it('supports mock interface via getSupportedInterfaces', async function () {
|
||||
expect(await this.mock.$getSupportedInterfaces(this.target, [DUMMY_ID])).to.deep.equal([true]);
|
||||
});
|
||||
|
||||
it('supports mock interface via supportsERC165InterfaceUnchecked', async function () {
|
||||
expect(await this.mock.$supportsERC165InterfaceUnchecked(this.target, DUMMY_ID)).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('ERC165 and many interfaces supported', function () {
|
||||
const supportedInterfaces = [DUMMY_ID, DUMMY_ID_2, DUMMY_ID_3];
|
||||
beforeEach(async function () {
|
||||
this.target = await ethers.deployContract('ERC165InterfacesSupported', [supportedInterfaces]);
|
||||
});
|
||||
|
||||
it('supports ERC165', async function () {
|
||||
expect(await this.mock.$supportsERC165(this.target)).to.be.true;
|
||||
});
|
||||
|
||||
it('supports each interfaceId via supportsInterface', async function () {
|
||||
for (const interfaceId of supportedInterfaces) {
|
||||
expect(await this.mock.$supportsInterface(this.target, interfaceId)).to.be.true;
|
||||
}
|
||||
});
|
||||
|
||||
it('supports all interfaceIds via supportsAllInterfaces', async function () {
|
||||
expect(await this.mock.$supportsAllInterfaces(this.target, supportedInterfaces)).to.be.true;
|
||||
});
|
||||
|
||||
it('supports none of the interfaces queried via supportsAllInterfaces', async function () {
|
||||
const interfaceIdsToTest = [DUMMY_UNSUPPORTED_ID, DUMMY_UNSUPPORTED_ID_2];
|
||||
|
||||
expect(await this.mock.$supportsAllInterfaces(this.target, interfaceIdsToTest)).to.be.false;
|
||||
});
|
||||
|
||||
it('supports not all of the interfaces queried via supportsAllInterfaces', async function () {
|
||||
const interfaceIdsToTest = [...supportedInterfaces, DUMMY_UNSUPPORTED_ID];
|
||||
expect(await this.mock.$supportsAllInterfaces(this.target, interfaceIdsToTest)).to.be.false;
|
||||
});
|
||||
|
||||
it('supports all interfaceIds via getSupportedInterfaces', async function () {
|
||||
expect(await this.mock.$getSupportedInterfaces(this.target, supportedInterfaces)).to.deep.equal(
|
||||
supportedInterfaces.map(i => supportedInterfaces.includes(i)),
|
||||
);
|
||||
});
|
||||
|
||||
it('supports none of the interfaces queried via getSupportedInterfaces', async function () {
|
||||
const interfaceIdsToTest = [DUMMY_UNSUPPORTED_ID, DUMMY_UNSUPPORTED_ID_2];
|
||||
|
||||
expect(await this.mock.$getSupportedInterfaces(this.target, interfaceIdsToTest)).to.deep.equal(
|
||||
interfaceIdsToTest.map(i => supportedInterfaces.includes(i)),
|
||||
);
|
||||
});
|
||||
|
||||
it('supports not all of the interfaces queried via getSupportedInterfaces', async function () {
|
||||
const interfaceIdsToTest = [...supportedInterfaces, DUMMY_UNSUPPORTED_ID];
|
||||
|
||||
expect(await this.mock.$getSupportedInterfaces(this.target, interfaceIdsToTest)).to.deep.equal(
|
||||
interfaceIdsToTest.map(i => supportedInterfaces.includes(i)),
|
||||
);
|
||||
});
|
||||
|
||||
it('supports each interfaceId via supportsERC165InterfaceUnchecked', async function () {
|
||||
for (const interfaceId of supportedInterfaces) {
|
||||
expect(await this.mock.$supportsERC165InterfaceUnchecked(this.target, interfaceId)).to.be.true;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('account address does not support ERC165', function () {
|
||||
it('does not support ERC165', async function () {
|
||||
expect(await this.mock.$supportsERC165(DUMMY_ACCOUNT)).to.be.false;
|
||||
});
|
||||
|
||||
it('does not support mock interface via supportsInterface', async function () {
|
||||
expect(await this.mock.$supportsInterface(DUMMY_ACCOUNT, DUMMY_ID)).to.be.false;
|
||||
});
|
||||
|
||||
it('does not support mock interface via supportsAllInterfaces', async function () {
|
||||
expect(await this.mock.$supportsAllInterfaces(DUMMY_ACCOUNT, [DUMMY_ID])).to.be.false;
|
||||
});
|
||||
|
||||
it('does not support mock interface via getSupportedInterfaces', async function () {
|
||||
expect(await this.mock.$getSupportedInterfaces(DUMMY_ACCOUNT, [DUMMY_ID])).to.deep.equal([false]);
|
||||
});
|
||||
|
||||
it('does not support mock interface via supportsERC165InterfaceUnchecked', async function () {
|
||||
expect(await this.mock.$supportsERC165InterfaceUnchecked(DUMMY_ACCOUNT, DUMMY_ID)).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
it('Return bomb resistance', async function () {
|
||||
this.target = await ethers.deployContract('ERC165ReturnBombMock');
|
||||
|
||||
const { gasUsed: gasUsed1 } = await this.mock.$supportsInterface.send(this.target, DUMMY_ID).then(tx => tx.wait());
|
||||
expect(gasUsed1).to.be.lessThan(120_000n); // 3*30k + 21k + some margin
|
||||
|
||||
const { gasUsed: gasUsed2 } = await this.mock.$getSupportedInterfaces
|
||||
.send(this.target, [DUMMY_ID, DUMMY_ID_2, DUMMY_ID_3, DUMMY_UNSUPPORTED_ID, DUMMY_UNSUPPORTED_ID_2])
|
||||
.then(tx => tx.wait());
|
||||
|
||||
expect(gasUsed2).to.be.lessThan(250_000n); // (2+5)*30k + 21k + some margin
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,145 @@
|
||||
const { expect } = require('chai');
|
||||
const { interfaceId } = require('../../helpers/methods');
|
||||
const { mapValues } = require('../../helpers/iterate');
|
||||
|
||||
const INVALID_ID = '0xffffffff';
|
||||
const SIGNATURES = {
|
||||
ERC165: ['supportsInterface(bytes4)'],
|
||||
ERC721: [
|
||||
'balanceOf(address)',
|
||||
'ownerOf(uint256)',
|
||||
'approve(address,uint256)',
|
||||
'getApproved(uint256)',
|
||||
'setApprovalForAll(address,bool)',
|
||||
'isApprovedForAll(address,address)',
|
||||
'transferFrom(address,address,uint256)',
|
||||
'safeTransferFrom(address,address,uint256)',
|
||||
'safeTransferFrom(address,address,uint256,bytes)',
|
||||
],
|
||||
ERC721Enumerable: ['totalSupply()', 'tokenOfOwnerByIndex(address,uint256)', 'tokenByIndex(uint256)'],
|
||||
ERC721Metadata: ['name()', 'symbol()', 'tokenURI(uint256)'],
|
||||
ERC1155: [
|
||||
'balanceOf(address,uint256)',
|
||||
'balanceOfBatch(address[],uint256[])',
|
||||
'setApprovalForAll(address,bool)',
|
||||
'isApprovedForAll(address,address)',
|
||||
'safeTransferFrom(address,address,uint256,uint256,bytes)',
|
||||
'safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)',
|
||||
],
|
||||
ERC1155MetadataURI: ['uri(uint256)'],
|
||||
ERC1155Receiver: [
|
||||
'onERC1155Received(address,address,uint256,uint256,bytes)',
|
||||
'onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)',
|
||||
],
|
||||
ERC1363: [
|
||||
'transferAndCall(address,uint256)',
|
||||
'transferAndCall(address,uint256,bytes)',
|
||||
'transferFromAndCall(address,address,uint256)',
|
||||
'transferFromAndCall(address,address,uint256,bytes)',
|
||||
'approveAndCall(address,uint256)',
|
||||
'approveAndCall(address,uint256,bytes)',
|
||||
],
|
||||
AccessControl: [
|
||||
'hasRole(bytes32,address)',
|
||||
'getRoleAdmin(bytes32)',
|
||||
'grantRole(bytes32,address)',
|
||||
'revokeRole(bytes32,address)',
|
||||
'renounceRole(bytes32,address)',
|
||||
],
|
||||
AccessControlEnumerable: ['getRoleMember(bytes32,uint256)', 'getRoleMemberCount(bytes32)'],
|
||||
AccessControlDefaultAdminRules: [
|
||||
'defaultAdminDelay()',
|
||||
'pendingDefaultAdminDelay()',
|
||||
'defaultAdmin()',
|
||||
'pendingDefaultAdmin()',
|
||||
'defaultAdminDelayIncreaseWait()',
|
||||
'changeDefaultAdminDelay(uint48)',
|
||||
'rollbackDefaultAdminDelay()',
|
||||
'beginDefaultAdminTransfer(address)',
|
||||
'acceptDefaultAdminTransfer()',
|
||||
'cancelDefaultAdminTransfer()',
|
||||
],
|
||||
Governor: [
|
||||
'name()',
|
||||
'version()',
|
||||
'COUNTING_MODE()',
|
||||
'hashProposal(address[],uint256[],bytes[],bytes32)',
|
||||
'state(uint256)',
|
||||
'proposalThreshold()',
|
||||
'proposalSnapshot(uint256)',
|
||||
'proposalDeadline(uint256)',
|
||||
'proposalProposer(uint256)',
|
||||
'proposalEta(uint256)',
|
||||
'proposalNeedsQueuing(uint256)',
|
||||
'votingDelay()',
|
||||
'votingPeriod()',
|
||||
'quorum(uint256)',
|
||||
'getVotes(address,uint256)',
|
||||
'getVotesWithParams(address,uint256,bytes)',
|
||||
'hasVoted(uint256,address)',
|
||||
'propose(address[],uint256[],bytes[],string)',
|
||||
'queue(address[],uint256[],bytes[],bytes32)',
|
||||
'execute(address[],uint256[],bytes[],bytes32)',
|
||||
'cancel(address[],uint256[],bytes[],bytes32)',
|
||||
'castVote(uint256,uint8)',
|
||||
'castVoteWithReason(uint256,uint8,string)',
|
||||
'castVoteWithReasonAndParams(uint256,uint8,string,bytes)',
|
||||
'castVoteBySig(uint256,uint8,address,bytes)',
|
||||
'castVoteWithReasonAndParamsBySig(uint256,uint8,address,string,bytes,bytes)',
|
||||
],
|
||||
ERC2981: ['royaltyInfo(uint256,uint256)'],
|
||||
};
|
||||
|
||||
const INTERFACE_IDS = mapValues(SIGNATURES, interfaceId);
|
||||
|
||||
function shouldSupportInterfaces(interfaces = []) {
|
||||
interfaces.unshift('ERC165');
|
||||
|
||||
describe('ERC165', function () {
|
||||
beforeEach(function () {
|
||||
this.contractUnderTest = this.mock || this.token;
|
||||
});
|
||||
|
||||
describe('when the interfaceId is supported', function () {
|
||||
it('uses less than 30k gas', async function () {
|
||||
for (const k of interfaces) {
|
||||
const interface = INTERFACE_IDS[k] ?? k;
|
||||
expect(await this.contractUnderTest.supportsInterface.estimateGas(interface)).to.lte(30_000n);
|
||||
}
|
||||
});
|
||||
|
||||
it('returns true', async function () {
|
||||
for (const k of interfaces) {
|
||||
const interfaceId = INTERFACE_IDS[k] ?? k;
|
||||
expect(await this.contractUnderTest.supportsInterface(interfaceId), `does not support ${k}`).to.be.true;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the interfaceId is not supported', function () {
|
||||
it('uses less than 30k', async function () {
|
||||
expect(await this.contractUnderTest.supportsInterface.estimateGas(INVALID_ID)).to.lte(30_000n);
|
||||
});
|
||||
|
||||
it('returns false', async function () {
|
||||
expect(await this.contractUnderTest.supportsInterface(INVALID_ID), `supports ${INVALID_ID}`).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
it('all interface functions are in ABI', async function () {
|
||||
for (const k of interfaces) {
|
||||
// skip interfaces for which we don't have a function list
|
||||
if (SIGNATURES[k] === undefined) continue;
|
||||
|
||||
// Check the presence of each function in the contract's interface
|
||||
for (const fnSig of SIGNATURES[k]) {
|
||||
expect(this.contractUnderTest.interface.hasFunction(fnSig), `did not find ${fnSig}`).to.be.true;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
shouldSupportInterfaces,
|
||||
};
|
||||
311
lib_openzeppelin_contracts/test/utils/math/Math.t.sol
Normal file
311
lib_openzeppelin_contracts/test/utils/math/Math.t.sol
Normal file
@@ -0,0 +1,311 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {Test, stdError} from "@forge-std/Test.sol";
|
||||
|
||||
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
|
||||
|
||||
contract MathTest is Test {
|
||||
function testSelect(bool f, uint256 a, uint256 b) public {
|
||||
assertEq(Math.ternary(f, a, b), f ? a : b);
|
||||
}
|
||||
|
||||
// MIN & MAX
|
||||
function testMinMax(uint256 a, uint256 b) public {
|
||||
assertEq(Math.min(a, b), a < b ? a : b);
|
||||
assertEq(Math.max(a, b), a > b ? a : b);
|
||||
}
|
||||
|
||||
// CEILDIV
|
||||
function testCeilDiv(uint256 a, uint256 b) public {
|
||||
vm.assume(b > 0);
|
||||
|
||||
uint256 result = Math.ceilDiv(a, b);
|
||||
|
||||
if (result == 0) {
|
||||
assertEq(a, 0);
|
||||
} else {
|
||||
uint256 expect = a / b;
|
||||
if (expect * b < a) {
|
||||
expect += 1;
|
||||
}
|
||||
assertEq(result, expect);
|
||||
}
|
||||
}
|
||||
|
||||
// SQRT
|
||||
function testSqrt(uint256 input, uint8 r) public {
|
||||
Math.Rounding rounding = _asRounding(r);
|
||||
|
||||
uint256 result = Math.sqrt(input, rounding);
|
||||
|
||||
// square of result is bigger than input
|
||||
if (_squareBigger(result, input)) {
|
||||
assertTrue(Math.unsignedRoundsUp(rounding));
|
||||
assertTrue(_squareSmaller(result - 1, input));
|
||||
}
|
||||
// square of result is smaller than input
|
||||
else if (_squareSmaller(result, input)) {
|
||||
assertFalse(Math.unsignedRoundsUp(rounding));
|
||||
assertTrue(_squareBigger(result + 1, input));
|
||||
}
|
||||
// input is perfect square
|
||||
else {
|
||||
assertEq(result * result, input);
|
||||
}
|
||||
}
|
||||
|
||||
function _squareBigger(uint256 value, uint256 ref) private pure returns (bool) {
|
||||
(bool noOverflow, uint256 square) = Math.tryMul(value, value);
|
||||
return !noOverflow || square > ref;
|
||||
}
|
||||
|
||||
function _squareSmaller(uint256 value, uint256 ref) private pure returns (bool) {
|
||||
return value * value < ref;
|
||||
}
|
||||
|
||||
// INV
|
||||
function testInvMod(uint256 value, uint256 p) public {
|
||||
_testInvMod(value, p, true);
|
||||
}
|
||||
|
||||
function testInvMod2(uint256 seed) public {
|
||||
uint256 p = 2; // prime
|
||||
_testInvMod(bound(seed, 1, p - 1), p, false);
|
||||
}
|
||||
|
||||
function testInvMod17(uint256 seed) public {
|
||||
uint256 p = 17; // prime
|
||||
_testInvMod(bound(seed, 1, p - 1), p, false);
|
||||
}
|
||||
|
||||
function testInvMod65537(uint256 seed) public {
|
||||
uint256 p = 65537; // prime
|
||||
_testInvMod(bound(seed, 1, p - 1), p, false);
|
||||
}
|
||||
|
||||
function testInvModP256(uint256 seed) public {
|
||||
uint256 p = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff; // prime
|
||||
_testInvMod(bound(seed, 1, p - 1), p, false);
|
||||
}
|
||||
|
||||
function _testInvMod(uint256 value, uint256 p, bool allowZero) private {
|
||||
uint256 inverse = Math.invMod(value, p);
|
||||
if (inverse != 0) {
|
||||
assertEq(mulmod(value, inverse, p), 1);
|
||||
assertLt(inverse, p);
|
||||
} else {
|
||||
assertTrue(allowZero);
|
||||
}
|
||||
}
|
||||
|
||||
// LOG2
|
||||
function testLog2(uint256 input, uint8 r) public {
|
||||
Math.Rounding rounding = _asRounding(r);
|
||||
|
||||
uint256 result = Math.log2(input, rounding);
|
||||
|
||||
if (input == 0) {
|
||||
assertEq(result, 0);
|
||||
} else if (_powerOf2Bigger(result, input)) {
|
||||
assertTrue(Math.unsignedRoundsUp(rounding));
|
||||
assertTrue(_powerOf2Smaller(result - 1, input));
|
||||
} else if (_powerOf2Smaller(result, input)) {
|
||||
assertFalse(Math.unsignedRoundsUp(rounding));
|
||||
assertTrue(_powerOf2Bigger(result + 1, input));
|
||||
} else {
|
||||
assertEq(2 ** result, input);
|
||||
}
|
||||
}
|
||||
|
||||
function _powerOf2Bigger(uint256 value, uint256 ref) private pure returns (bool) {
|
||||
return value >= 256 || 2 ** value > ref; // 2**256 overflows uint256
|
||||
}
|
||||
|
||||
function _powerOf2Smaller(uint256 value, uint256 ref) private pure returns (bool) {
|
||||
return 2 ** value < ref;
|
||||
}
|
||||
|
||||
// LOG10
|
||||
function testLog10(uint256 input, uint8 r) public {
|
||||
Math.Rounding rounding = _asRounding(r);
|
||||
|
||||
uint256 result = Math.log10(input, rounding);
|
||||
|
||||
if (input == 0) {
|
||||
assertEq(result, 0);
|
||||
} else if (_powerOf10Bigger(result, input)) {
|
||||
assertTrue(Math.unsignedRoundsUp(rounding));
|
||||
assertTrue(_powerOf10Smaller(result - 1, input));
|
||||
} else if (_powerOf10Smaller(result, input)) {
|
||||
assertFalse(Math.unsignedRoundsUp(rounding));
|
||||
assertTrue(_powerOf10Bigger(result + 1, input));
|
||||
} else {
|
||||
assertEq(10 ** result, input);
|
||||
}
|
||||
}
|
||||
|
||||
function _powerOf10Bigger(uint256 value, uint256 ref) private pure returns (bool) {
|
||||
return value >= 78 || 10 ** value > ref; // 10**78 overflows uint256
|
||||
}
|
||||
|
||||
function _powerOf10Smaller(uint256 value, uint256 ref) private pure returns (bool) {
|
||||
return 10 ** value < ref;
|
||||
}
|
||||
|
||||
// LOG256
|
||||
function testLog256(uint256 input, uint8 r) public {
|
||||
Math.Rounding rounding = _asRounding(r);
|
||||
|
||||
uint256 result = Math.log256(input, rounding);
|
||||
|
||||
if (input == 0) {
|
||||
assertEq(result, 0);
|
||||
} else if (_powerOf256Bigger(result, input)) {
|
||||
assertTrue(Math.unsignedRoundsUp(rounding));
|
||||
assertTrue(_powerOf256Smaller(result - 1, input));
|
||||
} else if (_powerOf256Smaller(result, input)) {
|
||||
assertFalse(Math.unsignedRoundsUp(rounding));
|
||||
assertTrue(_powerOf256Bigger(result + 1, input));
|
||||
} else {
|
||||
assertEq(256 ** result, input);
|
||||
}
|
||||
}
|
||||
|
||||
function _powerOf256Bigger(uint256 value, uint256 ref) private pure returns (bool) {
|
||||
return value >= 32 || 256 ** value > ref; // 256**32 overflows uint256
|
||||
}
|
||||
|
||||
function _powerOf256Smaller(uint256 value, uint256 ref) private pure returns (bool) {
|
||||
return 256 ** value < ref;
|
||||
}
|
||||
|
||||
// MULDIV
|
||||
function testMulDiv(uint256 x, uint256 y, uint256 d) public {
|
||||
// Full precision for x * y
|
||||
(uint256 xyHi, uint256 xyLo) = _mulHighLow(x, y);
|
||||
|
||||
// Assume result won't overflow (see {testMulDivDomain})
|
||||
// This also checks that `d` is positive
|
||||
vm.assume(xyHi < d);
|
||||
|
||||
// Perform muldiv
|
||||
uint256 q = Math.mulDiv(x, y, d);
|
||||
|
||||
// Full precision for q * d
|
||||
(uint256 qdHi, uint256 qdLo) = _mulHighLow(q, d);
|
||||
// Add remainder of x * y / d (computed as rem = (x * y % d))
|
||||
(uint256 qdRemLo, uint256 c) = _addCarry(qdLo, mulmod(x, y, d));
|
||||
uint256 qdRemHi = qdHi + c;
|
||||
|
||||
// Full precision check that x * y = q * d + rem
|
||||
assertEq(xyHi, qdRemHi);
|
||||
assertEq(xyLo, qdRemLo);
|
||||
}
|
||||
|
||||
function testMulDivDomain(uint256 x, uint256 y, uint256 d) public {
|
||||
(uint256 xyHi, ) = _mulHighLow(x, y);
|
||||
|
||||
// Violate {testMulDiv} assumption (covers d is 0 and result overflow)
|
||||
vm.assume(xyHi >= d);
|
||||
|
||||
// we are outside the scope of {testMulDiv}, we expect muldiv to revert
|
||||
vm.expectRevert(d == 0 ? stdError.divisionError : stdError.arithmeticError);
|
||||
Math.mulDiv(x, y, d);
|
||||
}
|
||||
|
||||
// MOD EXP
|
||||
function testModExp(uint256 b, uint256 e, uint256 m) public {
|
||||
if (m == 0) {
|
||||
vm.expectRevert(stdError.divisionError);
|
||||
}
|
||||
uint256 result = Math.modExp(b, e, m);
|
||||
assertLt(result, m);
|
||||
assertEq(result, _nativeModExp(b, e, m));
|
||||
}
|
||||
|
||||
function testTryModExp(uint256 b, uint256 e, uint256 m) public {
|
||||
(bool success, uint256 result) = Math.tryModExp(b, e, m);
|
||||
assertEq(success, m != 0);
|
||||
if (success) {
|
||||
assertLt(result, m);
|
||||
assertEq(result, _nativeModExp(b, e, m));
|
||||
} else {
|
||||
assertEq(result, 0);
|
||||
}
|
||||
}
|
||||
|
||||
function testModExpMemory(uint256 b, uint256 e, uint256 m) public {
|
||||
if (m == 0) {
|
||||
vm.expectRevert(stdError.divisionError);
|
||||
}
|
||||
bytes memory result = Math.modExp(abi.encodePacked(b), abi.encodePacked(e), abi.encodePacked(m));
|
||||
assertEq(result.length, 0x20);
|
||||
uint256 res = abi.decode(result, (uint256));
|
||||
assertLt(res, m);
|
||||
assertEq(res, _nativeModExp(b, e, m));
|
||||
}
|
||||
|
||||
function testTryModExpMemory(uint256 b, uint256 e, uint256 m) public {
|
||||
(bool success, bytes memory result) = Math.tryModExp(
|
||||
abi.encodePacked(b),
|
||||
abi.encodePacked(e),
|
||||
abi.encodePacked(m)
|
||||
);
|
||||
if (success) {
|
||||
assertEq(result.length, 0x20); // m is a uint256, so abi.encodePacked(m).length is 0x20
|
||||
uint256 res = abi.decode(result, (uint256));
|
||||
assertLt(res, m);
|
||||
assertEq(res, _nativeModExp(b, e, m));
|
||||
} else {
|
||||
assertEq(result.length, 0);
|
||||
}
|
||||
}
|
||||
|
||||
function _nativeModExp(uint256 b, uint256 e, uint256 m) private pure returns (uint256) {
|
||||
if (m == 1) return 0;
|
||||
uint256 r = 1;
|
||||
while (e > 0) {
|
||||
if (e % 2 > 0) {
|
||||
r = mulmod(r, b, m);
|
||||
}
|
||||
b = mulmod(b, b, m);
|
||||
e >>= 1;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
// Helpers
|
||||
function _asRounding(uint8 r) private pure returns (Math.Rounding) {
|
||||
vm.assume(r < uint8(type(Math.Rounding).max));
|
||||
return Math.Rounding(r);
|
||||
}
|
||||
|
||||
function _mulHighLow(uint256 x, uint256 y) private pure returns (uint256 high, uint256 low) {
|
||||
(uint256 x0, uint256 x1) = (x & type(uint128).max, x >> 128);
|
||||
(uint256 y0, uint256 y1) = (y & type(uint128).max, y >> 128);
|
||||
|
||||
// Karatsuba algorithm
|
||||
// https://en.wikipedia.org/wiki/Karatsuba_algorithm
|
||||
uint256 z2 = x1 * y1;
|
||||
uint256 z1a = x1 * y0;
|
||||
uint256 z1b = x0 * y1;
|
||||
uint256 z0 = x0 * y0;
|
||||
|
||||
uint256 carry = ((z1a & type(uint128).max) + (z1b & type(uint128).max) + (z0 >> 128)) >> 128;
|
||||
|
||||
high = z2 + (z1a >> 128) + (z1b >> 128) + carry;
|
||||
|
||||
unchecked {
|
||||
low = x * y;
|
||||
}
|
||||
}
|
||||
|
||||
function _addCarry(uint256 x, uint256 y) private pure returns (uint256 res, uint256 carry) {
|
||||
unchecked {
|
||||
res = x + y;
|
||||
}
|
||||
carry = res < x ? 1 : 0;
|
||||
}
|
||||
}
|
||||
562
lib_openzeppelin_contracts/test/utils/math/Math.test.js
Normal file
562
lib_openzeppelin_contracts/test/utils/math/Math.test.js
Normal file
@@ -0,0 +1,562 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
const { PANIC_CODES } = require('@nomicfoundation/hardhat-chai-matchers/panic');
|
||||
|
||||
const { Rounding } = require('../../helpers/enums');
|
||||
const { min, max, modExp } = require('../../helpers/math');
|
||||
const { generators } = require('../../helpers/random');
|
||||
const { product, range } = require('../../helpers/iterate');
|
||||
|
||||
const RoundingDown = [Rounding.Floor, Rounding.Trunc];
|
||||
const RoundingUp = [Rounding.Ceil, Rounding.Expand];
|
||||
|
||||
const bytes = (value, width = undefined) => ethers.Typed.bytes(ethers.toBeHex(value, width));
|
||||
const uint256 = value => ethers.Typed.uint256(value);
|
||||
bytes.zero = '0x';
|
||||
uint256.zero = 0n;
|
||||
|
||||
async function testCommutative(fn, lhs, rhs, expected, ...extra) {
|
||||
expect(await fn(lhs, rhs, ...extra)).to.deep.equal(expected);
|
||||
expect(await fn(rhs, lhs, ...extra)).to.deep.equal(expected);
|
||||
}
|
||||
|
||||
async function fixture() {
|
||||
const mock = await ethers.deployContract('$Math');
|
||||
|
||||
// disambiguation, we use the version with explicit rounding
|
||||
mock.$mulDiv = mock['$mulDiv(uint256,uint256,uint256,uint8)'];
|
||||
mock.$sqrt = mock['$sqrt(uint256,uint8)'];
|
||||
mock.$log2 = mock['$log2(uint256,uint8)'];
|
||||
mock.$log10 = mock['$log10(uint256,uint8)'];
|
||||
mock.$log256 = mock['$log256(uint256,uint8)'];
|
||||
|
||||
return { mock };
|
||||
}
|
||||
|
||||
describe('Math', function () {
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
describe('tryAdd', function () {
|
||||
it('adds correctly', async function () {
|
||||
const a = 5678n;
|
||||
const b = 1234n;
|
||||
await testCommutative(this.mock.$tryAdd, a, b, [true, a + b]);
|
||||
});
|
||||
|
||||
it('reverts on addition overflow', async function () {
|
||||
const a = ethers.MaxUint256;
|
||||
const b = 1n;
|
||||
await testCommutative(this.mock.$tryAdd, a, b, [false, 0n]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('trySub', function () {
|
||||
it('subtracts correctly', async function () {
|
||||
const a = 5678n;
|
||||
const b = 1234n;
|
||||
expect(await this.mock.$trySub(a, b)).to.deep.equal([true, a - b]);
|
||||
});
|
||||
|
||||
it('reverts if subtraction result would be negative', async function () {
|
||||
const a = 1234n;
|
||||
const b = 5678n;
|
||||
expect(await this.mock.$trySub(a, b)).to.deep.equal([false, 0n]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('tryMul', function () {
|
||||
it('multiplies correctly', async function () {
|
||||
const a = 1234n;
|
||||
const b = 5678n;
|
||||
await testCommutative(this.mock.$tryMul, a, b, [true, a * b]);
|
||||
});
|
||||
|
||||
it('multiplies by zero correctly', async function () {
|
||||
const a = 0n;
|
||||
const b = 5678n;
|
||||
await testCommutative(this.mock.$tryMul, a, b, [true, a * b]);
|
||||
});
|
||||
|
||||
it('reverts on multiplication overflow', async function () {
|
||||
const a = ethers.MaxUint256;
|
||||
const b = 2n;
|
||||
await testCommutative(this.mock.$tryMul, a, b, [false, 0n]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('tryDiv', function () {
|
||||
it('divides correctly', async function () {
|
||||
const a = 5678n;
|
||||
const b = 5678n;
|
||||
expect(await this.mock.$tryDiv(a, b)).to.deep.equal([true, a / b]);
|
||||
});
|
||||
|
||||
it('divides zero correctly', async function () {
|
||||
const a = 0n;
|
||||
const b = 5678n;
|
||||
expect(await this.mock.$tryDiv(a, b)).to.deep.equal([true, a / b]);
|
||||
});
|
||||
|
||||
it('returns complete number result on non-even division', async function () {
|
||||
const a = 7000n;
|
||||
const b = 5678n;
|
||||
expect(await this.mock.$tryDiv(a, b)).to.deep.equal([true, a / b]);
|
||||
});
|
||||
|
||||
it('reverts on division by zero', async function () {
|
||||
const a = 5678n;
|
||||
const b = 0n;
|
||||
expect(await this.mock.$tryDiv(a, b)).to.deep.equal([false, 0n]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('tryMod', function () {
|
||||
describe('modulos correctly', function () {
|
||||
it('when the dividend is smaller than the divisor', async function () {
|
||||
const a = 284n;
|
||||
const b = 5678n;
|
||||
expect(await this.mock.$tryMod(a, b)).to.deep.equal([true, a % b]);
|
||||
});
|
||||
|
||||
it('when the dividend is equal to the divisor', async function () {
|
||||
const a = 5678n;
|
||||
const b = 5678n;
|
||||
expect(await this.mock.$tryMod(a, b)).to.deep.equal([true, a % b]);
|
||||
});
|
||||
|
||||
it('when the dividend is larger than the divisor', async function () {
|
||||
const a = 7000n;
|
||||
const b = 5678n;
|
||||
expect(await this.mock.$tryMod(a, b)).to.deep.equal([true, a % b]);
|
||||
});
|
||||
|
||||
it('when the dividend is a multiple of the divisor', async function () {
|
||||
const a = 17034n; // 17034 == 5678 * 3
|
||||
const b = 5678n;
|
||||
expect(await this.mock.$tryMod(a, b)).to.deep.equal([true, a % b]);
|
||||
});
|
||||
});
|
||||
|
||||
it('reverts with a 0 divisor', async function () {
|
||||
const a = 5678n;
|
||||
const b = 0n;
|
||||
expect(await this.mock.$tryMod(a, b)).to.deep.equal([false, 0n]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('max', function () {
|
||||
it('is correctly detected in both position', async function () {
|
||||
await testCommutative(this.mock.$max, 1234n, 5678n, max(1234n, 5678n));
|
||||
});
|
||||
});
|
||||
|
||||
describe('min', function () {
|
||||
it('is correctly detected in both position', async function () {
|
||||
await testCommutative(this.mock.$min, 1234n, 5678n, min(1234n, 5678n));
|
||||
});
|
||||
});
|
||||
|
||||
describe('average', function () {
|
||||
it('is correctly calculated with two odd numbers', async function () {
|
||||
const a = 57417n;
|
||||
const b = 95431n;
|
||||
expect(await this.mock.$average(a, b)).to.equal((a + b) / 2n);
|
||||
});
|
||||
|
||||
it('is correctly calculated with two even numbers', async function () {
|
||||
const a = 42304n;
|
||||
const b = 84346n;
|
||||
expect(await this.mock.$average(a, b)).to.equal((a + b) / 2n);
|
||||
});
|
||||
|
||||
it('is correctly calculated with one even and one odd number', async function () {
|
||||
const a = 57417n;
|
||||
const b = 84346n;
|
||||
expect(await this.mock.$average(a, b)).to.equal((a + b) / 2n);
|
||||
});
|
||||
|
||||
it('is correctly calculated with two max uint256 numbers', async function () {
|
||||
const a = ethers.MaxUint256;
|
||||
expect(await this.mock.$average(a, a)).to.equal(a);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ceilDiv', function () {
|
||||
it('reverts on zero division', async function () {
|
||||
const a = 2n;
|
||||
const b = 0n;
|
||||
// It's unspecified because it's a low level 0 division error
|
||||
await expect(this.mock.$ceilDiv(a, b)).to.be.revertedWithPanic(PANIC_CODES.DIVISION_BY_ZERO);
|
||||
});
|
||||
|
||||
it('does not round up a zero result', async function () {
|
||||
const a = 0n;
|
||||
const b = 2n;
|
||||
const r = 0n;
|
||||
expect(await this.mock.$ceilDiv(a, b)).to.equal(r);
|
||||
});
|
||||
|
||||
it('does not round up on exact division', async function () {
|
||||
const a = 10n;
|
||||
const b = 5n;
|
||||
const r = 2n;
|
||||
expect(await this.mock.$ceilDiv(a, b)).to.equal(r);
|
||||
});
|
||||
|
||||
it('rounds up on division with remainders', async function () {
|
||||
const a = 42n;
|
||||
const b = 13n;
|
||||
const r = 4n;
|
||||
expect(await this.mock.$ceilDiv(a, b)).to.equal(r);
|
||||
});
|
||||
|
||||
it('does not overflow', async function () {
|
||||
const a = ethers.MaxUint256;
|
||||
const b = 2n;
|
||||
const r = 1n << 255n;
|
||||
expect(await this.mock.$ceilDiv(a, b)).to.equal(r);
|
||||
});
|
||||
|
||||
it('correctly computes max uint256 divided by 1', async function () {
|
||||
const a = ethers.MaxUint256;
|
||||
const b = 1n;
|
||||
const r = ethers.MaxUint256;
|
||||
expect(await this.mock.$ceilDiv(a, b)).to.equal(r);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mulDiv', function () {
|
||||
it('divide by 0', async function () {
|
||||
const a = 1n;
|
||||
const b = 1n;
|
||||
const c = 0n;
|
||||
await expect(this.mock.$mulDiv(a, b, c, Rounding.Floor)).to.be.revertedWithPanic(PANIC_CODES.DIVISION_BY_ZERO);
|
||||
});
|
||||
|
||||
it('reverts with result higher than 2 ^ 256', async function () {
|
||||
const a = 5n;
|
||||
const b = ethers.MaxUint256;
|
||||
const c = 2n;
|
||||
await expect(this.mock.$mulDiv(a, b, c, Rounding.Floor)).to.be.revertedWithPanic(
|
||||
PANIC_CODES.ARITHMETIC_UNDER_OR_OVERFLOW,
|
||||
);
|
||||
});
|
||||
|
||||
describe('does round down', function () {
|
||||
it('small values', async function () {
|
||||
for (const rounding of RoundingDown) {
|
||||
expect(await this.mock.$mulDiv(3n, 4n, 5n, rounding)).to.equal(2n);
|
||||
expect(await this.mock.$mulDiv(3n, 5n, 5n, rounding)).to.equal(3n);
|
||||
}
|
||||
});
|
||||
|
||||
it('large values', async function () {
|
||||
for (const rounding of RoundingDown) {
|
||||
expect(await this.mock.$mulDiv(42n, ethers.MaxUint256 - 1n, ethers.MaxUint256, rounding)).to.equal(41n);
|
||||
|
||||
expect(await this.mock.$mulDiv(17n, ethers.MaxUint256, ethers.MaxUint256, rounding)).to.equal(17n);
|
||||
|
||||
expect(
|
||||
await this.mock.$mulDiv(ethers.MaxUint256 - 1n, ethers.MaxUint256 - 1n, ethers.MaxUint256, rounding),
|
||||
).to.equal(ethers.MaxUint256 - 2n);
|
||||
|
||||
expect(
|
||||
await this.mock.$mulDiv(ethers.MaxUint256, ethers.MaxUint256 - 1n, ethers.MaxUint256, rounding),
|
||||
).to.equal(ethers.MaxUint256 - 1n);
|
||||
|
||||
expect(await this.mock.$mulDiv(ethers.MaxUint256, ethers.MaxUint256, ethers.MaxUint256, rounding)).to.equal(
|
||||
ethers.MaxUint256,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('does round up', function () {
|
||||
it('small values', async function () {
|
||||
for (const rounding of RoundingUp) {
|
||||
expect(await this.mock.$mulDiv(3n, 4n, 5n, rounding)).to.equal(3n);
|
||||
expect(await this.mock.$mulDiv(3n, 5n, 5n, rounding)).to.equal(3n);
|
||||
}
|
||||
});
|
||||
|
||||
it('large values', async function () {
|
||||
for (const rounding of RoundingUp) {
|
||||
expect(await this.mock.$mulDiv(42n, ethers.MaxUint256 - 1n, ethers.MaxUint256, rounding)).to.equal(42n);
|
||||
|
||||
expect(await this.mock.$mulDiv(17n, ethers.MaxUint256, ethers.MaxUint256, rounding)).to.equal(17n);
|
||||
|
||||
expect(
|
||||
await this.mock.$mulDiv(ethers.MaxUint256 - 1n, ethers.MaxUint256 - 1n, ethers.MaxUint256, rounding),
|
||||
).to.equal(ethers.MaxUint256 - 1n);
|
||||
|
||||
expect(
|
||||
await this.mock.$mulDiv(ethers.MaxUint256, ethers.MaxUint256 - 1n, ethers.MaxUint256, rounding),
|
||||
).to.equal(ethers.MaxUint256 - 1n);
|
||||
|
||||
expect(await this.mock.$mulDiv(ethers.MaxUint256, ethers.MaxUint256, ethers.MaxUint256, rounding)).to.equal(
|
||||
ethers.MaxUint256,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('invMod', function () {
|
||||
for (const factors of [
|
||||
[0n],
|
||||
[1n],
|
||||
[2n],
|
||||
[17n],
|
||||
[65537n],
|
||||
[0xffffffff00000001000000000000000000000000ffffffffffffffffffffffffn],
|
||||
[3n, 5n],
|
||||
[3n, 7n],
|
||||
[47n, 53n],
|
||||
]) {
|
||||
const p = factors.reduce((acc, f) => acc * f, 1n);
|
||||
|
||||
describe(`using p=${p} which is ${p > 1 && factors.length > 1 ? 'not ' : ''}a prime`, function () {
|
||||
it('trying to inverse 0 returns 0', async function () {
|
||||
expect(await this.mock.$invMod(0, p)).to.equal(0n);
|
||||
expect(await this.mock.$invMod(p, p)).to.equal(0n); // p is 0 mod p
|
||||
});
|
||||
|
||||
if (p != 0) {
|
||||
for (const value of Array.from({ length: 16 }, generators.uint256)) {
|
||||
const isInversible = factors.every(f => value % f);
|
||||
it(`trying to inverse ${value}`, async function () {
|
||||
const result = await this.mock.$invMod(value, p);
|
||||
if (isInversible) {
|
||||
expect((value * result) % p).to.equal(1n);
|
||||
} else {
|
||||
expect(result).to.equal(0n);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('modExp', function () {
|
||||
for (const [name, type] of Object.entries({ uint256, bytes })) {
|
||||
describe(`with ${name} inputs`, function () {
|
||||
it('is correctly calculating modulus', async function () {
|
||||
const b = 3n;
|
||||
const e = 200n;
|
||||
const m = 50n;
|
||||
|
||||
expect(await this.mock.$modExp(type(b), type(e), type(m))).to.equal(type(b ** e % m).value);
|
||||
});
|
||||
|
||||
it('is correctly reverting when modulus is zero', async function () {
|
||||
const b = 3n;
|
||||
const e = 200n;
|
||||
const m = 0n;
|
||||
|
||||
await expect(this.mock.$modExp(type(b), type(e), type(m))).to.be.revertedWithPanic(
|
||||
PANIC_CODES.DIVISION_BY_ZERO,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe('with large bytes inputs', function () {
|
||||
for (const [[b, log2b], [e, log2e], [m, log2m]] of product(
|
||||
range(320, 512, 64).map(e => [2n ** BigInt(e) + 1n, e]),
|
||||
range(320, 512, 64).map(e => [2n ** BigInt(e) + 1n, e]),
|
||||
range(320, 512, 64).map(e => [2n ** BigInt(e) + 1n, e]),
|
||||
)) {
|
||||
it(`calculates b ** e % m (b=2**${log2b}+1) (e=2**${log2e}+1) (m=2**${log2m}+1)`, async function () {
|
||||
const mLength = ethers.dataLength(ethers.toBeHex(m));
|
||||
|
||||
expect(await this.mock.$modExp(bytes(b), bytes(e), bytes(m))).to.equal(bytes(modExp(b, e, m), mLength).value);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('tryModExp', function () {
|
||||
for (const [name, type] of Object.entries({ uint256, bytes })) {
|
||||
describe(`with ${name} inputs`, function () {
|
||||
it('is correctly calculating modulus', async function () {
|
||||
const b = 3n;
|
||||
const e = 200n;
|
||||
const m = 50n;
|
||||
|
||||
expect(await this.mock.$tryModExp(type(b), type(e), type(m))).to.deep.equal([true, type(b ** e % m).value]);
|
||||
});
|
||||
|
||||
it('is correctly reverting when modulus is zero', async function () {
|
||||
const b = 3n;
|
||||
const e = 200n;
|
||||
const m = 0n;
|
||||
|
||||
expect(await this.mock.$tryModExp(type(b), type(e), type(m))).to.deep.equal([false, type.zero]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe('with large bytes inputs', function () {
|
||||
for (const [[b, log2b], [e, log2e], [m, log2m]] of product(
|
||||
range(320, 513, 64).map(e => [2n ** BigInt(e) + 1n, e]),
|
||||
range(320, 513, 64).map(e => [2n ** BigInt(e) + 1n, e]),
|
||||
range(320, 513, 64).map(e => [2n ** BigInt(e) + 1n, e]),
|
||||
)) {
|
||||
it(`calculates b ** e % m (b=2**${log2b}+1) (e=2**${log2e}+1) (m=2**${log2m}+1)`, async function () {
|
||||
const mLength = ethers.dataLength(ethers.toBeHex(m));
|
||||
|
||||
expect(await this.mock.$tryModExp(bytes(b), bytes(e), bytes(m))).to.deep.equal([
|
||||
true,
|
||||
bytes(modExp(b, e, m), mLength).value,
|
||||
]);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('sqrt', function () {
|
||||
it('rounds down', async function () {
|
||||
for (const rounding of RoundingDown) {
|
||||
expect(await this.mock.$sqrt(0n, rounding)).to.equal(0n);
|
||||
expect(await this.mock.$sqrt(1n, rounding)).to.equal(1n);
|
||||
expect(await this.mock.$sqrt(2n, rounding)).to.equal(1n);
|
||||
expect(await this.mock.$sqrt(3n, rounding)).to.equal(1n);
|
||||
expect(await this.mock.$sqrt(4n, rounding)).to.equal(2n);
|
||||
expect(await this.mock.$sqrt(144n, rounding)).to.equal(12n);
|
||||
expect(await this.mock.$sqrt(999999n, rounding)).to.equal(999n);
|
||||
expect(await this.mock.$sqrt(1000000n, rounding)).to.equal(1000n);
|
||||
expect(await this.mock.$sqrt(1000001n, rounding)).to.equal(1000n);
|
||||
expect(await this.mock.$sqrt(1002000n, rounding)).to.equal(1000n);
|
||||
expect(await this.mock.$sqrt(1002001n, rounding)).to.equal(1001n);
|
||||
expect(await this.mock.$sqrt(ethers.MaxUint256, rounding)).to.equal(340282366920938463463374607431768211455n);
|
||||
}
|
||||
});
|
||||
|
||||
it('rounds up', async function () {
|
||||
for (const rounding of RoundingUp) {
|
||||
expect(await this.mock.$sqrt(0n, rounding)).to.equal(0n);
|
||||
expect(await this.mock.$sqrt(1n, rounding)).to.equal(1n);
|
||||
expect(await this.mock.$sqrt(2n, rounding)).to.equal(2n);
|
||||
expect(await this.mock.$sqrt(3n, rounding)).to.equal(2n);
|
||||
expect(await this.mock.$sqrt(4n, rounding)).to.equal(2n);
|
||||
expect(await this.mock.$sqrt(144n, rounding)).to.equal(12n);
|
||||
expect(await this.mock.$sqrt(999999n, rounding)).to.equal(1000n);
|
||||
expect(await this.mock.$sqrt(1000000n, rounding)).to.equal(1000n);
|
||||
expect(await this.mock.$sqrt(1000001n, rounding)).to.equal(1001n);
|
||||
expect(await this.mock.$sqrt(1002000n, rounding)).to.equal(1001n);
|
||||
expect(await this.mock.$sqrt(1002001n, rounding)).to.equal(1001n);
|
||||
expect(await this.mock.$sqrt(ethers.MaxUint256, rounding)).to.equal(340282366920938463463374607431768211456n);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('log', function () {
|
||||
describe('log2', function () {
|
||||
it('rounds down', async function () {
|
||||
for (const rounding of RoundingDown) {
|
||||
expect(await this.mock.$log2(0n, rounding)).to.equal(0n);
|
||||
expect(await this.mock.$log2(1n, rounding)).to.equal(0n);
|
||||
expect(await this.mock.$log2(2n, rounding)).to.equal(1n);
|
||||
expect(await this.mock.$log2(3n, rounding)).to.equal(1n);
|
||||
expect(await this.mock.$log2(4n, rounding)).to.equal(2n);
|
||||
expect(await this.mock.$log2(5n, rounding)).to.equal(2n);
|
||||
expect(await this.mock.$log2(6n, rounding)).to.equal(2n);
|
||||
expect(await this.mock.$log2(7n, rounding)).to.equal(2n);
|
||||
expect(await this.mock.$log2(8n, rounding)).to.equal(3n);
|
||||
expect(await this.mock.$log2(9n, rounding)).to.equal(3n);
|
||||
expect(await this.mock.$log2(ethers.MaxUint256, rounding)).to.equal(255n);
|
||||
}
|
||||
});
|
||||
|
||||
it('rounds up', async function () {
|
||||
for (const rounding of RoundingUp) {
|
||||
expect(await this.mock.$log2(0n, rounding)).to.equal(0n);
|
||||
expect(await this.mock.$log2(1n, rounding)).to.equal(0n);
|
||||
expect(await this.mock.$log2(2n, rounding)).to.equal(1n);
|
||||
expect(await this.mock.$log2(3n, rounding)).to.equal(2n);
|
||||
expect(await this.mock.$log2(4n, rounding)).to.equal(2n);
|
||||
expect(await this.mock.$log2(5n, rounding)).to.equal(3n);
|
||||
expect(await this.mock.$log2(6n, rounding)).to.equal(3n);
|
||||
expect(await this.mock.$log2(7n, rounding)).to.equal(3n);
|
||||
expect(await this.mock.$log2(8n, rounding)).to.equal(3n);
|
||||
expect(await this.mock.$log2(9n, rounding)).to.equal(4n);
|
||||
expect(await this.mock.$log2(ethers.MaxUint256, rounding)).to.equal(256n);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('log10', function () {
|
||||
it('rounds down', async function () {
|
||||
for (const rounding of RoundingDown) {
|
||||
expect(await this.mock.$log10(0n, rounding)).to.equal(0n);
|
||||
expect(await this.mock.$log10(1n, rounding)).to.equal(0n);
|
||||
expect(await this.mock.$log10(2n, rounding)).to.equal(0n);
|
||||
expect(await this.mock.$log10(9n, rounding)).to.equal(0n);
|
||||
expect(await this.mock.$log10(10n, rounding)).to.equal(1n);
|
||||
expect(await this.mock.$log10(11n, rounding)).to.equal(1n);
|
||||
expect(await this.mock.$log10(99n, rounding)).to.equal(1n);
|
||||
expect(await this.mock.$log10(100n, rounding)).to.equal(2n);
|
||||
expect(await this.mock.$log10(101n, rounding)).to.equal(2n);
|
||||
expect(await this.mock.$log10(999n, rounding)).to.equal(2n);
|
||||
expect(await this.mock.$log10(1000n, rounding)).to.equal(3n);
|
||||
expect(await this.mock.$log10(1001n, rounding)).to.equal(3n);
|
||||
expect(await this.mock.$log10(ethers.MaxUint256, rounding)).to.equal(77n);
|
||||
}
|
||||
});
|
||||
|
||||
it('rounds up', async function () {
|
||||
for (const rounding of RoundingUp) {
|
||||
expect(await this.mock.$log10(0n, rounding)).to.equal(0n);
|
||||
expect(await this.mock.$log10(1n, rounding)).to.equal(0n);
|
||||
expect(await this.mock.$log10(2n, rounding)).to.equal(1n);
|
||||
expect(await this.mock.$log10(9n, rounding)).to.equal(1n);
|
||||
expect(await this.mock.$log10(10n, rounding)).to.equal(1n);
|
||||
expect(await this.mock.$log10(11n, rounding)).to.equal(2n);
|
||||
expect(await this.mock.$log10(99n, rounding)).to.equal(2n);
|
||||
expect(await this.mock.$log10(100n, rounding)).to.equal(2n);
|
||||
expect(await this.mock.$log10(101n, rounding)).to.equal(3n);
|
||||
expect(await this.mock.$log10(999n, rounding)).to.equal(3n);
|
||||
expect(await this.mock.$log10(1000n, rounding)).to.equal(3n);
|
||||
expect(await this.mock.$log10(1001n, rounding)).to.equal(4n);
|
||||
expect(await this.mock.$log10(ethers.MaxUint256, rounding)).to.equal(78n);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('log256', function () {
|
||||
it('rounds down', async function () {
|
||||
for (const rounding of RoundingDown) {
|
||||
expect(await this.mock.$log256(0n, rounding)).to.equal(0n);
|
||||
expect(await this.mock.$log256(1n, rounding)).to.equal(0n);
|
||||
expect(await this.mock.$log256(2n, rounding)).to.equal(0n);
|
||||
expect(await this.mock.$log256(255n, rounding)).to.equal(0n);
|
||||
expect(await this.mock.$log256(256n, rounding)).to.equal(1n);
|
||||
expect(await this.mock.$log256(257n, rounding)).to.equal(1n);
|
||||
expect(await this.mock.$log256(65535n, rounding)).to.equal(1n);
|
||||
expect(await this.mock.$log256(65536n, rounding)).to.equal(2n);
|
||||
expect(await this.mock.$log256(65537n, rounding)).to.equal(2n);
|
||||
expect(await this.mock.$log256(ethers.MaxUint256, rounding)).to.equal(31n);
|
||||
}
|
||||
});
|
||||
|
||||
it('rounds up', async function () {
|
||||
for (const rounding of RoundingUp) {
|
||||
expect(await this.mock.$log256(0n, rounding)).to.equal(0n);
|
||||
expect(await this.mock.$log256(1n, rounding)).to.equal(0n);
|
||||
expect(await this.mock.$log256(2n, rounding)).to.equal(1n);
|
||||
expect(await this.mock.$log256(255n, rounding)).to.equal(1n);
|
||||
expect(await this.mock.$log256(256n, rounding)).to.equal(1n);
|
||||
expect(await this.mock.$log256(257n, rounding)).to.equal(2n);
|
||||
expect(await this.mock.$log256(65535n, rounding)).to.equal(2n);
|
||||
expect(await this.mock.$log256(65536n, rounding)).to.equal(2n);
|
||||
expect(await this.mock.$log256(65537n, rounding)).to.equal(3n);
|
||||
expect(await this.mock.$log256(ethers.MaxUint256, rounding)).to.equal(32n);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
159
lib_openzeppelin_contracts/test/utils/math/SafeCast.test.js
Normal file
159
lib_openzeppelin_contracts/test/utils/math/SafeCast.test.js
Normal file
@@ -0,0 +1,159 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
const { range } = require('../../helpers/iterate');
|
||||
|
||||
async function fixture() {
|
||||
const mock = await ethers.deployContract('$SafeCast');
|
||||
return { mock };
|
||||
}
|
||||
|
||||
describe('SafeCast', function () {
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
for (const bits of range(8, 256, 8).map(ethers.toBigInt)) {
|
||||
const maxValue = 2n ** bits - 1n;
|
||||
|
||||
describe(`toUint${bits}`, () => {
|
||||
it('downcasts 0', async function () {
|
||||
expect(await this.mock[`$toUint${bits}`](0n)).is.equal(0n);
|
||||
});
|
||||
|
||||
it('downcasts 1', async function () {
|
||||
expect(await this.mock[`$toUint${bits}`](1n)).is.equal(1n);
|
||||
});
|
||||
|
||||
it(`downcasts 2^${bits} - 1 (${maxValue})`, async function () {
|
||||
expect(await this.mock[`$toUint${bits}`](maxValue)).is.equal(maxValue);
|
||||
});
|
||||
|
||||
it(`reverts when downcasting 2^${bits} (${maxValue + 1n})`, async function () {
|
||||
await expect(this.mock[`$toUint${bits}`](maxValue + 1n))
|
||||
.to.be.revertedWithCustomError(this.mock, 'SafeCastOverflowedUintDowncast')
|
||||
.withArgs(bits, maxValue + 1n);
|
||||
});
|
||||
|
||||
it(`reverts when downcasting 2^${bits} + 1 (${maxValue + 2n})`, async function () {
|
||||
await expect(this.mock[`$toUint${bits}`](maxValue + 2n))
|
||||
.to.be.revertedWithCustomError(this.mock, 'SafeCastOverflowedUintDowncast')
|
||||
.withArgs(bits, maxValue + 2n);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe('toUint256', () => {
|
||||
it('casts 0', async function () {
|
||||
expect(await this.mock.$toUint256(0n)).is.equal(0n);
|
||||
});
|
||||
|
||||
it('casts 1', async function () {
|
||||
expect(await this.mock.$toUint256(1n)).is.equal(1n);
|
||||
});
|
||||
|
||||
it(`casts INT256_MAX (${ethers.MaxInt256})`, async function () {
|
||||
expect(await this.mock.$toUint256(ethers.MaxInt256)).is.equal(ethers.MaxInt256);
|
||||
});
|
||||
|
||||
it('reverts when casting -1', async function () {
|
||||
await expect(this.mock.$toUint256(-1n))
|
||||
.to.be.revertedWithCustomError(this.mock, 'SafeCastOverflowedIntToUint')
|
||||
.withArgs(-1n);
|
||||
});
|
||||
|
||||
it(`reverts when casting INT256_MIN (${ethers.MinInt256})`, async function () {
|
||||
await expect(this.mock.$toUint256(ethers.MinInt256))
|
||||
.to.be.revertedWithCustomError(this.mock, 'SafeCastOverflowedIntToUint')
|
||||
.withArgs(ethers.MinInt256);
|
||||
});
|
||||
});
|
||||
|
||||
for (const bits of range(8, 256, 8).map(ethers.toBigInt)) {
|
||||
const minValue = -(2n ** (bits - 1n));
|
||||
const maxValue = 2n ** (bits - 1n) - 1n;
|
||||
|
||||
describe(`toInt${bits}`, () => {
|
||||
it('downcasts 0', async function () {
|
||||
expect(await this.mock[`$toInt${bits}`](0n)).is.equal(0n);
|
||||
});
|
||||
|
||||
it('downcasts 1', async function () {
|
||||
expect(await this.mock[`$toInt${bits}`](1n)).is.equal(1n);
|
||||
});
|
||||
|
||||
it('downcasts -1', async function () {
|
||||
expect(await this.mock[`$toInt${bits}`](-1n)).is.equal(-1n);
|
||||
});
|
||||
|
||||
it(`downcasts -2^${bits - 1n} (${minValue})`, async function () {
|
||||
expect(await this.mock[`$toInt${bits}`](minValue)).is.equal(minValue);
|
||||
});
|
||||
|
||||
it(`downcasts 2^${bits - 1n} - 1 (${maxValue})`, async function () {
|
||||
expect(await this.mock[`$toInt${bits}`](maxValue)).is.equal(maxValue);
|
||||
});
|
||||
|
||||
it(`reverts when downcasting -2^${bits - 1n} - 1 (${minValue - 1n})`, async function () {
|
||||
await expect(this.mock[`$toInt${bits}`](minValue - 1n))
|
||||
.to.be.revertedWithCustomError(this.mock, 'SafeCastOverflowedIntDowncast')
|
||||
.withArgs(bits, minValue - 1n);
|
||||
});
|
||||
|
||||
it(`reverts when downcasting -2^${bits - 1n} - 2 (${minValue - 2n})`, async function () {
|
||||
await expect(this.mock[`$toInt${bits}`](minValue - 2n))
|
||||
.to.be.revertedWithCustomError(this.mock, 'SafeCastOverflowedIntDowncast')
|
||||
.withArgs(bits, minValue - 2n);
|
||||
});
|
||||
|
||||
it(`reverts when downcasting 2^${bits - 1n} (${maxValue + 1n})`, async function () {
|
||||
await expect(this.mock[`$toInt${bits}`](maxValue + 1n))
|
||||
.to.be.revertedWithCustomError(this.mock, 'SafeCastOverflowedIntDowncast')
|
||||
.withArgs(bits, maxValue + 1n);
|
||||
});
|
||||
|
||||
it(`reverts when downcasting 2^${bits - 1n} + 1 (${maxValue + 2n})`, async function () {
|
||||
await expect(this.mock[`$toInt${bits}`](maxValue + 2n))
|
||||
.to.be.revertedWithCustomError(this.mock, 'SafeCastOverflowedIntDowncast')
|
||||
.withArgs(bits, maxValue + 2n);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe('toInt256', () => {
|
||||
it('casts 0', async function () {
|
||||
expect(await this.mock.$toInt256(0)).is.equal(0n);
|
||||
});
|
||||
|
||||
it('casts 1', async function () {
|
||||
expect(await this.mock.$toInt256(1)).is.equal(1n);
|
||||
});
|
||||
|
||||
it(`casts INT256_MAX (${ethers.MaxInt256})`, async function () {
|
||||
expect(await this.mock.$toInt256(ethers.MaxInt256)).is.equal(ethers.MaxInt256);
|
||||
});
|
||||
|
||||
it(`reverts when casting INT256_MAX + 1 (${ethers.MaxInt256 + 1n})`, async function () {
|
||||
await expect(this.mock.$toInt256(ethers.MaxInt256 + 1n))
|
||||
.to.be.revertedWithCustomError(this.mock, 'SafeCastOverflowedUintToInt')
|
||||
.withArgs(ethers.MaxInt256 + 1n);
|
||||
});
|
||||
|
||||
it(`reverts when casting UINT256_MAX (${ethers.MaxUint256})`, async function () {
|
||||
await expect(this.mock.$toInt256(ethers.MaxUint256))
|
||||
.to.be.revertedWithCustomError(this.mock, 'SafeCastOverflowedUintToInt')
|
||||
.withArgs(ethers.MaxUint256);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toUint (bool)', function () {
|
||||
it('toUint(false) should be 0', async function () {
|
||||
expect(await this.mock.$toUint(false)).to.equal(0n);
|
||||
});
|
||||
|
||||
it('toUint(true) should be 1', async function () {
|
||||
expect(await this.mock.$toUint(true)).to.equal(1n);
|
||||
});
|
||||
});
|
||||
});
|
||||
80
lib_openzeppelin_contracts/test/utils/math/SignedMath.t.sol
Normal file
80
lib_openzeppelin_contracts/test/utils/math/SignedMath.t.sol
Normal file
@@ -0,0 +1,80 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {Test} from "@forge-std/Test.sol";
|
||||
|
||||
import {Math} from "../../../contracts/utils/math/Math.sol";
|
||||
import {SignedMath} from "../../../contracts/utils/math/SignedMath.sol";
|
||||
|
||||
contract SignedMathTest is Test {
|
||||
function testSelect(bool f, int256 a, int256 b) public {
|
||||
assertEq(SignedMath.ternary(f, a, b), f ? a : b);
|
||||
}
|
||||
|
||||
// MIN & MAX
|
||||
function testMinMax(int256 a, int256 b) public {
|
||||
assertEq(SignedMath.min(a, b), a < b ? a : b);
|
||||
assertEq(SignedMath.max(a, b), a > b ? a : b);
|
||||
}
|
||||
|
||||
// MIN
|
||||
function testMin(int256 a, int256 b) public {
|
||||
int256 result = SignedMath.min(a, b);
|
||||
|
||||
assertLe(result, a);
|
||||
assertLe(result, b);
|
||||
assertTrue(result == a || result == b);
|
||||
}
|
||||
|
||||
// MAX
|
||||
function testMax(int256 a, int256 b) public {
|
||||
int256 result = SignedMath.max(a, b);
|
||||
|
||||
assertGe(result, a);
|
||||
assertGe(result, b);
|
||||
assertTrue(result == a || result == b);
|
||||
}
|
||||
|
||||
// AVERAGE
|
||||
// 1. simple test, not full int256 range
|
||||
function testAverage1(int256 a, int256 b) public {
|
||||
a = bound(a, type(int256).min / 2, type(int256).max / 2);
|
||||
b = bound(b, type(int256).min / 2, type(int256).max / 2);
|
||||
|
||||
int256 result = SignedMath.average(a, b);
|
||||
|
||||
assertEq(result, (a + b) / 2);
|
||||
}
|
||||
|
||||
// 2. more complex test, full int256 range
|
||||
function testAverage2(int256 a, int256 b) public {
|
||||
(int256 result, int256 min, int256 max) = (
|
||||
SignedMath.average(a, b),
|
||||
SignedMath.min(a, b),
|
||||
SignedMath.max(a, b)
|
||||
);
|
||||
|
||||
// average must be between `a` and `b`
|
||||
assertGe(result, min);
|
||||
assertLe(result, max);
|
||||
|
||||
unchecked {
|
||||
// must be unchecked in order to support `a = type(int256).min, b = type(int256).max`
|
||||
uint256 deltaLower = uint256(result - min);
|
||||
uint256 deltaUpper = uint256(max - result);
|
||||
uint256 remainder = uint256((a & 1) ^ (b & 1));
|
||||
assertEq(remainder, Math.max(deltaLower, deltaUpper) - Math.min(deltaLower, deltaUpper));
|
||||
}
|
||||
}
|
||||
|
||||
// ABS
|
||||
function testAbs(int256 a) public {
|
||||
uint256 result = SignedMath.abs(a);
|
||||
|
||||
unchecked {
|
||||
// must be unchecked in order to support `n = type(int256).min`
|
||||
assertEq(result, a < 0 ? uint256(-a) : uint256(a));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
const { min, max } = require('../../helpers/math');
|
||||
|
||||
async function testCommutative(fn, lhs, rhs, expected, ...extra) {
|
||||
expect(await fn(lhs, rhs, ...extra)).to.deep.equal(expected);
|
||||
expect(await fn(rhs, lhs, ...extra)).to.deep.equal(expected);
|
||||
}
|
||||
|
||||
async function fixture() {
|
||||
const mock = await ethers.deployContract('$SignedMath');
|
||||
return { mock };
|
||||
}
|
||||
|
||||
describe('SignedMath', function () {
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
describe('max', function () {
|
||||
it('is correctly detected in both position', async function () {
|
||||
await testCommutative(this.mock.$max, -1234n, 5678n, max(-1234n, 5678n));
|
||||
});
|
||||
});
|
||||
|
||||
describe('min', function () {
|
||||
it('is correctly detected in both position', async function () {
|
||||
await testCommutative(this.mock.$min, -1234n, 5678n, min(-1234n, 5678n));
|
||||
});
|
||||
});
|
||||
|
||||
describe('average', function () {
|
||||
it('is correctly calculated with various input', async function () {
|
||||
for (const x of [ethers.MinInt256, -57417n, -42304n, -4n, -3n, 0n, 3n, 4n, 42304n, 57417n, ethers.MaxInt256]) {
|
||||
for (const y of [ethers.MinInt256, -57417n, -42304n, -5n, -2n, 0n, 2n, 5n, 42304n, 57417n, ethers.MaxInt256]) {
|
||||
expect(await this.mock.$average(x, y)).to.equal((x + y) / 2n);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('abs', function () {
|
||||
const abs = x => (x < 0n ? -x : x);
|
||||
|
||||
for (const n of [ethers.MinInt256, ethers.MinInt256 + 1n, -1n, 0n, 1n, ethers.MaxInt256 - 1n, ethers.MaxInt256]) {
|
||||
it(`correctly computes the absolute value of ${n}`, async function () {
|
||||
expect(await this.mock.$abs(n)).to.equal(abs(n));
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
149
lib_openzeppelin_contracts/test/utils/structs/BitMap.test.js
Normal file
149
lib_openzeppelin_contracts/test/utils/structs/BitMap.test.js
Normal file
@@ -0,0 +1,149 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
async function fixture() {
|
||||
const bitmap = await ethers.deployContract('$BitMaps');
|
||||
return { bitmap };
|
||||
}
|
||||
|
||||
describe('BitMap', function () {
|
||||
const keyA = 7891n;
|
||||
const keyB = 451n;
|
||||
const keyC = 9592328n;
|
||||
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
it('starts empty', async function () {
|
||||
expect(await this.bitmap.$get(0, keyA)).to.be.false;
|
||||
expect(await this.bitmap.$get(0, keyB)).to.be.false;
|
||||
expect(await this.bitmap.$get(0, keyC)).to.be.false;
|
||||
});
|
||||
|
||||
describe('setTo', function () {
|
||||
it('set a key to true', async function () {
|
||||
await this.bitmap.$setTo(0, keyA, true);
|
||||
expect(await this.bitmap.$get(0, keyA)).to.be.true;
|
||||
expect(await this.bitmap.$get(0, keyB)).to.be.false;
|
||||
expect(await this.bitmap.$get(0, keyC)).to.be.false;
|
||||
});
|
||||
|
||||
it('set a key to false', async function () {
|
||||
await this.bitmap.$setTo(0, keyA, true);
|
||||
await this.bitmap.$setTo(0, keyA, false);
|
||||
expect(await this.bitmap.$get(0, keyA)).to.be.false;
|
||||
expect(await this.bitmap.$get(0, keyB)).to.be.false;
|
||||
expect(await this.bitmap.$get(0, keyC)).to.be.false;
|
||||
});
|
||||
|
||||
it('set several consecutive keys', async function () {
|
||||
await this.bitmap.$setTo(0, keyA + 0n, true);
|
||||
await this.bitmap.$setTo(0, keyA + 1n, true);
|
||||
await this.bitmap.$setTo(0, keyA + 2n, true);
|
||||
await this.bitmap.$setTo(0, keyA + 3n, true);
|
||||
await this.bitmap.$setTo(0, keyA + 4n, true);
|
||||
await this.bitmap.$setTo(0, keyA + 2n, false);
|
||||
await this.bitmap.$setTo(0, keyA + 4n, false);
|
||||
expect(await this.bitmap.$get(0, keyA + 0n)).to.be.true;
|
||||
expect(await this.bitmap.$get(0, keyA + 1n)).to.be.true;
|
||||
expect(await this.bitmap.$get(0, keyA + 2n)).to.be.false;
|
||||
expect(await this.bitmap.$get(0, keyA + 3n)).to.be.true;
|
||||
expect(await this.bitmap.$get(0, keyA + 4n)).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe('set', function () {
|
||||
it('adds a key', async function () {
|
||||
await this.bitmap.$set(0, keyA);
|
||||
expect(await this.bitmap.$get(0, keyA)).to.be.true;
|
||||
expect(await this.bitmap.$get(0, keyB)).to.be.false;
|
||||
expect(await this.bitmap.$get(0, keyC)).to.be.false;
|
||||
});
|
||||
|
||||
it('adds several keys', async function () {
|
||||
await this.bitmap.$set(0, keyA);
|
||||
await this.bitmap.$set(0, keyB);
|
||||
expect(await this.bitmap.$get(0, keyA)).to.be.true;
|
||||
expect(await this.bitmap.$get(0, keyB)).to.be.true;
|
||||
expect(await this.bitmap.$get(0, keyC)).to.be.false;
|
||||
});
|
||||
|
||||
it('adds several consecutive keys', async function () {
|
||||
await this.bitmap.$set(0, keyA + 0n);
|
||||
await this.bitmap.$set(0, keyA + 1n);
|
||||
await this.bitmap.$set(0, keyA + 3n);
|
||||
expect(await this.bitmap.$get(0, keyA + 0n)).to.be.true;
|
||||
expect(await this.bitmap.$get(0, keyA + 1n)).to.be.true;
|
||||
expect(await this.bitmap.$get(0, keyA + 2n)).to.be.false;
|
||||
expect(await this.bitmap.$get(0, keyA + 3n)).to.be.true;
|
||||
expect(await this.bitmap.$get(0, keyA + 4n)).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe('unset', function () {
|
||||
it('removes added keys', async function () {
|
||||
await this.bitmap.$set(0, keyA);
|
||||
await this.bitmap.$set(0, keyB);
|
||||
await this.bitmap.$unset(0, keyA);
|
||||
expect(await this.bitmap.$get(0, keyA)).to.be.false;
|
||||
expect(await this.bitmap.$get(0, keyB)).to.be.true;
|
||||
expect(await this.bitmap.$get(0, keyC)).to.be.false;
|
||||
});
|
||||
|
||||
it('removes consecutive added keys', async function () {
|
||||
await this.bitmap.$set(0, keyA + 0n);
|
||||
await this.bitmap.$set(0, keyA + 1n);
|
||||
await this.bitmap.$set(0, keyA + 3n);
|
||||
await this.bitmap.$unset(0, keyA + 1n);
|
||||
expect(await this.bitmap.$get(0, keyA + 0n)).to.be.true;
|
||||
expect(await this.bitmap.$get(0, keyA + 1n)).to.be.false;
|
||||
expect(await this.bitmap.$get(0, keyA + 2n)).to.be.false;
|
||||
expect(await this.bitmap.$get(0, keyA + 3n)).to.be.true;
|
||||
expect(await this.bitmap.$get(0, keyA + 4n)).to.be.false;
|
||||
});
|
||||
|
||||
it('adds and removes multiple keys', async function () {
|
||||
// []
|
||||
|
||||
await this.bitmap.$set(0, keyA);
|
||||
await this.bitmap.$set(0, keyC);
|
||||
|
||||
// [A, C]
|
||||
|
||||
await this.bitmap.$unset(0, keyA);
|
||||
await this.bitmap.$unset(0, keyB);
|
||||
|
||||
// [C]
|
||||
|
||||
await this.bitmap.$set(0, keyB);
|
||||
|
||||
// [C, B]
|
||||
|
||||
await this.bitmap.$set(0, keyA);
|
||||
await this.bitmap.$unset(0, keyC);
|
||||
|
||||
// [A, B]
|
||||
|
||||
await this.bitmap.$set(0, keyA);
|
||||
await this.bitmap.$set(0, keyB);
|
||||
|
||||
// [A, B]
|
||||
|
||||
await this.bitmap.$set(0, keyC);
|
||||
await this.bitmap.$unset(0, keyA);
|
||||
|
||||
// [B, C]
|
||||
|
||||
await this.bitmap.$set(0, keyA);
|
||||
await this.bitmap.$unset(0, keyB);
|
||||
|
||||
// [A, C]
|
||||
|
||||
expect(await this.bitmap.$get(0, keyA)).to.be.true;
|
||||
expect(await this.bitmap.$get(0, keyB)).to.be.false;
|
||||
expect(await this.bitmap.$get(0, keyC)).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
332
lib_openzeppelin_contracts/test/utils/structs/Checkpoints.t.sol
Normal file
332
lib_openzeppelin_contracts/test/utils/structs/Checkpoints.t.sol
Normal file
@@ -0,0 +1,332 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// This file was procedurally generated from scripts/generate/templates/Checkpoints.t.js.
|
||||
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {Test} from "@forge-std/Test.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;
|
||||
|
||||
// Maximum gap between keys used during the fuzzing tests: the `_prepareKeys` function with make sure that
|
||||
// key#n+1 is in the [key#n, key#n + _KEY_MAX_GAP] range.
|
||||
uint8 internal constant _KEY_MAX_GAP = 64;
|
||||
|
||||
Checkpoints.Trace224 internal _ckpts;
|
||||
|
||||
// helpers
|
||||
function _boundUint32(uint32 x, uint32 min, uint32 max) internal pure returns (uint32) {
|
||||
return SafeCast.toUint32(bound(uint256(x), uint256(min), uint256(max)));
|
||||
}
|
||||
|
||||
function _prepareKeys(uint32[] memory keys, uint32 maxSpread) internal pure {
|
||||
uint32 lastKey = 0;
|
||||
for (uint256 i = 0; i < keys.length; ++i) {
|
||||
uint32 key = _boundUint32(keys[i], lastKey, lastKey + maxSpread);
|
||||
keys[i] = key;
|
||||
lastKey = key;
|
||||
}
|
||||
}
|
||||
|
||||
function _assertLatestCheckpoint(bool exist, uint32 key, uint224 value) internal {
|
||||
(bool _exist, uint32 _key, uint224 _value) = _ckpts.latestCheckpoint();
|
||||
assertEq(_exist, exist);
|
||||
assertEq(_key, key);
|
||||
assertEq(_value, value);
|
||||
}
|
||||
|
||||
// tests
|
||||
function testPush(uint32[] memory keys, uint224[] memory values, uint32 pastKey) public {
|
||||
vm.assume(values.length > 0 && values.length <= keys.length);
|
||||
_prepareKeys(keys, _KEY_MAX_GAP);
|
||||
|
||||
// initial state
|
||||
assertEq(_ckpts.length(), 0);
|
||||
assertEq(_ckpts.latest(), 0);
|
||||
_assertLatestCheckpoint(false, 0, 0);
|
||||
|
||||
uint256 duplicates = 0;
|
||||
for (uint256 i = 0; i < keys.length; ++i) {
|
||||
uint32 key = keys[i];
|
||||
uint224 value = values[i % values.length];
|
||||
if (i > 0 && key == keys[i - 1]) ++duplicates;
|
||||
|
||||
// push
|
||||
_ckpts.push(key, value);
|
||||
|
||||
// check length & latest
|
||||
assertEq(_ckpts.length(), i + 1 - duplicates);
|
||||
assertEq(_ckpts.latest(), value);
|
||||
_assertLatestCheckpoint(true, key, value);
|
||||
}
|
||||
|
||||
if (keys.length > 0) {
|
||||
uint32 lastKey = keys[keys.length - 1];
|
||||
if (lastKey > 0) {
|
||||
pastKey = _boundUint32(pastKey, 0, lastKey - 1);
|
||||
|
||||
vm.expectRevert();
|
||||
this.push(pastKey, values[keys.length % values.length]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// used to test reverts
|
||||
function push(uint32 key, uint224 value) external {
|
||||
_ckpts.push(key, value);
|
||||
}
|
||||
|
||||
function testLookup(uint32[] memory keys, uint224[] memory values, uint32 lookup) public {
|
||||
vm.assume(values.length > 0 && values.length <= keys.length);
|
||||
_prepareKeys(keys, _KEY_MAX_GAP);
|
||||
|
||||
uint32 lastKey = keys.length == 0 ? 0 : keys[keys.length - 1];
|
||||
lookup = _boundUint32(lookup, 0, lastKey + _KEY_MAX_GAP);
|
||||
|
||||
uint224 upper = 0;
|
||||
uint224 lower = 0;
|
||||
uint32 lowerKey = type(uint32).max;
|
||||
for (uint256 i = 0; i < keys.length; ++i) {
|
||||
uint32 key = keys[i];
|
||||
uint224 value = values[i % values.length];
|
||||
|
||||
// push
|
||||
_ckpts.push(key, value);
|
||||
|
||||
// track expected result of lookups
|
||||
if (key <= lookup) {
|
||||
upper = value;
|
||||
}
|
||||
// find the first key that is not smaller than the lookup key
|
||||
if (key >= lookup && (i == 0 || keys[i - 1] < lookup)) {
|
||||
lowerKey = key;
|
||||
}
|
||||
if (key == lowerKey) {
|
||||
lower = value;
|
||||
}
|
||||
}
|
||||
|
||||
// check lookup
|
||||
assertEq(_ckpts.lowerLookup(lookup), lower);
|
||||
assertEq(_ckpts.upperLookup(lookup), upper);
|
||||
assertEq(_ckpts.upperLookupRecent(lookup), upper);
|
||||
}
|
||||
}
|
||||
|
||||
contract CheckpointsTrace208Test is Test {
|
||||
using Checkpoints for Checkpoints.Trace208;
|
||||
|
||||
// Maximum gap between keys used during the fuzzing tests: the `_prepareKeys` function with make sure that
|
||||
// key#n+1 is in the [key#n, key#n + _KEY_MAX_GAP] range.
|
||||
uint8 internal constant _KEY_MAX_GAP = 64;
|
||||
|
||||
Checkpoints.Trace208 internal _ckpts;
|
||||
|
||||
// helpers
|
||||
function _boundUint48(uint48 x, uint48 min, uint48 max) internal pure returns (uint48) {
|
||||
return SafeCast.toUint48(bound(uint256(x), uint256(min), uint256(max)));
|
||||
}
|
||||
|
||||
function _prepareKeys(uint48[] memory keys, uint48 maxSpread) internal pure {
|
||||
uint48 lastKey = 0;
|
||||
for (uint256 i = 0; i < keys.length; ++i) {
|
||||
uint48 key = _boundUint48(keys[i], lastKey, lastKey + maxSpread);
|
||||
keys[i] = key;
|
||||
lastKey = key;
|
||||
}
|
||||
}
|
||||
|
||||
function _assertLatestCheckpoint(bool exist, uint48 key, uint208 value) internal {
|
||||
(bool _exist, uint48 _key, uint208 _value) = _ckpts.latestCheckpoint();
|
||||
assertEq(_exist, exist);
|
||||
assertEq(_key, key);
|
||||
assertEq(_value, value);
|
||||
}
|
||||
|
||||
// tests
|
||||
function testPush(uint48[] memory keys, uint208[] memory values, uint48 pastKey) public {
|
||||
vm.assume(values.length > 0 && values.length <= keys.length);
|
||||
_prepareKeys(keys, _KEY_MAX_GAP);
|
||||
|
||||
// initial state
|
||||
assertEq(_ckpts.length(), 0);
|
||||
assertEq(_ckpts.latest(), 0);
|
||||
_assertLatestCheckpoint(false, 0, 0);
|
||||
|
||||
uint256 duplicates = 0;
|
||||
for (uint256 i = 0; i < keys.length; ++i) {
|
||||
uint48 key = keys[i];
|
||||
uint208 value = values[i % values.length];
|
||||
if (i > 0 && key == keys[i - 1]) ++duplicates;
|
||||
|
||||
// push
|
||||
_ckpts.push(key, value);
|
||||
|
||||
// check length & latest
|
||||
assertEq(_ckpts.length(), i + 1 - duplicates);
|
||||
assertEq(_ckpts.latest(), value);
|
||||
_assertLatestCheckpoint(true, key, value);
|
||||
}
|
||||
|
||||
if (keys.length > 0) {
|
||||
uint48 lastKey = keys[keys.length - 1];
|
||||
if (lastKey > 0) {
|
||||
pastKey = _boundUint48(pastKey, 0, lastKey - 1);
|
||||
|
||||
vm.expectRevert();
|
||||
this.push(pastKey, values[keys.length % values.length]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// used to test reverts
|
||||
function push(uint48 key, uint208 value) external {
|
||||
_ckpts.push(key, value);
|
||||
}
|
||||
|
||||
function testLookup(uint48[] memory keys, uint208[] memory values, uint48 lookup) public {
|
||||
vm.assume(values.length > 0 && values.length <= keys.length);
|
||||
_prepareKeys(keys, _KEY_MAX_GAP);
|
||||
|
||||
uint48 lastKey = keys.length == 0 ? 0 : keys[keys.length - 1];
|
||||
lookup = _boundUint48(lookup, 0, lastKey + _KEY_MAX_GAP);
|
||||
|
||||
uint208 upper = 0;
|
||||
uint208 lower = 0;
|
||||
uint48 lowerKey = type(uint48).max;
|
||||
for (uint256 i = 0; i < keys.length; ++i) {
|
||||
uint48 key = keys[i];
|
||||
uint208 value = values[i % values.length];
|
||||
|
||||
// push
|
||||
_ckpts.push(key, value);
|
||||
|
||||
// track expected result of lookups
|
||||
if (key <= lookup) {
|
||||
upper = value;
|
||||
}
|
||||
// find the first key that is not smaller than the lookup key
|
||||
if (key >= lookup && (i == 0 || keys[i - 1] < lookup)) {
|
||||
lowerKey = key;
|
||||
}
|
||||
if (key == lowerKey) {
|
||||
lower = value;
|
||||
}
|
||||
}
|
||||
|
||||
// check lookup
|
||||
assertEq(_ckpts.lowerLookup(lookup), lower);
|
||||
assertEq(_ckpts.upperLookup(lookup), upper);
|
||||
assertEq(_ckpts.upperLookupRecent(lookup), upper);
|
||||
}
|
||||
}
|
||||
|
||||
contract CheckpointsTrace160Test is Test {
|
||||
using Checkpoints for Checkpoints.Trace160;
|
||||
|
||||
// Maximum gap between keys used during the fuzzing tests: the `_prepareKeys` function with make sure that
|
||||
// key#n+1 is in the [key#n, key#n + _KEY_MAX_GAP] range.
|
||||
uint8 internal constant _KEY_MAX_GAP = 64;
|
||||
|
||||
Checkpoints.Trace160 internal _ckpts;
|
||||
|
||||
// helpers
|
||||
function _boundUint96(uint96 x, uint96 min, uint96 max) internal pure returns (uint96) {
|
||||
return SafeCast.toUint96(bound(uint256(x), uint256(min), uint256(max)));
|
||||
}
|
||||
|
||||
function _prepareKeys(uint96[] memory keys, uint96 maxSpread) internal pure {
|
||||
uint96 lastKey = 0;
|
||||
for (uint256 i = 0; i < keys.length; ++i) {
|
||||
uint96 key = _boundUint96(keys[i], lastKey, lastKey + maxSpread);
|
||||
keys[i] = key;
|
||||
lastKey = key;
|
||||
}
|
||||
}
|
||||
|
||||
function _assertLatestCheckpoint(bool exist, uint96 key, uint160 value) internal {
|
||||
(bool _exist, uint96 _key, uint160 _value) = _ckpts.latestCheckpoint();
|
||||
assertEq(_exist, exist);
|
||||
assertEq(_key, key);
|
||||
assertEq(_value, value);
|
||||
}
|
||||
|
||||
// tests
|
||||
function testPush(uint96[] memory keys, uint160[] memory values, uint96 pastKey) public {
|
||||
vm.assume(values.length > 0 && values.length <= keys.length);
|
||||
_prepareKeys(keys, _KEY_MAX_GAP);
|
||||
|
||||
// initial state
|
||||
assertEq(_ckpts.length(), 0);
|
||||
assertEq(_ckpts.latest(), 0);
|
||||
_assertLatestCheckpoint(false, 0, 0);
|
||||
|
||||
uint256 duplicates = 0;
|
||||
for (uint256 i = 0; i < keys.length; ++i) {
|
||||
uint96 key = keys[i];
|
||||
uint160 value = values[i % values.length];
|
||||
if (i > 0 && key == keys[i - 1]) ++duplicates;
|
||||
|
||||
// push
|
||||
_ckpts.push(key, value);
|
||||
|
||||
// check length & latest
|
||||
assertEq(_ckpts.length(), i + 1 - duplicates);
|
||||
assertEq(_ckpts.latest(), value);
|
||||
_assertLatestCheckpoint(true, key, value);
|
||||
}
|
||||
|
||||
if (keys.length > 0) {
|
||||
uint96 lastKey = keys[keys.length - 1];
|
||||
if (lastKey > 0) {
|
||||
pastKey = _boundUint96(pastKey, 0, lastKey - 1);
|
||||
|
||||
vm.expectRevert();
|
||||
this.push(pastKey, values[keys.length % values.length]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// used to test reverts
|
||||
function push(uint96 key, uint160 value) external {
|
||||
_ckpts.push(key, value);
|
||||
}
|
||||
|
||||
function testLookup(uint96[] memory keys, uint160[] memory values, uint96 lookup) public {
|
||||
vm.assume(values.length > 0 && values.length <= keys.length);
|
||||
_prepareKeys(keys, _KEY_MAX_GAP);
|
||||
|
||||
uint96 lastKey = keys.length == 0 ? 0 : keys[keys.length - 1];
|
||||
lookup = _boundUint96(lookup, 0, lastKey + _KEY_MAX_GAP);
|
||||
|
||||
uint160 upper = 0;
|
||||
uint160 lower = 0;
|
||||
uint96 lowerKey = type(uint96).max;
|
||||
for (uint256 i = 0; i < keys.length; ++i) {
|
||||
uint96 key = keys[i];
|
||||
uint160 value = values[i % values.length];
|
||||
|
||||
// push
|
||||
_ckpts.push(key, value);
|
||||
|
||||
// track expected result of lookups
|
||||
if (key <= lookup) {
|
||||
upper = value;
|
||||
}
|
||||
// find the first key that is not smaller than the lookup key
|
||||
if (key >= lookup && (i == 0 || keys[i - 1] < lookup)) {
|
||||
lowerKey = key;
|
||||
}
|
||||
if (key == lowerKey) {
|
||||
lower = value;
|
||||
}
|
||||
}
|
||||
|
||||
// check lookup
|
||||
assertEq(_ckpts.lowerLookup(lookup), lower);
|
||||
assertEq(_ckpts.upperLookup(lookup), upper);
|
||||
assertEq(_ckpts.upperLookupRecent(lookup), upper);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
const { VALUE_SIZES } = require('../../../scripts/generate/templates/Checkpoints.opts');
|
||||
|
||||
describe('Checkpoints', function () {
|
||||
for (const length of VALUE_SIZES) {
|
||||
describe(`Trace${length}`, function () {
|
||||
const fixture = async () => {
|
||||
const mock = await ethers.deployContract('$Checkpoints');
|
||||
const methods = {
|
||||
at: (...args) => mock.getFunction(`$at_Checkpoints_Trace${length}`)(0, ...args),
|
||||
latest: (...args) => mock.getFunction(`$latest_Checkpoints_Trace${length}`)(0, ...args),
|
||||
latestCheckpoint: (...args) => mock.getFunction(`$latestCheckpoint_Checkpoints_Trace${length}`)(0, ...args),
|
||||
length: (...args) => mock.getFunction(`$length_Checkpoints_Trace${length}`)(0, ...args),
|
||||
push: (...args) => mock.getFunction(`$push(uint256,uint${256 - length},uint${length})`)(0, ...args),
|
||||
lowerLookup: (...args) => mock.getFunction(`$lowerLookup(uint256,uint${256 - length})`)(0, ...args),
|
||||
upperLookup: (...args) => mock.getFunction(`$upperLookup(uint256,uint${256 - length})`)(0, ...args),
|
||||
upperLookupRecent: (...args) =>
|
||||
mock.getFunction(`$upperLookupRecent(uint256,uint${256 - length})`)(0, ...args),
|
||||
};
|
||||
|
||||
return { mock, methods };
|
||||
};
|
||||
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
describe('without checkpoints', function () {
|
||||
it('at zero reverts', async function () {
|
||||
// Reverts with array out of bound access, which is unspecified
|
||||
await expect(this.methods.at(0)).to.be.reverted;
|
||||
});
|
||||
|
||||
it('returns zero as latest value', async function () {
|
||||
expect(await this.methods.latest()).to.equal(0n);
|
||||
|
||||
const ckpt = await this.methods.latestCheckpoint();
|
||||
expect(ckpt[0]).to.be.false;
|
||||
expect(ckpt[1]).to.equal(0n);
|
||||
expect(ckpt[2]).to.equal(0n);
|
||||
});
|
||||
|
||||
it('lookup returns 0', async function () {
|
||||
expect(await this.methods.lowerLookup(0)).to.equal(0n);
|
||||
expect(await this.methods.upperLookup(0)).to.equal(0n);
|
||||
expect(await this.methods.upperLookupRecent(0)).to.equal(0n);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with checkpoints', function () {
|
||||
beforeEach('pushing checkpoints', async function () {
|
||||
this.checkpoints = [
|
||||
{ key: 2n, value: 17n },
|
||||
{ key: 3n, value: 42n },
|
||||
{ key: 5n, value: 101n },
|
||||
{ key: 7n, value: 23n },
|
||||
{ key: 11n, value: 99n },
|
||||
];
|
||||
for (const { key, value } of this.checkpoints) {
|
||||
await this.methods.push(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
it('at keys', async function () {
|
||||
for (const [index, { key, value }] of this.checkpoints.entries()) {
|
||||
const at = await this.methods.at(index);
|
||||
expect(at._value).to.equal(value);
|
||||
expect(at._key).to.equal(key);
|
||||
}
|
||||
});
|
||||
|
||||
it('length', async function () {
|
||||
expect(await this.methods.length()).to.equal(this.checkpoints.length);
|
||||
});
|
||||
|
||||
it('returns latest value', async function () {
|
||||
const latest = this.checkpoints.at(-1);
|
||||
expect(await this.methods.latest()).to.equal(latest.value);
|
||||
expect(await this.methods.latestCheckpoint()).to.deep.equal([true, latest.key, latest.value]);
|
||||
});
|
||||
|
||||
it('cannot push values in the past', async function () {
|
||||
await expect(this.methods.push(this.checkpoints.at(-1).key - 1n, 0n)).to.be.revertedWithCustomError(
|
||||
this.mock,
|
||||
'CheckpointUnorderedInsertion',
|
||||
);
|
||||
});
|
||||
|
||||
it('can update last value', async function () {
|
||||
const newValue = 42n;
|
||||
|
||||
// check length before the update
|
||||
expect(await this.methods.length()).to.equal(this.checkpoints.length);
|
||||
|
||||
// update last key
|
||||
await this.methods.push(this.checkpoints.at(-1).key, newValue);
|
||||
expect(await this.methods.latest()).to.equal(newValue);
|
||||
|
||||
// check that length did not change
|
||||
expect(await this.methods.length()).to.equal(this.checkpoints.length);
|
||||
});
|
||||
|
||||
it('lower lookup', async function () {
|
||||
for (let i = 0; i < 14; ++i) {
|
||||
const value = this.checkpoints.find(x => i <= x.key)?.value || 0n;
|
||||
|
||||
expect(await this.methods.lowerLookup(i)).to.equal(value);
|
||||
}
|
||||
});
|
||||
|
||||
it('upper lookup & upperLookupRecent', async function () {
|
||||
for (let i = 0; i < 14; ++i) {
|
||||
const value = this.checkpoints.findLast(x => i >= x.key)?.value || 0n;
|
||||
|
||||
expect(await this.methods.upperLookup(i)).to.equal(value);
|
||||
expect(await this.methods.upperLookupRecent(i)).to.equal(value);
|
||||
}
|
||||
});
|
||||
|
||||
it('upperLookupRecent with more than 5 checkpoints', async function () {
|
||||
const moreCheckpoints = [
|
||||
{ key: 12n, value: 22n },
|
||||
{ key: 13n, value: 131n },
|
||||
{ key: 17n, value: 45n },
|
||||
{ key: 19n, value: 31452n },
|
||||
{ key: 21n, value: 0n },
|
||||
];
|
||||
const allCheckpoints = [].concat(this.checkpoints, moreCheckpoints);
|
||||
|
||||
for (const { key, value } of moreCheckpoints) {
|
||||
await this.methods.push(key, value);
|
||||
}
|
||||
|
||||
for (let i = 0; i < 25; ++i) {
|
||||
const value = allCheckpoints.findLast(x => i >= x.key)?.value || 0n;
|
||||
expect(await this.methods.upperLookup(i)).to.equal(value);
|
||||
expect(await this.methods.upperLookupRecent(i)).to.equal(value);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,79 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
const { PANIC_CODES } = require('@nomicfoundation/hardhat-chai-matchers/panic');
|
||||
|
||||
const { generators } = require('../../helpers/random');
|
||||
|
||||
const LENGTH = 4;
|
||||
|
||||
async function fixture() {
|
||||
const mock = await ethers.deployContract('$CircularBuffer');
|
||||
await mock.$setup(0, LENGTH);
|
||||
return { mock };
|
||||
}
|
||||
|
||||
describe('CircularBuffer', function () {
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
it('starts empty', async function () {
|
||||
expect(await this.mock.$count(0)).to.equal(0n);
|
||||
expect(await this.mock.$length(0)).to.equal(LENGTH);
|
||||
expect(await this.mock.$includes(0, ethers.ZeroHash)).to.be.false;
|
||||
await expect(this.mock.$last(0, 0)).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS);
|
||||
});
|
||||
|
||||
it('push', async function () {
|
||||
const values = Array.from({ length: LENGTH + 3 }, generators.bytes32);
|
||||
|
||||
for (const [i, value] of values.map((v, i) => [i, v])) {
|
||||
// push value
|
||||
await this.mock.$push(0, value);
|
||||
|
||||
// view of the values
|
||||
const pushed = values.slice(0, i + 1);
|
||||
const stored = pushed.slice(-LENGTH);
|
||||
const dropped = pushed.slice(0, -LENGTH);
|
||||
|
||||
// check count
|
||||
expect(await this.mock.$length(0)).to.equal(LENGTH);
|
||||
expect(await this.mock.$count(0)).to.equal(stored.length);
|
||||
|
||||
// check last
|
||||
for (const j in stored) {
|
||||
expect(await this.mock.$last(0, j)).to.equal(stored.at(-j - 1));
|
||||
}
|
||||
await expect(this.mock.$last(0, stored.length + 1)).to.be.revertedWithPanic(
|
||||
PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS,
|
||||
);
|
||||
|
||||
// check included and non-included values
|
||||
for (const v of stored) {
|
||||
expect(await this.mock.$includes(0, v)).to.be.true;
|
||||
}
|
||||
for (const v of dropped) {
|
||||
expect(await this.mock.$includes(0, v)).to.be.false;
|
||||
}
|
||||
expect(await this.mock.$includes(0, ethers.ZeroHash)).to.be.false;
|
||||
}
|
||||
});
|
||||
|
||||
it('clear', async function () {
|
||||
const value = generators.bytes32();
|
||||
await this.mock.$push(0, value);
|
||||
|
||||
expect(await this.mock.$count(0)).to.equal(1n);
|
||||
expect(await this.mock.$length(0)).to.equal(LENGTH);
|
||||
expect(await this.mock.$includes(0, value)).to.be.true;
|
||||
await this.mock.$last(0, 0); // not revert
|
||||
|
||||
await this.mock.$clear(0);
|
||||
|
||||
expect(await this.mock.$count(0)).to.equal(0n);
|
||||
expect(await this.mock.$length(0)).to.equal(LENGTH);
|
||||
expect(await this.mock.$includes(0, value)).to.be.false;
|
||||
await expect(this.mock.$last(0, 0)).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,102 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
const { PANIC_CODES } = require('@nomicfoundation/hardhat-chai-matchers/panic');
|
||||
|
||||
async function fixture() {
|
||||
const mock = await ethers.deployContract('$DoubleEndedQueue');
|
||||
|
||||
/** Rebuild the content of the deque as a JS array. */
|
||||
const getContent = () =>
|
||||
mock.$length(0).then(length => Promise.all(Array.from({ length: Number(length) }, (_, i) => mock.$at(0, i))));
|
||||
|
||||
return { mock, getContent };
|
||||
}
|
||||
|
||||
describe('DoubleEndedQueue', function () {
|
||||
const coder = ethers.AbiCoder.defaultAbiCoder();
|
||||
const bytesA = coder.encode(['uint256'], [0xdeadbeef]);
|
||||
const bytesB = coder.encode(['uint256'], [0x0123456789]);
|
||||
const bytesC = coder.encode(['uint256'], [0x42424242]);
|
||||
const bytesD = coder.encode(['uint256'], [0x171717]);
|
||||
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
describe('when empty', function () {
|
||||
it('getters', async function () {
|
||||
expect(await this.mock.$empty(0)).to.be.true;
|
||||
expect(await this.getContent()).to.have.ordered.members([]);
|
||||
});
|
||||
|
||||
it('reverts on accesses', async function () {
|
||||
await expect(this.mock.$popBack(0)).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY);
|
||||
await expect(this.mock.$popFront(0)).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY);
|
||||
await expect(this.mock.$back(0)).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS);
|
||||
await expect(this.mock.$front(0)).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when not empty', function () {
|
||||
beforeEach(async function () {
|
||||
await this.mock.$pushBack(0, bytesB);
|
||||
await this.mock.$pushFront(0, bytesA);
|
||||
await this.mock.$pushBack(0, bytesC);
|
||||
this.content = [bytesA, bytesB, bytesC];
|
||||
});
|
||||
|
||||
it('getters', async function () {
|
||||
expect(await this.mock.$empty(0)).to.be.false;
|
||||
expect(await this.mock.$length(0)).to.equal(this.content.length);
|
||||
expect(await this.mock.$front(0)).to.equal(this.content[0]);
|
||||
expect(await this.mock.$back(0)).to.equal(this.content[this.content.length - 1]);
|
||||
expect(await this.getContent()).to.have.ordered.members(this.content);
|
||||
});
|
||||
|
||||
it('out of bounds access', async function () {
|
||||
await expect(this.mock.$at(0, this.content.length)).to.be.revertedWithPanic(
|
||||
PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS,
|
||||
);
|
||||
});
|
||||
|
||||
describe('push', function () {
|
||||
it('front', async function () {
|
||||
await this.mock.$pushFront(0, bytesD);
|
||||
this.content.unshift(bytesD); // add element at the beginning
|
||||
|
||||
expect(await this.getContent()).to.have.ordered.members(this.content);
|
||||
});
|
||||
|
||||
it('back', async function () {
|
||||
await this.mock.$pushBack(0, bytesD);
|
||||
this.content.push(bytesD); // add element at the end
|
||||
|
||||
expect(await this.getContent()).to.have.ordered.members(this.content);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pop', function () {
|
||||
it('front', async function () {
|
||||
const value = this.content.shift(); // remove first element
|
||||
await expect(this.mock.$popFront(0)).to.emit(this.mock, 'return$popFront').withArgs(value);
|
||||
|
||||
expect(await this.getContent()).to.have.ordered.members(this.content);
|
||||
});
|
||||
|
||||
it('back', async function () {
|
||||
const value = this.content.pop(); // remove last element
|
||||
await expect(this.mock.$popBack(0)).to.emit(this.mock, 'return$popBack').withArgs(value);
|
||||
|
||||
expect(await this.getContent()).to.have.ordered.members(this.content);
|
||||
});
|
||||
});
|
||||
|
||||
it('clear', async function () {
|
||||
await this.mock.$clear(0);
|
||||
|
||||
expect(await this.mock.$empty(0)).to.be.true;
|
||||
expect(await this.getContent()).to.have.ordered.members([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,151 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
|
||||
const zip = (array1, array2) => array1.map((item, index) => [item, array2[index]]);
|
||||
|
||||
function shouldBehaveLikeMap() {
|
||||
async function expectMembersMatch(methods, keys, values) {
|
||||
expect(keys.length).to.equal(values.length);
|
||||
expect(await methods.length()).to.equal(keys.length);
|
||||
expect([...(await methods.keys())]).to.have.members(keys);
|
||||
|
||||
for (const [key, value] of zip(keys, values)) {
|
||||
expect(await methods.contains(key)).to.be.true;
|
||||
expect(await methods.get(key)).to.equal(value);
|
||||
}
|
||||
|
||||
expect(await Promise.all(keys.map((_, index) => methods.at(index)))).to.have.deep.members(zip(keys, values));
|
||||
}
|
||||
|
||||
it('starts empty', async function () {
|
||||
expect(await this.methods.contains(this.keyA)).to.be.false;
|
||||
|
||||
await expectMembersMatch(this.methods, [], []);
|
||||
});
|
||||
|
||||
describe('set', function () {
|
||||
it('adds a key', async function () {
|
||||
await expect(this.methods.set(this.keyA, this.valueA)).to.emit(this.mock, this.events.setReturn).withArgs(true);
|
||||
|
||||
await expectMembersMatch(this.methods, [this.keyA], [this.valueA]);
|
||||
});
|
||||
|
||||
it('adds several keys', async function () {
|
||||
await this.methods.set(this.keyA, this.valueA);
|
||||
await this.methods.set(this.keyB, this.valueB);
|
||||
|
||||
await expectMembersMatch(this.methods, [this.keyA, this.keyB], [this.valueA, this.valueB]);
|
||||
expect(await this.methods.contains(this.keyC)).to.be.false;
|
||||
});
|
||||
|
||||
it('returns false when adding keys already in the set', async function () {
|
||||
await this.methods.set(this.keyA, this.valueA);
|
||||
|
||||
await expect(this.methods.set(this.keyA, this.valueA)).to.emit(this.mock, this.events.setReturn).withArgs(false);
|
||||
|
||||
await expectMembersMatch(this.methods, [this.keyA], [this.valueA]);
|
||||
});
|
||||
|
||||
it('updates values for keys already in the set', async function () {
|
||||
await this.methods.set(this.keyA, this.valueA);
|
||||
await this.methods.set(this.keyA, this.valueB);
|
||||
|
||||
await expectMembersMatch(this.methods, [this.keyA], [this.valueB]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove', function () {
|
||||
it('removes added keys', async function () {
|
||||
await this.methods.set(this.keyA, this.valueA);
|
||||
|
||||
await expect(this.methods.remove(this.keyA)).to.emit(this.mock, this.events.removeReturn).withArgs(true);
|
||||
|
||||
expect(await this.methods.contains(this.keyA)).to.be.false;
|
||||
await expectMembersMatch(this.methods, [], []);
|
||||
});
|
||||
|
||||
it('returns false when removing keys not in the set', async function () {
|
||||
await expect(await this.methods.remove(this.keyA))
|
||||
.to.emit(this.mock, this.events.removeReturn)
|
||||
.withArgs(false);
|
||||
|
||||
expect(await this.methods.contains(this.keyA)).to.be.false;
|
||||
});
|
||||
|
||||
it('adds and removes multiple keys', async function () {
|
||||
// []
|
||||
|
||||
await this.methods.set(this.keyA, this.valueA);
|
||||
await this.methods.set(this.keyC, this.valueC);
|
||||
|
||||
// [A, C]
|
||||
|
||||
await this.methods.remove(this.keyA);
|
||||
await this.methods.remove(this.keyB);
|
||||
|
||||
// [C]
|
||||
|
||||
await this.methods.set(this.keyB, this.valueB);
|
||||
|
||||
// [C, B]
|
||||
|
||||
await this.methods.set(this.keyA, this.valueA);
|
||||
await this.methods.remove(this.keyC);
|
||||
|
||||
// [A, B]
|
||||
|
||||
await this.methods.set(this.keyA, this.valueA);
|
||||
await this.methods.set(this.keyB, this.valueB);
|
||||
|
||||
// [A, B]
|
||||
|
||||
await this.methods.set(this.keyC, this.valueC);
|
||||
await this.methods.remove(this.keyA);
|
||||
|
||||
// [B, C]
|
||||
|
||||
await this.methods.set(this.keyA, this.valueA);
|
||||
await this.methods.remove(this.keyB);
|
||||
|
||||
// [A, C]
|
||||
|
||||
await expectMembersMatch(this.methods, [this.keyA, this.keyC], [this.valueA, this.valueC]);
|
||||
|
||||
expect(await this.methods.contains(this.keyA)).to.be.true;
|
||||
expect(await this.methods.contains(this.keyB)).to.be.false;
|
||||
expect(await this.methods.contains(this.keyC)).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('read', function () {
|
||||
beforeEach(async function () {
|
||||
await this.methods.set(this.keyA, this.valueA);
|
||||
});
|
||||
|
||||
describe('get', function () {
|
||||
it('existing value', async function () {
|
||||
expect(await this.methods.get(this.keyA)).to.equal(this.valueA);
|
||||
});
|
||||
|
||||
it('missing value', async function () {
|
||||
await expect(this.methods.get(this.keyB))
|
||||
.to.be.revertedWithCustomError(this.mock, 'EnumerableMapNonexistentKey')
|
||||
.withArgs(ethers.AbiCoder.defaultAbiCoder().encode([this.keyType], [this.keyB]));
|
||||
});
|
||||
});
|
||||
|
||||
describe('tryGet', function () {
|
||||
it('existing value', async function () {
|
||||
expect(await this.methods.tryGet(this.keyA)).to.have.ordered.members([true, this.valueA]);
|
||||
});
|
||||
|
||||
it('missing value', async function () {
|
||||
expect(await this.methods.tryGet(this.keyB)).to.have.ordered.members([false, this.zeroValue]);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
shouldBehaveLikeMap,
|
||||
};
|
||||
@@ -0,0 +1,65 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
const { mapValues } = require('../../helpers/iterate');
|
||||
const { generators } = require('../../helpers/random');
|
||||
const { TYPES, formatType } = require('../../../scripts/generate/templates/EnumerableMap.opts');
|
||||
|
||||
const { shouldBehaveLikeMap } = require('./EnumerableMap.behavior');
|
||||
|
||||
// Add Bytes32ToBytes32Map that must be tested but is not part of the generated types.
|
||||
TYPES.unshift(formatType('bytes32', 'bytes32'));
|
||||
|
||||
async function fixture() {
|
||||
const mock = await ethers.deployContract('$EnumerableMap');
|
||||
const env = Object.fromEntries(
|
||||
TYPES.map(({ name, keyType, valueType }) => [
|
||||
name,
|
||||
{
|
||||
keyType,
|
||||
keys: Array.from({ length: 3 }, generators[keyType]),
|
||||
values: Array.from({ length: 3 }, generators[valueType]),
|
||||
zeroValue: generators[valueType].zero,
|
||||
methods: mapValues(
|
||||
{
|
||||
set: `$set(uint256,${keyType},${valueType})`,
|
||||
get: `$get_EnumerableMap_${name}(uint256,${keyType})`,
|
||||
tryGet: `$tryGet_EnumerableMap_${name}(uint256,${keyType})`,
|
||||
remove: `$remove_EnumerableMap_${name}(uint256,${keyType})`,
|
||||
length: `$length_EnumerableMap_${name}(uint256)`,
|
||||
at: `$at_EnumerableMap_${name}(uint256,uint256)`,
|
||||
contains: `$contains_EnumerableMap_${name}(uint256,${keyType})`,
|
||||
keys: `$keys_EnumerableMap_${name}(uint256)`,
|
||||
},
|
||||
fnSig =>
|
||||
(...args) =>
|
||||
mock.getFunction(fnSig)(0, ...args),
|
||||
),
|
||||
events: {
|
||||
setReturn: `return$set_EnumerableMap_${name}_${keyType}_${valueType}`,
|
||||
removeReturn: `return$remove_EnumerableMap_${name}_${keyType}`,
|
||||
},
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
return { mock, env };
|
||||
}
|
||||
|
||||
describe('EnumerableMap', function () {
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
for (const { name } of TYPES) {
|
||||
describe(name, function () {
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, this.env[name]);
|
||||
[this.keyA, this.keyB, this.keyC] = this.keys;
|
||||
[this.valueA, this.valueB, this.valueC] = this.values;
|
||||
});
|
||||
|
||||
shouldBehaveLikeMap();
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,116 @@
|
||||
const { expect } = require('chai');
|
||||
const { PANIC_CODES } = require('@nomicfoundation/hardhat-chai-matchers/panic');
|
||||
|
||||
function shouldBehaveLikeSet() {
|
||||
async function expectMembersMatch(methods, values) {
|
||||
expect(await methods.length()).to.equal(values.length);
|
||||
for (const value of values) expect(await methods.contains(value)).to.be.true;
|
||||
|
||||
expect(await Promise.all(values.map((_, index) => methods.at(index)))).to.have.deep.members(values);
|
||||
expect([...(await methods.values())]).to.have.deep.members(values);
|
||||
}
|
||||
|
||||
it('starts empty', async function () {
|
||||
expect(await this.methods.contains(this.valueA)).to.be.false;
|
||||
|
||||
await expectMembersMatch(this.methods, []);
|
||||
});
|
||||
|
||||
describe('add', function () {
|
||||
it('adds a value', async function () {
|
||||
await expect(this.methods.add(this.valueA)).to.emit(this.mock, this.events.addReturn).withArgs(true);
|
||||
|
||||
await expectMembersMatch(this.methods, [this.valueA]);
|
||||
});
|
||||
|
||||
it('adds several values', async function () {
|
||||
await this.methods.add(this.valueA);
|
||||
await this.methods.add(this.valueB);
|
||||
|
||||
await expectMembersMatch(this.methods, [this.valueA, this.valueB]);
|
||||
expect(await this.methods.contains(this.valueC)).to.be.false;
|
||||
});
|
||||
|
||||
it('returns false when adding values already in the set', async function () {
|
||||
await this.methods.add(this.valueA);
|
||||
|
||||
await expect(this.methods.add(this.valueA)).to.emit(this.mock, this.events.addReturn).withArgs(false);
|
||||
|
||||
await expectMembersMatch(this.methods, [this.valueA]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('at', function () {
|
||||
it('reverts when retrieving non-existent elements', async function () {
|
||||
await expect(this.methods.at(0)).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS);
|
||||
});
|
||||
|
||||
it('retrieves existing element', async function () {
|
||||
await this.methods.add(this.valueA);
|
||||
expect(await this.methods.at(0)).to.equal(this.valueA);
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove', function () {
|
||||
it('removes added values', async function () {
|
||||
await this.methods.add(this.valueA);
|
||||
|
||||
await expect(this.methods.remove(this.valueA)).to.emit(this.mock, this.events.removeReturn).withArgs(true);
|
||||
|
||||
expect(await this.methods.contains(this.valueA)).to.be.false;
|
||||
await expectMembersMatch(this.methods, []);
|
||||
});
|
||||
|
||||
it('returns false when removing values not in the set', async function () {
|
||||
await expect(this.methods.remove(this.valueA)).to.emit(this.mock, this.events.removeReturn).withArgs(false);
|
||||
|
||||
expect(await this.methods.contains(this.valueA)).to.be.false;
|
||||
});
|
||||
|
||||
it('adds and removes multiple values', async function () {
|
||||
// []
|
||||
|
||||
await this.methods.add(this.valueA);
|
||||
await this.methods.add(this.valueC);
|
||||
|
||||
// [A, C]
|
||||
|
||||
await this.methods.remove(this.valueA);
|
||||
await this.methods.remove(this.valueB);
|
||||
|
||||
// [C]
|
||||
|
||||
await this.methods.add(this.valueB);
|
||||
|
||||
// [C, B]
|
||||
|
||||
await this.methods.add(this.valueA);
|
||||
await this.methods.remove(this.valueC);
|
||||
|
||||
// [A, B]
|
||||
|
||||
await this.methods.add(this.valueA);
|
||||
await this.methods.add(this.valueB);
|
||||
|
||||
// [A, B]
|
||||
|
||||
await this.methods.add(this.valueC);
|
||||
await this.methods.remove(this.valueA);
|
||||
|
||||
// [B, C]
|
||||
|
||||
await this.methods.add(this.valueA);
|
||||
await this.methods.remove(this.valueB);
|
||||
|
||||
// [A, C]
|
||||
|
||||
await expectMembersMatch(this.methods, [this.valueA, this.valueC]);
|
||||
|
||||
expect(await this.methods.contains(this.valueB)).to.be.false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
shouldBehaveLikeSet,
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
const { mapValues } = require('../../helpers/iterate');
|
||||
const { generators } = require('../../helpers/random');
|
||||
const { TYPES } = require('../../../scripts/generate/templates/EnumerableSet.opts');
|
||||
|
||||
const { shouldBehaveLikeSet } = require('./EnumerableSet.behavior');
|
||||
|
||||
const getMethods = (mock, fnSigs) => {
|
||||
return mapValues(
|
||||
fnSigs,
|
||||
fnSig =>
|
||||
(...args) =>
|
||||
mock.getFunction(fnSig)(0, ...args),
|
||||
);
|
||||
};
|
||||
|
||||
async function fixture() {
|
||||
const mock = await ethers.deployContract('$EnumerableSet');
|
||||
|
||||
const env = Object.fromEntries(
|
||||
TYPES.map(({ name, type }) => [
|
||||
type,
|
||||
{
|
||||
values: Array.from({ length: 3 }, generators[type]),
|
||||
methods: getMethods(mock, {
|
||||
add: `$add(uint256,${type})`,
|
||||
remove: `$remove(uint256,${type})`,
|
||||
contains: `$contains(uint256,${type})`,
|
||||
length: `$length_EnumerableSet_${name}(uint256)`,
|
||||
at: `$at_EnumerableSet_${name}(uint256,uint256)`,
|
||||
values: `$values_EnumerableSet_${name}(uint256)`,
|
||||
}),
|
||||
events: {
|
||||
addReturn: `return$add_EnumerableSet_${name}_${type}`,
|
||||
removeReturn: `return$remove_EnumerableSet_${name}_${type}`,
|
||||
},
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
return { mock, env };
|
||||
}
|
||||
|
||||
describe('EnumerableSet', function () {
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
for (const { type } of TYPES) {
|
||||
describe(type, function () {
|
||||
beforeEach(function () {
|
||||
Object.assign(this, this.env[type]);
|
||||
[this.valueA, this.valueB, this.valueC] = this.values;
|
||||
});
|
||||
|
||||
shouldBehaveLikeSet();
|
||||
});
|
||||
}
|
||||
});
|
||||
100
lib_openzeppelin_contracts/test/utils/structs/MerkleTree.test.js
Normal file
100
lib_openzeppelin_contracts/test/utils/structs/MerkleTree.test.js
Normal file
@@ -0,0 +1,100 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
const { PANIC_CODES } = require('@nomicfoundation/hardhat-chai-matchers/panic');
|
||||
const { StandardMerkleTree } = require('@openzeppelin/merkle-tree');
|
||||
|
||||
const { generators } = require('../../helpers/random');
|
||||
|
||||
const makeTree = (leafs = [ethers.ZeroHash]) =>
|
||||
StandardMerkleTree.of(
|
||||
leafs.map(leaf => [leaf]),
|
||||
['bytes32'],
|
||||
{ sortLeaves: false },
|
||||
);
|
||||
|
||||
const hashLeaf = leaf => makeTree().leafHash([leaf]);
|
||||
|
||||
const DEPTH = 4n; // 16 slots
|
||||
const ZERO = hashLeaf(ethers.ZeroHash);
|
||||
|
||||
async function fixture() {
|
||||
const mock = await ethers.deployContract('MerkleTreeMock');
|
||||
await mock.setup(DEPTH, ZERO);
|
||||
return { mock };
|
||||
}
|
||||
|
||||
describe('MerkleTree', function () {
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
it('sets initial values at setup', async function () {
|
||||
const merkleTree = makeTree(Array.from({ length: 2 ** Number(DEPTH) }, () => ethers.ZeroHash));
|
||||
|
||||
expect(await this.mock.root()).to.equal(merkleTree.root);
|
||||
expect(await this.mock.depth()).to.equal(DEPTH);
|
||||
expect(await this.mock.nextLeafIndex()).to.equal(0n);
|
||||
});
|
||||
|
||||
describe('push', function () {
|
||||
it('tree is correctly updated', async function () {
|
||||
const leafs = Array.from({ length: 2 ** Number(DEPTH) }, () => ethers.ZeroHash);
|
||||
|
||||
// for each leaf slot
|
||||
for (const i in leafs) {
|
||||
// generate random leaf and hash it
|
||||
const hashedLeaf = hashLeaf((leafs[i] = generators.bytes32()));
|
||||
|
||||
// update leaf list and rebuild tree.
|
||||
const tree = makeTree(leafs);
|
||||
|
||||
// push value to tree
|
||||
await expect(this.mock.push(hashedLeaf)).to.emit(this.mock, 'LeafInserted').withArgs(hashedLeaf, i, tree.root);
|
||||
|
||||
// check tree
|
||||
expect(await this.mock.root()).to.equal(tree.root);
|
||||
expect(await this.mock.nextLeafIndex()).to.equal(BigInt(i) + 1n);
|
||||
}
|
||||
});
|
||||
|
||||
it('revert when tree is full', async function () {
|
||||
await Promise.all(Array.from({ length: 2 ** Number(DEPTH) }).map(() => this.mock.push(ethers.ZeroHash)));
|
||||
|
||||
await expect(this.mock.push(ethers.ZeroHash)).to.be.revertedWithPanic(PANIC_CODES.TOO_MUCH_MEMORY_ALLOCATED);
|
||||
});
|
||||
});
|
||||
|
||||
it('reset', async function () {
|
||||
// empty tree
|
||||
const zeroLeafs = Array.from({ length: 2 ** Number(DEPTH) }, () => ethers.ZeroHash);
|
||||
const zeroTree = makeTree(zeroLeafs);
|
||||
|
||||
// tree with one element
|
||||
const leafs = Array.from({ length: 2 ** Number(DEPTH) }, () => ethers.ZeroHash);
|
||||
const hashedLeaf = hashLeaf((leafs[0] = generators.bytes32())); // fill first leaf and hash it
|
||||
const tree = makeTree(leafs);
|
||||
|
||||
// root should be that of a zero tree
|
||||
expect(await this.mock.root()).to.equal(zeroTree.root);
|
||||
expect(await this.mock.nextLeafIndex()).to.equal(0n);
|
||||
|
||||
// push leaf and check root
|
||||
await expect(this.mock.push(hashedLeaf)).to.emit(this.mock, 'LeafInserted').withArgs(hashedLeaf, 0, tree.root);
|
||||
|
||||
expect(await this.mock.root()).to.equal(tree.root);
|
||||
expect(await this.mock.nextLeafIndex()).to.equal(1n);
|
||||
|
||||
// reset tree
|
||||
await this.mock.setup(DEPTH, ZERO);
|
||||
|
||||
expect(await this.mock.root()).to.equal(zeroTree.root);
|
||||
expect(await this.mock.nextLeafIndex()).to.equal(0n);
|
||||
|
||||
// re-push leaf and check root
|
||||
await expect(this.mock.push(hashedLeaf)).to.emit(this.mock, 'LeafInserted').withArgs(hashedLeaf, 0, tree.root);
|
||||
|
||||
expect(await this.mock.root()).to.equal(tree.root);
|
||||
expect(await this.mock.nextLeafIndex()).to.equal(1n);
|
||||
});
|
||||
});
|
||||
135
lib_openzeppelin_contracts/test/utils/types/Time.test.js
Normal file
135
lib_openzeppelin_contracts/test/utils/types/Time.test.js
Normal file
@@ -0,0 +1,135 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
const { product } = require('../../helpers/iterate');
|
||||
const { max } = require('../../helpers/math');
|
||||
const time = require('../../helpers/time');
|
||||
|
||||
const MAX_UINT32 = 1n << (32n - 1n);
|
||||
const MAX_UINT48 = 1n << (48n - 1n);
|
||||
const SOME_VALUES = [0n, 1n, 2n, 15n, 16n, 17n, 42n];
|
||||
|
||||
const asUint = (value, size) => {
|
||||
value = ethers.toBigInt(value);
|
||||
size = ethers.toBigInt(size);
|
||||
expect(value).to.be.greaterThanOrEqual(0n, `value is not a valid uint${size}`);
|
||||
expect(value).to.be.lessThan(1n << size, `value is not a valid uint${size}`);
|
||||
return value;
|
||||
};
|
||||
|
||||
const unpackDelay = delay => ({
|
||||
valueBefore: (asUint(delay, 112) >> 32n) % (1n << 32n),
|
||||
valueAfter: (asUint(delay, 112) >> 0n) % (1n << 32n),
|
||||
effect: (asUint(delay, 112) >> 64n) % (1n << 48n),
|
||||
});
|
||||
|
||||
const packDelay = ({ valueBefore, valueAfter = 0n, effect = 0n }) =>
|
||||
(asUint(valueAfter, 32) << 0n) + (asUint(valueBefore, 32) << 32n) + (asUint(effect, 48) << 64n);
|
||||
|
||||
const effectSamplesForTimepoint = timepoint => [
|
||||
0n,
|
||||
timepoint,
|
||||
...product([-1n, 1n], [1n, 2n, 17n, 42n])
|
||||
.map(([sign, shift]) => timepoint + sign * shift)
|
||||
.filter(effect => effect > 0n && effect <= MAX_UINT48),
|
||||
MAX_UINT48,
|
||||
];
|
||||
|
||||
async function fixture() {
|
||||
const mock = await ethers.deployContract('$Time');
|
||||
return { mock };
|
||||
}
|
||||
|
||||
describe('Time', function () {
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
describe('clocks', function () {
|
||||
it('timestamp', async function () {
|
||||
expect(await this.mock.$timestamp()).to.equal(await time.clock.timestamp());
|
||||
});
|
||||
|
||||
it('block number', async function () {
|
||||
expect(await this.mock.$blockNumber()).to.equal(await time.clock.blocknumber());
|
||||
});
|
||||
});
|
||||
|
||||
describe('Delay', function () {
|
||||
describe('packing and unpacking', function () {
|
||||
const valueBefore = 17n;
|
||||
const valueAfter = 42n;
|
||||
const effect = 69n;
|
||||
const delay = 1272825341158973505578n;
|
||||
|
||||
it('pack', async function () {
|
||||
expect(await this.mock.$pack(valueBefore, valueAfter, effect)).to.equal(delay);
|
||||
expect(packDelay({ valueBefore, valueAfter, effect })).to.equal(delay);
|
||||
});
|
||||
|
||||
it('unpack', async function () {
|
||||
expect(await this.mock.$unpack(delay)).to.deep.equal([valueBefore, valueAfter, effect]);
|
||||
|
||||
expect(unpackDelay(delay)).to.deep.equal({
|
||||
valueBefore,
|
||||
valueAfter,
|
||||
effect,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('toDelay', async function () {
|
||||
for (const value of [...SOME_VALUES, MAX_UINT32]) {
|
||||
expect(await this.mock.$toDelay(value).then(unpackDelay)).to.deep.equal({
|
||||
valueBefore: 0n,
|
||||
valueAfter: value,
|
||||
effect: 0n,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('get & getFull', async function () {
|
||||
const timepoint = await time.clock.timestamp();
|
||||
const valueBefore = 24194n;
|
||||
const valueAfter = 4214143n;
|
||||
|
||||
for (const effect of effectSamplesForTimepoint(timepoint)) {
|
||||
const isPast = effect <= timepoint;
|
||||
const delay = packDelay({ valueBefore, valueAfter, effect });
|
||||
|
||||
expect(await this.mock.$get(delay)).to.equal(isPast ? valueAfter : valueBefore);
|
||||
expect(await this.mock.$getFull(delay)).to.deep.equal([
|
||||
isPast ? valueAfter : valueBefore,
|
||||
isPast ? 0n : valueAfter,
|
||||
isPast ? 0n : effect,
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
it('withUpdate', async function () {
|
||||
const timepoint = await time.clock.timestamp();
|
||||
const valueBefore = 24194n;
|
||||
const valueAfter = 4214143n;
|
||||
const newvalueAfter = 94716n;
|
||||
|
||||
for (const effect of effectSamplesForTimepoint(timepoint))
|
||||
for (const minSetback of [...SOME_VALUES, MAX_UINT32]) {
|
||||
const isPast = effect <= timepoint;
|
||||
const expectedvalueBefore = isPast ? valueAfter : valueBefore;
|
||||
const expectedSetback = max(minSetback, expectedvalueBefore - newvalueAfter, 0n);
|
||||
|
||||
expect(
|
||||
await this.mock.$withUpdate(packDelay({ valueBefore, valueAfter, effect }), newvalueAfter, minSetback),
|
||||
).to.deep.equal([
|
||||
packDelay({
|
||||
valueBefore: expectedvalueBefore,
|
||||
valueAfter: newvalueAfter,
|
||||
effect: timepoint + expectedSetback,
|
||||
}),
|
||||
timepoint + expectedSetback,
|
||||
]);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user