Migrate utils to ethersjs v6 (#4736)
Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com> Co-authored-by: ernestognw <ernestognw@gmail.com>
This commit is contained in:
@ -4,7 +4,7 @@ pragma solidity ^0.8.20;
|
||||
|
||||
import {ERC20MulticallMock} from "./token/ERC20MulticallMock.sol";
|
||||
|
||||
contract MulticallTest {
|
||||
contract MulticallHelper {
|
||||
function checkReturnValues(
|
||||
ERC20MulticallMock multicallToken,
|
||||
address[] calldata recipients,
|
||||
@ -7,41 +7,41 @@ import {StorageSlot} from "../utils/StorageSlot.sol";
|
||||
contract StorageSlotMock {
|
||||
using StorageSlot for *;
|
||||
|
||||
function setBoolean(bytes32 slot, bool value) public {
|
||||
function setBooleanSlot(bytes32 slot, bool value) public {
|
||||
slot.getBooleanSlot().value = value;
|
||||
}
|
||||
|
||||
function setAddress(bytes32 slot, address value) public {
|
||||
function setAddressSlot(bytes32 slot, address value) public {
|
||||
slot.getAddressSlot().value = value;
|
||||
}
|
||||
|
||||
function setBytes32(bytes32 slot, bytes32 value) public {
|
||||
function setBytes32Slot(bytes32 slot, bytes32 value) public {
|
||||
slot.getBytes32Slot().value = value;
|
||||
}
|
||||
|
||||
function setUint256(bytes32 slot, uint256 value) public {
|
||||
function setUint256Slot(bytes32 slot, uint256 value) public {
|
||||
slot.getUint256Slot().value = value;
|
||||
}
|
||||
|
||||
function getBoolean(bytes32 slot) public view returns (bool) {
|
||||
function getBooleanSlot(bytes32 slot) public view returns (bool) {
|
||||
return slot.getBooleanSlot().value;
|
||||
}
|
||||
|
||||
function getAddress(bytes32 slot) public view returns (address) {
|
||||
function getAddressSlot(bytes32 slot) public view returns (address) {
|
||||
return slot.getAddressSlot().value;
|
||||
}
|
||||
|
||||
function getBytes32(bytes32 slot) public view returns (bytes32) {
|
||||
function getBytes32Slot(bytes32 slot) public view returns (bytes32) {
|
||||
return slot.getBytes32Slot().value;
|
||||
}
|
||||
|
||||
function getUint256(bytes32 slot) public view returns (uint256) {
|
||||
function getUint256Slot(bytes32 slot) public view returns (uint256) {
|
||||
return slot.getUint256Slot().value;
|
||||
}
|
||||
|
||||
mapping(uint256 key => string) public stringMap;
|
||||
|
||||
function setString(bytes32 slot, string calldata value) public {
|
||||
function setStringSlot(bytes32 slot, string calldata value) public {
|
||||
slot.getStringSlot().value = value;
|
||||
}
|
||||
|
||||
@ -49,7 +49,7 @@ contract StorageSlotMock {
|
||||
stringMap[key].getStringSlot().value = value;
|
||||
}
|
||||
|
||||
function getString(bytes32 slot) public view returns (string memory) {
|
||||
function getStringSlot(bytes32 slot) public view returns (string memory) {
|
||||
return slot.getStringSlot().value;
|
||||
}
|
||||
|
||||
@ -59,7 +59,7 @@ contract StorageSlotMock {
|
||||
|
||||
mapping(uint256 key => bytes) public bytesMap;
|
||||
|
||||
function setBytes(bytes32 slot, bytes calldata value) public {
|
||||
function setBytesSlot(bytes32 slot, bytes calldata value) public {
|
||||
slot.getBytesSlot().value = value;
|
||||
}
|
||||
|
||||
@ -67,7 +67,7 @@ contract StorageSlotMock {
|
||||
bytesMap[key].getBytesSlot().value = value;
|
||||
}
|
||||
|
||||
function getBytes(bytes32 slot) public view returns (bytes memory) {
|
||||
function getBytesSlot(bytes32 slot) public view returns (bytes memory) {
|
||||
return slot.getBytesSlot().value;
|
||||
}
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@ const generators = {
|
||||
address: () => ethers.Wallet.createRandom().address,
|
||||
bytes32: () => ethers.hexlify(ethers.randomBytes(32)),
|
||||
uint256: () => ethers.toBigInt(ethers.randomBytes(32)),
|
||||
hexBytes: length => ethers.hexlify(ethers.randomBytes(length)),
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
|
||||
@ -1,121 +1,120 @@
|
||||
require('@openzeppelin/test-helpers');
|
||||
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
const AddressArraysMock = artifacts.require('AddressArraysMock');
|
||||
const Bytes32ArraysMock = artifacts.require('Bytes32ArraysMock');
|
||||
const Uint256ArraysMock = artifacts.require('Uint256ArraysMock');
|
||||
const { randomArray, generators } = require('../helpers/random');
|
||||
|
||||
contract('Arrays', function () {
|
||||
// See https://en.cppreference.com/w/cpp/algorithm/ranges/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 hasDuplicates = array => array.some((v, i) => array.indexOf(v) != i);
|
||||
|
||||
describe('Arrays', function () {
|
||||
describe('findUpperBound', function () {
|
||||
context('Even number of elements', function () {
|
||||
const EVEN_ELEMENTS_ARRAY = [11, 12, 13, 14, 15, 16, 17, 18, 19, 20];
|
||||
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 { mock: await ethers.deployContract('Uint256ArraysMock', [array]) };
|
||||
};
|
||||
|
||||
beforeEach(async function () {
|
||||
this.arrays = await Uint256ArraysMock.new(EVEN_ELEMENTS_ARRAY);
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
for (const [name, input] of Object.entries(tests)) {
|
||||
it(name, async function () {
|
||||
// findUpperBound does not support duplicated
|
||||
if (hasDuplicates(array)) this.skip();
|
||||
expect(await this.mock.findUpperBound(input)).to.be.equal(lowerBound(array, input));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('returns correct index for the basic case', async function () {
|
||||
expect(await this.arrays.findUpperBound(16)).to.be.bignumber.equal('5');
|
||||
});
|
||||
|
||||
it('returns 0 for the first element', async function () {
|
||||
expect(await this.arrays.findUpperBound(11)).to.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('returns index of the last element', async function () {
|
||||
expect(await this.arrays.findUpperBound(20)).to.be.bignumber.equal('9');
|
||||
});
|
||||
|
||||
it('returns first index after last element if searched value is over the upper boundary', async function () {
|
||||
expect(await this.arrays.findUpperBound(32)).to.be.bignumber.equal('10');
|
||||
});
|
||||
|
||||
it('returns 0 for the element under the lower boundary', async function () {
|
||||
expect(await this.arrays.findUpperBound(2)).to.be.bignumber.equal('0');
|
||||
});
|
||||
});
|
||||
|
||||
context('Odd number of elements', function () {
|
||||
const ODD_ELEMENTS_ARRAY = [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21];
|
||||
|
||||
beforeEach(async function () {
|
||||
this.arrays = await Uint256ArraysMock.new(ODD_ELEMENTS_ARRAY);
|
||||
});
|
||||
|
||||
it('returns correct index for the basic case', async function () {
|
||||
expect(await this.arrays.findUpperBound(16)).to.be.bignumber.equal('5');
|
||||
});
|
||||
|
||||
it('returns 0 for the first element', async function () {
|
||||
expect(await this.arrays.findUpperBound(11)).to.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('returns index of the last element', async function () {
|
||||
expect(await this.arrays.findUpperBound(21)).to.be.bignumber.equal('10');
|
||||
});
|
||||
|
||||
it('returns first index after last element if searched value is over the upper boundary', async function () {
|
||||
expect(await this.arrays.findUpperBound(32)).to.be.bignumber.equal('11');
|
||||
});
|
||||
|
||||
it('returns 0 for the element under the lower boundary', async function () {
|
||||
expect(await this.arrays.findUpperBound(2)).to.be.bignumber.equal('0');
|
||||
});
|
||||
});
|
||||
|
||||
context('Array with gap', function () {
|
||||
const WITH_GAP_ARRAY = [11, 12, 13, 14, 15, 20, 21, 22, 23, 24];
|
||||
|
||||
beforeEach(async function () {
|
||||
this.arrays = await Uint256ArraysMock.new(WITH_GAP_ARRAY);
|
||||
});
|
||||
|
||||
it('returns index of first element in next filled range', async function () {
|
||||
expect(await this.arrays.findUpperBound(17)).to.be.bignumber.equal('5');
|
||||
});
|
||||
});
|
||||
|
||||
context('Empty array', function () {
|
||||
beforeEach(async function () {
|
||||
this.arrays = await Uint256ArraysMock.new([]);
|
||||
});
|
||||
|
||||
it('always returns 0 for empty array', async function () {
|
||||
expect(await this.arrays.findUpperBound(10)).to.be.bignumber.equal('0');
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('unsafeAccess', function () {
|
||||
for (const { type, artifact, elements } of [
|
||||
{
|
||||
type: 'address',
|
||||
artifact: AddressArraysMock,
|
||||
elements: Array(10)
|
||||
.fill()
|
||||
.map(() => web3.utils.randomHex(20)),
|
||||
},
|
||||
{
|
||||
type: 'bytes32',
|
||||
artifact: Bytes32ArraysMock,
|
||||
elements: Array(10)
|
||||
.fill()
|
||||
.map(() => web3.utils.randomHex(32)),
|
||||
},
|
||||
{
|
||||
type: 'uint256',
|
||||
artifact: Uint256ArraysMock,
|
||||
elements: Array(10)
|
||||
.fill()
|
||||
.map(() => web3.utils.randomHex(32)),
|
||||
},
|
||||
]) {
|
||||
it(type, async function () {
|
||||
const contract = await artifact.new(elements);
|
||||
const contractCases = {
|
||||
address: { artifact: 'AddressArraysMock', elements: randomArray(generators.address, 10) },
|
||||
bytes32: { artifact: 'Bytes32ArraysMock', elements: randomArray(generators.bytes32, 10) },
|
||||
uint256: { artifact: 'Uint256ArraysMock', elements: randomArray(generators.uint256, 10) },
|
||||
};
|
||||
|
||||
const fixture = async () => {
|
||||
const contracts = {};
|
||||
for (const [name, { artifact, elements }] of Object.entries(contractCases)) {
|
||||
contracts[name] = await ethers.deployContract(artifact, [elements]);
|
||||
}
|
||||
return { contracts };
|
||||
};
|
||||
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
for (const [name, { elements }] of Object.entries(contractCases)) {
|
||||
it(name, async function () {
|
||||
for (const i in elements) {
|
||||
expect(await contract.unsafeAccess(i)).to.be.bignumber.equal(elements[i]);
|
||||
expect(await this.contracts[name].unsafeAccess(i)).to.be.equal(elements[i]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,33 +1,29 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
const Base64 = artifacts.require('$Base64');
|
||||
async function fixture() {
|
||||
const mock = await ethers.deployContract('$Base64');
|
||||
return { mock };
|
||||
}
|
||||
|
||||
contract('Strings', function () {
|
||||
describe('Strings', function () {
|
||||
beforeEach(async function () {
|
||||
this.base64 = await Base64.new();
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
describe('from bytes - base64', function () {
|
||||
it('converts to base64 encoded string with double padding', async function () {
|
||||
const TEST_MESSAGE = 'test';
|
||||
const input = web3.utils.asciiToHex(TEST_MESSAGE);
|
||||
expect(await this.base64.$encode(input)).to.equal('dGVzdA==');
|
||||
});
|
||||
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: 'empty bytes', input: '0x', expected: '' },
|
||||
])
|
||||
it(title, async function () {
|
||||
const raw = ethers.isBytesLike(input) ? input : ethers.toUtf8Bytes(input);
|
||||
|
||||
it('converts to base64 encoded string with single padding', async function () {
|
||||
const TEST_MESSAGE = 'test1';
|
||||
const input = web3.utils.asciiToHex(TEST_MESSAGE);
|
||||
expect(await this.base64.$encode(input)).to.equal('dGVzdDE=');
|
||||
});
|
||||
|
||||
it('converts to base64 encoded string without padding', async function () {
|
||||
const TEST_MESSAGE = 'test12';
|
||||
const input = web3.utils.asciiToHex(TEST_MESSAGE);
|
||||
expect(await this.base64.$encode(input)).to.equal('dGVzdDEy');
|
||||
});
|
||||
|
||||
it('empty bytes', async function () {
|
||||
expect(await this.base64.$encode([])).to.equal('');
|
||||
});
|
||||
expect(await this.mock.$encode(raw)).to.equal(ethers.encodeBase64(raw));
|
||||
expect(await this.mock.$encode(raw)).to.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,35 +1,42 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { balance, ether, expectEvent, expectRevert, send } = require('@openzeppelin/test-helpers');
|
||||
const { expectRevertCustomError } = require('../helpers/customError');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
const Create2 = artifacts.require('$Create2');
|
||||
const VestingWallet = artifacts.require('VestingWallet');
|
||||
// This should be a contract that:
|
||||
// - has no constructor arguments
|
||||
// - has no immutable variable populated during construction
|
||||
const ConstructorLessContract = Create2;
|
||||
async function fixture() {
|
||||
const [deployer, other] = await ethers.getSigners();
|
||||
|
||||
contract('Create2', function (accounts) {
|
||||
const [deployerAccount, other] = accounts;
|
||||
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 = web3.utils.soliditySha3(salt);
|
||||
|
||||
const encodedParams = web3.eth.abi.encodeParameters(['address', 'uint64', 'uint64'], [other, 0, 0]).slice(2);
|
||||
|
||||
const constructorByteCode = `${VestingWallet.bytecode}${encodedParams}`;
|
||||
const saltHex = ethers.id(salt);
|
||||
|
||||
beforeEach(async function () {
|
||||
this.factory = await Create2.new();
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
describe('computeAddress', function () {
|
||||
it('computes the correct contract address', async function () {
|
||||
const onChainComputed = await this.factory.$computeAddress(saltHex, web3.utils.keccak256(constructorByteCode));
|
||||
const onChainComputed = await this.factory.$computeAddress(saltHex, ethers.keccak256(this.constructorByteCode));
|
||||
const offChainComputed = ethers.getCreate2Address(
|
||||
this.factory.address,
|
||||
this.factory.target,
|
||||
saltHex,
|
||||
ethers.keccak256(constructorByteCode),
|
||||
ethers.keccak256(this.constructorByteCode),
|
||||
);
|
||||
expect(onChainComputed).to.equal(offChainComputed);
|
||||
});
|
||||
@ -37,13 +44,13 @@ contract('Create2', function (accounts) {
|
||||
it('computes the correct contract address with deployer', async function () {
|
||||
const onChainComputed = await this.factory.$computeAddress(
|
||||
saltHex,
|
||||
web3.utils.keccak256(constructorByteCode),
|
||||
deployerAccount,
|
||||
ethers.keccak256(this.constructorByteCode),
|
||||
ethers.Typed.address(this.deployer),
|
||||
);
|
||||
const offChainComputed = ethers.getCreate2Address(
|
||||
deployerAccount,
|
||||
this.deployer.address,
|
||||
saltHex,
|
||||
ethers.keccak256(constructorByteCode),
|
||||
ethers.keccak256(this.constructorByteCode),
|
||||
);
|
||||
expect(onChainComputed).to.equal(offChainComputed);
|
||||
});
|
||||
@ -52,71 +59,76 @@ contract('Create2', function (accounts) {
|
||||
describe('deploy', function () {
|
||||
it('deploys a contract without constructor', async function () {
|
||||
const offChainComputed = ethers.getCreate2Address(
|
||||
this.factory.address,
|
||||
this.factory.target,
|
||||
saltHex,
|
||||
ethers.keccak256(ConstructorLessContract.bytecode),
|
||||
ethers.keccak256(this.constructorLessBytecode),
|
||||
);
|
||||
|
||||
expectEvent(await this.factory.$deploy(0, saltHex, ConstructorLessContract.bytecode), 'return$deploy', {
|
||||
addr: offChainComputed,
|
||||
});
|
||||
await expect(this.factory.$deploy(0n, saltHex, this.constructorLessBytecode))
|
||||
.to.emit(this.factory, 'return$deploy')
|
||||
.withArgs(offChainComputed);
|
||||
|
||||
expect(ConstructorLessContract.bytecode).to.include((await web3.eth.getCode(offChainComputed)).slice(2));
|
||||
expect(this.constructorLessBytecode).to.include((await web3.eth.getCode(offChainComputed)).slice(2));
|
||||
});
|
||||
|
||||
it('deploys a contract with constructor arguments', async function () {
|
||||
const offChainComputed = ethers.getCreate2Address(
|
||||
this.factory.address,
|
||||
this.factory.target,
|
||||
saltHex,
|
||||
ethers.keccak256(constructorByteCode),
|
||||
ethers.keccak256(this.constructorByteCode),
|
||||
);
|
||||
|
||||
expectEvent(await this.factory.$deploy(0, saltHex, constructorByteCode), 'return$deploy', {
|
||||
addr: offChainComputed,
|
||||
});
|
||||
await expect(this.factory.$deploy(0n, saltHex, this.constructorByteCode))
|
||||
.to.emit(this.factory, 'return$deploy')
|
||||
.withArgs(offChainComputed);
|
||||
|
||||
const instance = await VestingWallet.at(offChainComputed);
|
||||
const instance = await ethers.getContractAt('VestingWallet', offChainComputed);
|
||||
|
||||
expect(await instance.owner()).to.be.equal(other);
|
||||
expect(await instance.owner()).to.equal(this.other.address);
|
||||
});
|
||||
|
||||
it('deploys a contract with funds deposited in the factory', async function () {
|
||||
const deposit = ether('2');
|
||||
await send.ether(deployerAccount, this.factory.address, deposit);
|
||||
expect(await balance.current(this.factory.address)).to.be.bignumber.equal(deposit);
|
||||
const value = 10n;
|
||||
|
||||
await this.deployer.sendTransaction({ to: this.factory, value });
|
||||
|
||||
const offChainComputed = ethers.getCreate2Address(
|
||||
this.factory.address,
|
||||
this.factory.target,
|
||||
saltHex,
|
||||
ethers.keccak256(constructorByteCode),
|
||||
ethers.keccak256(this.constructorByteCode),
|
||||
);
|
||||
|
||||
expectEvent(await this.factory.$deploy(deposit, saltHex, constructorByteCode), 'return$deploy', {
|
||||
addr: offChainComputed,
|
||||
});
|
||||
expect(await ethers.provider.getBalance(this.factory)).to.equal(value);
|
||||
expect(await ethers.provider.getBalance(offChainComputed)).to.equal(0n);
|
||||
|
||||
expect(await balance.current(offChainComputed)).to.be.bignumber.equal(deposit);
|
||||
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 () {
|
||||
expectEvent(await this.factory.$deploy(0, saltHex, constructorByteCode), 'return$deploy');
|
||||
await expect(this.factory.$deploy(0n, saltHex, this.constructorByteCode)).to.emit(this.factory, 'return$deploy');
|
||||
|
||||
// TODO: Make sure it actually throws "Create2FailedDeployment".
|
||||
// For some unknown reason, the revert reason sometimes return:
|
||||
// `revert with unrecognized return data or custom error`
|
||||
await expectRevert.unspecified(this.factory.$deploy(0, saltHex, constructorByteCode));
|
||||
await expect(this.factory.$deploy(0n, saltHex, this.constructorByteCode)).to.be.revertedWithCustomError(
|
||||
this.factory,
|
||||
'Create2FailedDeployment',
|
||||
);
|
||||
});
|
||||
|
||||
it('fails deploying a contract if the bytecode length is zero', async function () {
|
||||
await expectRevertCustomError(this.factory.$deploy(0, saltHex, '0x'), 'Create2EmptyBytecode', []);
|
||||
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 expectRevertCustomError(
|
||||
this.factory.$deploy(1, saltHex, constructorByteCode),
|
||||
'Create2InsufficientBalance',
|
||||
[0, 1],
|
||||
);
|
||||
await expect(this.factory.$deploy(1n, saltHex, this.constructorByteCode))
|
||||
.to.be.revertedWithCustomError(this.factory, 'Create2InsufficientBalance')
|
||||
.withArgs(0n, 1n);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,69 +1,72 @@
|
||||
const { BN } = require('@openzeppelin/test-helpers');
|
||||
const { expectRevertCustomError } = require('../helpers/customError');
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
const ERC20MulticallMock = artifacts.require('$ERC20MulticallMock');
|
||||
async function fixture() {
|
||||
const [holder, alice, bruce] = await ethers.getSigners();
|
||||
|
||||
contract('Multicall', function (accounts) {
|
||||
const [deployer, alice, bob] = accounts;
|
||||
const amount = 12000;
|
||||
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 () {
|
||||
this.multicallToken = await ERC20MulticallMock.new('name', 'symbol');
|
||||
await this.multicallToken.$_mint(deployer, amount);
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
it('batches function calls', async function () {
|
||||
expect(await this.multicallToken.balanceOf(alice)).to.be.bignumber.equal(new BN('0'));
|
||||
expect(await this.multicallToken.balanceOf(bob)).to.be.bignumber.equal(new BN('0'));
|
||||
expect(await this.mock.balanceOf(this.alice)).to.equal(0n);
|
||||
expect(await this.mock.balanceOf(this.bruce)).to.equal(0n);
|
||||
|
||||
await this.multicallToken.multicall(
|
||||
[
|
||||
this.multicallToken.contract.methods.transfer(alice, amount / 2).encodeABI(),
|
||||
this.multicallToken.contract.methods.transfer(bob, amount / 3).encodeABI(),
|
||||
],
|
||||
{ from: deployer },
|
||||
);
|
||||
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.address, this.alice.address, this.amount / 2n)
|
||||
.to.emit(this.mock, 'Transfer')
|
||||
.withArgs(this.holder.address, this.bruce.address, this.amount / 3n);
|
||||
|
||||
expect(await this.multicallToken.balanceOf(alice)).to.be.bignumber.equal(new BN(amount / 2));
|
||||
expect(await this.multicallToken.balanceOf(bob)).to.be.bignumber.equal(new BN(amount / 3));
|
||||
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 () {
|
||||
const MulticallTest = artifacts.require('MulticallTest');
|
||||
const multicallTest = await MulticallTest.new({ from: deployer });
|
||||
await this.multicallToken.transfer(multicallTest.address, amount, { from: deployer });
|
||||
expect(await this.multicallToken.balanceOf(multicallTest.address)).to.be.bignumber.equal(new BN(amount));
|
||||
await this.mock.transfer(this.helper, this.amount);
|
||||
expect(await this.mock.balanceOf(this.helper)).to.equal(this.amount);
|
||||
|
||||
const recipients = [alice, bob];
|
||||
const amounts = [amount / 2, amount / 3].map(n => new BN(n));
|
||||
|
||||
await multicallTest.checkReturnValues(this.multicallToken.address, recipients, amounts);
|
||||
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.multicallToken.balanceOf(alice)).to.be.bignumber.equal(new BN('0'));
|
||||
expect(await this.mock.balanceOf(this.alice)).to.equal(0n);
|
||||
|
||||
const call = this.multicallToken.multicall(
|
||||
[
|
||||
this.multicallToken.contract.methods.transfer(alice, amount).encodeABI(),
|
||||
this.multicallToken.contract.methods.transfer(bob, amount).encodeABI(),
|
||||
],
|
||||
{ from: deployer },
|
||||
);
|
||||
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.address, 0, this.amount);
|
||||
|
||||
await expectRevertCustomError(call, 'ERC20InsufficientBalance', [deployer, 0, amount]);
|
||||
expect(await this.multicallToken.balanceOf(alice)).to.be.bignumber.equal(new BN('0'));
|
||||
expect(await this.mock.balanceOf(this.alice)).to.equal(0n);
|
||||
});
|
||||
|
||||
it('bubbles up revert reasons', async function () {
|
||||
const call = this.multicallToken.multicall(
|
||||
[
|
||||
this.multicallToken.contract.methods.transfer(alice, amount).encodeABI(),
|
||||
this.multicallToken.contract.methods.transfer(bob, amount).encodeABI(),
|
||||
],
|
||||
{ from: deployer },
|
||||
);
|
||||
|
||||
await expectRevertCustomError(call, 'ERC20InsufficientBalance', [deployer, 0, amount]);
|
||||
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.address, 0, this.amount);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,71 +1,75 @@
|
||||
const expectEvent = require('@openzeppelin/test-helpers/src/expectEvent');
|
||||
const { expectRevertCustomError } = require('../helpers/customError');
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
require('@openzeppelin/test-helpers');
|
||||
async function fixture() {
|
||||
const [sender, other] = await ethers.getSigners();
|
||||
|
||||
const Nonces = artifacts.require('$Nonces');
|
||||
const mock = await ethers.deployContract('$Nonces');
|
||||
|
||||
contract('Nonces', function (accounts) {
|
||||
const [sender, other] = accounts;
|
||||
return { sender, other, mock };
|
||||
}
|
||||
|
||||
describe('Nonces', function () {
|
||||
beforeEach(async function () {
|
||||
this.nonces = await Nonces.new();
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
it('gets a nonce', async function () {
|
||||
expect(await this.nonces.nonces(sender)).to.be.bignumber.equal('0');
|
||||
expect(await this.mock.nonces(this.sender)).to.equal(0n);
|
||||
});
|
||||
|
||||
describe('_useNonce', function () {
|
||||
it('increments a nonce', async function () {
|
||||
expect(await this.nonces.nonces(sender)).to.be.bignumber.equal('0');
|
||||
expect(await this.mock.nonces(this.sender)).to.equal(0n);
|
||||
|
||||
const { receipt } = await this.nonces.$_useNonce(sender);
|
||||
expectEvent(receipt, 'return$_useNonce', ['0']);
|
||||
await expect(await this.mock.$_useNonce(this.sender))
|
||||
.to.emit(this.mock, 'return$_useNonce')
|
||||
.withArgs(0n);
|
||||
|
||||
expect(await this.nonces.nonces(sender)).to.be.bignumber.equal('1');
|
||||
expect(await this.mock.nonces(this.sender)).to.equal(1n);
|
||||
});
|
||||
|
||||
it("increments only sender's nonce", async function () {
|
||||
expect(await this.nonces.nonces(sender)).to.be.bignumber.equal('0');
|
||||
expect(await this.nonces.nonces(other)).to.be.bignumber.equal('0');
|
||||
expect(await this.mock.nonces(this.sender)).to.equal(0n);
|
||||
expect(await this.mock.nonces(this.other)).to.equal(0n);
|
||||
|
||||
await this.nonces.$_useNonce(sender);
|
||||
await this.mock.$_useNonce(this.sender);
|
||||
|
||||
expect(await this.nonces.nonces(sender)).to.be.bignumber.equal('1');
|
||||
expect(await this.nonces.nonces(other)).to.be.bignumber.equal('0');
|
||||
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.nonces.nonces(sender);
|
||||
expect(currentNonce).to.be.bignumber.equal('0');
|
||||
const currentNonce = await this.mock.nonces(this.sender);
|
||||
|
||||
await this.nonces.$_useCheckedNonce(sender, currentNonce);
|
||||
expect(currentNonce).to.equal(0n);
|
||||
|
||||
expect(await this.nonces.nonces(sender)).to.be.bignumber.equal('1');
|
||||
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.nonces.nonces(sender);
|
||||
const currentNonce = await this.mock.nonces(this.sender);
|
||||
|
||||
expect(currentNonce).to.be.bignumber.equal('0');
|
||||
expect(await this.nonces.nonces(other)).to.be.bignumber.equal('0');
|
||||
expect(currentNonce).to.equal(0n);
|
||||
expect(await this.mock.nonces(this.other)).to.equal(0n);
|
||||
|
||||
await this.nonces.$_useCheckedNonce(sender, currentNonce);
|
||||
await this.mock.$_useCheckedNonce(this.sender, currentNonce);
|
||||
|
||||
expect(await this.nonces.nonces(sender)).to.be.bignumber.equal('1');
|
||||
expect(await this.nonces.nonces(other)).to.be.bignumber.equal('0');
|
||||
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.nonces.nonces(sender);
|
||||
await expectRevertCustomError(
|
||||
this.nonces.$_useCheckedNonce(sender, currentNonce.addn(1)),
|
||||
'InvalidAccountNonce',
|
||||
[sender, currentNonce],
|
||||
);
|
||||
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.address, currentNonce);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,83 +1,87 @@
|
||||
const { expectEvent } = require('@openzeppelin/test-helpers');
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
const { expectRevertCustomError } = require('../helpers/customError');
|
||||
async function fixture() {
|
||||
const [pauser] = await ethers.getSigners();
|
||||
|
||||
const PausableMock = artifacts.require('PausableMock');
|
||||
const mock = await ethers.deployContract('PausableMock');
|
||||
|
||||
contract('Pausable', function (accounts) {
|
||||
const [pauser] = accounts;
|
||||
return { pauser, mock };
|
||||
}
|
||||
|
||||
describe('Pausable', function () {
|
||||
beforeEach(async function () {
|
||||
this.pausable = await PausableMock.new();
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
context('when unpaused', function () {
|
||||
describe('when unpaused', function () {
|
||||
beforeEach(async function () {
|
||||
expect(await this.pausable.paused()).to.equal(false);
|
||||
expect(await this.mock.paused()).to.be.false;
|
||||
});
|
||||
|
||||
it('can perform normal process in non-pause', async function () {
|
||||
expect(await this.pausable.count()).to.be.bignumber.equal('0');
|
||||
expect(await this.mock.count()).to.equal(0n);
|
||||
|
||||
await this.pausable.normalProcess();
|
||||
expect(await this.pausable.count()).to.be.bignumber.equal('1');
|
||||
await this.mock.normalProcess();
|
||||
expect(await this.mock.count()).to.equal(1n);
|
||||
});
|
||||
|
||||
it('cannot take drastic measure in non-pause', async function () {
|
||||
await expectRevertCustomError(this.pausable.drasticMeasure(), 'ExpectedPause', []);
|
||||
expect(await this.pausable.drasticMeasureTaken()).to.equal(false);
|
||||
await expect(this.mock.drasticMeasure()).to.be.revertedWithCustomError(this.mock, 'ExpectedPause');
|
||||
|
||||
expect(await this.mock.drasticMeasureTaken()).to.be.false;
|
||||
});
|
||||
|
||||
context('when paused', function () {
|
||||
describe('when paused', function () {
|
||||
beforeEach(async function () {
|
||||
this.receipt = await this.pausable.pause({ from: pauser });
|
||||
this.tx = await this.mock.pause();
|
||||
});
|
||||
|
||||
it('emits a Paused event', function () {
|
||||
expectEvent(this.receipt, 'Paused', { account: pauser });
|
||||
it('emits a Paused event', async function () {
|
||||
await expect(this.tx).to.emit(this.mock, 'Paused').withArgs(this.pauser.address);
|
||||
});
|
||||
|
||||
it('cannot perform normal process in pause', async function () {
|
||||
await expectRevertCustomError(this.pausable.normalProcess(), 'EnforcedPause', []);
|
||||
await expect(this.mock.normalProcess()).to.be.revertedWithCustomError(this.mock, 'EnforcedPause');
|
||||
});
|
||||
|
||||
it('can take a drastic measure in a pause', async function () {
|
||||
await this.pausable.drasticMeasure();
|
||||
expect(await this.pausable.drasticMeasureTaken()).to.equal(true);
|
||||
await this.mock.drasticMeasure();
|
||||
expect(await this.mock.drasticMeasureTaken()).to.be.true;
|
||||
});
|
||||
|
||||
it('reverts when re-pausing', async function () {
|
||||
await expectRevertCustomError(this.pausable.pause(), 'EnforcedPause', []);
|
||||
await expect(this.mock.pause()).to.be.revertedWithCustomError(this.mock, 'EnforcedPause');
|
||||
});
|
||||
|
||||
describe('unpausing', function () {
|
||||
it('is unpausable by the pauser', async function () {
|
||||
await this.pausable.unpause();
|
||||
expect(await this.pausable.paused()).to.equal(false);
|
||||
await this.mock.unpause();
|
||||
expect(await this.mock.paused()).to.be.false;
|
||||
});
|
||||
|
||||
context('when unpaused', function () {
|
||||
describe('when unpaused', function () {
|
||||
beforeEach(async function () {
|
||||
this.receipt = await this.pausable.unpause({ from: pauser });
|
||||
this.tx = await this.mock.unpause();
|
||||
});
|
||||
|
||||
it('emits an Unpaused event', function () {
|
||||
expectEvent(this.receipt, 'Unpaused', { account: pauser });
|
||||
it('emits an Unpaused event', async function () {
|
||||
await expect(this.tx).to.emit(this.mock, 'Unpaused').withArgs(this.pauser.address);
|
||||
});
|
||||
|
||||
it('should resume allowing normal process', async function () {
|
||||
expect(await this.pausable.count()).to.be.bignumber.equal('0');
|
||||
await this.pausable.normalProcess();
|
||||
expect(await this.pausable.count()).to.be.bignumber.equal('1');
|
||||
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 expectRevertCustomError(this.pausable.drasticMeasure(), 'ExpectedPause', []);
|
||||
await expect(this.mock.drasticMeasure()).to.be.revertedWithCustomError(this.mock, 'ExpectedPause');
|
||||
});
|
||||
|
||||
it('reverts when re-unpausing', async function () {
|
||||
await expectRevertCustomError(this.pausable.unpause(), 'ExpectedPause', []);
|
||||
await expect(this.mock.unpause()).to.be.revertedWithCustomError(this.mock, 'ExpectedPause');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,44 +1,47 @@
|
||||
const { expectRevert } = require('@openzeppelin/test-helpers');
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
const { expectRevertCustomError } = require('../helpers/customError');
|
||||
async function fixture() {
|
||||
const mock = await ethers.deployContract('ReentrancyMock');
|
||||
return { mock };
|
||||
}
|
||||
|
||||
const ReentrancyMock = artifacts.require('ReentrancyMock');
|
||||
const ReentrancyAttack = artifacts.require('ReentrancyAttack');
|
||||
|
||||
contract('ReentrancyGuard', function () {
|
||||
describe('ReentrancyGuard', function () {
|
||||
beforeEach(async function () {
|
||||
this.reentrancyMock = await ReentrancyMock.new();
|
||||
expect(await this.reentrancyMock.counter()).to.be.bignumber.equal('0');
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
it('nonReentrant function can be called', async function () {
|
||||
expect(await this.reentrancyMock.counter()).to.be.bignumber.equal('0');
|
||||
await this.reentrancyMock.callback();
|
||||
expect(await this.reentrancyMock.counter()).to.be.bignumber.equal('1');
|
||||
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 ReentrancyAttack.new();
|
||||
await expectRevert(this.reentrancyMock.countAndCall(attacker.address), 'ReentrancyAttack: failed call', []);
|
||||
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.reentrancyMock.guardedCheckEntered();
|
||||
await this.mock.guardedCheckEntered();
|
||||
});
|
||||
|
||||
it('_reentrancyGuardEntered should be false when unguarded', async function () {
|
||||
await this.reentrancyMock.unguardedCheckNotEntered();
|
||||
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 expectRevertCustomError(this.reentrancyMock.countLocalRecursive(10), 'ReentrancyGuardReentrantCall', []);
|
||||
await expect(this.mock.countLocalRecursive(10n)).to.be.revertedWithCustomError(
|
||||
this.mock,
|
||||
'ReentrancyGuardReentrantCall',
|
||||
);
|
||||
});
|
||||
|
||||
it('does not allow indirect local recursion', async function () {
|
||||
await expectRevert(this.reentrancyMock.countThisRecursive(10), 'ReentrancyMock: failed call', []);
|
||||
await expect(this.mock.countThisRecursive(10n)).to.be.revertedWith('ReentrancyMock: failed call');
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,19 +1,27 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { expectRevertCustomError } = require('../helpers/customError');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
const ShortStrings = artifacts.require('$ShortStrings');
|
||||
const FALLBACK_SENTINEL = ethers.zeroPadValue('0xFF', 32);
|
||||
|
||||
function length(sstr) {
|
||||
return parseInt(sstr.slice(64), 16);
|
||||
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 };
|
||||
}
|
||||
|
||||
function decode(sstr) {
|
||||
return web3.utils.toUtf8(sstr).slice(0, length(sstr));
|
||||
}
|
||||
|
||||
contract('ShortStrings', function () {
|
||||
before(async function () {
|
||||
this.mock = await ShortStrings.new();
|
||||
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))) {
|
||||
@ -21,34 +29,35 @@ contract('ShortStrings', function () {
|
||||
it('encode / decode', async function () {
|
||||
if (str.length < 32) {
|
||||
const encoded = await this.mock.$toShortString(str);
|
||||
expect(decode(encoded)).to.be.equal(str);
|
||||
expect(encoded).to.equal(encode(str));
|
||||
expect(decode(encoded)).to.equal(str);
|
||||
|
||||
const length = await this.mock.$byteLength(encoded);
|
||||
expect(length.toNumber()).to.be.equal(str.length);
|
||||
|
||||
const decoded = await this.mock.$toString(encoded);
|
||||
expect(decoded).to.be.equal(str);
|
||||
expect(await this.mock.$byteLength(encoded)).to.equal(str.length);
|
||||
expect(await this.mock.$toString(encoded)).to.equal(str);
|
||||
} else {
|
||||
await expectRevertCustomError(this.mock.$toShortString(str), 'StringTooLong', [str]);
|
||||
await expect(this.mock.$toShortString(str))
|
||||
.to.be.revertedWithCustomError(this.mock, 'StringTooLong')
|
||||
.withArgs(str);
|
||||
}
|
||||
});
|
||||
|
||||
it('set / get with fallback', async function () {
|
||||
const { logs } = await this.mock.$toShortStringWithFallback(str, 0);
|
||||
const { ret0 } = logs.find(({ event }) => event == 'return$toShortStringWithFallback').args;
|
||||
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]);
|
||||
|
||||
const promise = this.mock.$toString(ret0);
|
||||
expect(short).to.equal(encode(str));
|
||||
|
||||
const promise = this.mock.$toString(short);
|
||||
if (str.length < 32) {
|
||||
expect(await promise).to.be.equal(str);
|
||||
expect(await promise).to.equal(str);
|
||||
} else {
|
||||
await expectRevertCustomError(promise, 'InvalidShortString', []);
|
||||
await expect(promise).to.be.revertedWithCustomError(this.mock, 'InvalidShortString');
|
||||
}
|
||||
|
||||
const length = await this.mock.$byteLengthWithFallback(ret0, 0);
|
||||
expect(length.toNumber()).to.be.equal(str.length);
|
||||
|
||||
const recovered = await this.mock.$toStringWithFallback(ret0, 0);
|
||||
expect(recovered).to.be.equal(str);
|
||||
expect(await this.mock.$byteLengthWithFallback(short, 0)).to.equal(str.length);
|
||||
expect(await this.mock.$toStringWithFallback(short, 0)).to.equal(str);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,210 +1,74 @@
|
||||
const { constants, BN } = require('@openzeppelin/test-helpers');
|
||||
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
const { generators } = require('../helpers/random');
|
||||
|
||||
const StorageSlotMock = artifacts.require('StorageSlotMock');
|
||||
const slot = ethers.id('some.storage.slot');
|
||||
const otherSlot = ethers.id('some.other.storage.slot');
|
||||
|
||||
const slot = web3.utils.keccak256('some.storage.slot');
|
||||
const otherSlot = web3.utils.keccak256('some.other.storage.slot');
|
||||
async function fixture() {
|
||||
const [account] = await ethers.getSigners();
|
||||
const mock = await ethers.deployContract('StorageSlotMock');
|
||||
return { mock, account };
|
||||
}
|
||||
|
||||
contract('StorageSlot', function (accounts) {
|
||||
describe('StorageSlot', function () {
|
||||
beforeEach(async function () {
|
||||
this.store = await StorageSlotMock.new();
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
describe('boolean storage slot', function () {
|
||||
beforeEach(async function () {
|
||||
this.value = true;
|
||||
});
|
||||
|
||||
it('set', async function () {
|
||||
await this.store.setBoolean(slot, this.value);
|
||||
});
|
||||
|
||||
describe('get', function () {
|
||||
beforeEach(async function () {
|
||||
await this.store.setBoolean(slot, this.value);
|
||||
for (const { type, value, zero } of [
|
||||
{ type: 'Boolean', value: true, zero: false },
|
||||
{ type: 'Address', value: generators.address(), zero: ethers.ZeroAddress },
|
||||
{ type: 'Bytes32', value: generators.bytes32(), zero: ethers.ZeroHash },
|
||||
{ type: 'String', value: 'lorem ipsum', zero: '' },
|
||||
{ type: 'Bytes', value: generators.hexBytes(128), zero: '0x' },
|
||||
]) {
|
||||
describe(`${type} storage slot`, function () {
|
||||
it('set', async function () {
|
||||
await this.mock.getFunction(`set${type}Slot`)(slot, value);
|
||||
});
|
||||
|
||||
it('from right slot', async function () {
|
||||
expect(await this.store.getBoolean(slot)).to.be.equal(this.value);
|
||||
});
|
||||
describe('get', function () {
|
||||
beforeEach(async function () {
|
||||
await this.mock.getFunction(`set${type}Slot`)(slot, value);
|
||||
});
|
||||
|
||||
it('from other slot', async function () {
|
||||
expect(await this.store.getBoolean(otherSlot)).to.be.equal(false);
|
||||
it('from right slot', async function () {
|
||||
expect(await this.mock.getFunction(`get${type}Slot`)(slot)).to.equal(value);
|
||||
});
|
||||
|
||||
it('from other slot', async function () {
|
||||
expect(await this.mock.getFunction(`get${type}Slot`)(otherSlot)).to.equal(zero);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe('address storage slot', function () {
|
||||
beforeEach(async function () {
|
||||
this.value = accounts[1];
|
||||
});
|
||||
|
||||
it('set', async function () {
|
||||
await this.store.setAddress(slot, this.value);
|
||||
});
|
||||
|
||||
describe('get', function () {
|
||||
beforeEach(async function () {
|
||||
await this.store.setAddress(slot, this.value);
|
||||
for (const { type, value, zero } of [
|
||||
{ type: 'String', value: 'lorem ipsum', zero: '' },
|
||||
{ type: 'Bytes', value: generators.hexBytes(128), zero: '0x' },
|
||||
]) {
|
||||
describe(`${type} storage pointer`, function () {
|
||||
it('set', async function () {
|
||||
await this.mock.getFunction(`set${type}Storage`)(slot, value);
|
||||
});
|
||||
|
||||
it('from right slot', async function () {
|
||||
expect(await this.store.getAddress(slot)).to.be.equal(this.value);
|
||||
});
|
||||
describe('get', function () {
|
||||
beforeEach(async function () {
|
||||
await this.mock.getFunction(`set${type}Storage`)(slot, value);
|
||||
});
|
||||
|
||||
it('from other slot', async function () {
|
||||
expect(await this.store.getAddress(otherSlot)).to.be.equal(constants.ZERO_ADDRESS);
|
||||
it('from right slot', async function () {
|
||||
expect(await this.mock.getFunction(`${type.toLowerCase()}Map`)(slot)).to.equal(value);
|
||||
expect(await this.mock.getFunction(`get${type}Storage`)(slot)).to.equal(value);
|
||||
});
|
||||
|
||||
it('from other slot', async function () {
|
||||
expect(await this.mock.getFunction(`${type.toLowerCase()}Map`)(otherSlot)).to.equal(zero);
|
||||
expect(await this.mock.getFunction(`get${type}Storage`)(otherSlot)).to.equal(zero);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('bytes32 storage slot', function () {
|
||||
beforeEach(async function () {
|
||||
this.value = web3.utils.keccak256('some byte32 value');
|
||||
});
|
||||
|
||||
it('set', async function () {
|
||||
await this.store.setBytes32(slot, this.value);
|
||||
});
|
||||
|
||||
describe('get', function () {
|
||||
beforeEach(async function () {
|
||||
await this.store.setBytes32(slot, this.value);
|
||||
});
|
||||
|
||||
it('from right slot', async function () {
|
||||
expect(await this.store.getBytes32(slot)).to.be.equal(this.value);
|
||||
});
|
||||
|
||||
it('from other slot', async function () {
|
||||
expect(await this.store.getBytes32(otherSlot)).to.be.equal(constants.ZERO_BYTES32);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('uint256 storage slot', function () {
|
||||
beforeEach(async function () {
|
||||
this.value = new BN(1742);
|
||||
});
|
||||
|
||||
it('set', async function () {
|
||||
await this.store.setUint256(slot, this.value);
|
||||
});
|
||||
|
||||
describe('get', function () {
|
||||
beforeEach(async function () {
|
||||
await this.store.setUint256(slot, this.value);
|
||||
});
|
||||
|
||||
it('from right slot', async function () {
|
||||
expect(await this.store.getUint256(slot)).to.be.bignumber.equal(this.value);
|
||||
});
|
||||
|
||||
it('from other slot', async function () {
|
||||
expect(await this.store.getUint256(otherSlot)).to.be.bignumber.equal('0');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('string storage slot', function () {
|
||||
beforeEach(async function () {
|
||||
this.value = 'lorem ipsum';
|
||||
});
|
||||
|
||||
it('set', async function () {
|
||||
await this.store.setString(slot, this.value);
|
||||
});
|
||||
|
||||
describe('get', function () {
|
||||
beforeEach(async function () {
|
||||
await this.store.setString(slot, this.value);
|
||||
});
|
||||
|
||||
it('from right slot', async function () {
|
||||
expect(await this.store.getString(slot)).to.be.equal(this.value);
|
||||
});
|
||||
|
||||
it('from other slot', async function () {
|
||||
expect(await this.store.getString(otherSlot)).to.be.equal('');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('string storage pointer', function () {
|
||||
beforeEach(async function () {
|
||||
this.value = 'lorem ipsum';
|
||||
});
|
||||
|
||||
it('set', async function () {
|
||||
await this.store.setStringStorage(slot, this.value);
|
||||
});
|
||||
|
||||
describe('get', function () {
|
||||
beforeEach(async function () {
|
||||
await this.store.setStringStorage(slot, this.value);
|
||||
});
|
||||
|
||||
it('from right slot', async function () {
|
||||
expect(await this.store.stringMap(slot)).to.be.equal(this.value);
|
||||
expect(await this.store.getStringStorage(slot)).to.be.equal(this.value);
|
||||
});
|
||||
|
||||
it('from other slot', async function () {
|
||||
expect(await this.store.stringMap(otherSlot)).to.be.equal('');
|
||||
expect(await this.store.getStringStorage(otherSlot)).to.be.equal('');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('bytes storage slot', function () {
|
||||
beforeEach(async function () {
|
||||
this.value = web3.utils.randomHex(128);
|
||||
});
|
||||
|
||||
it('set', async function () {
|
||||
await this.store.setBytes(slot, this.value);
|
||||
});
|
||||
|
||||
describe('get', function () {
|
||||
beforeEach(async function () {
|
||||
await this.store.setBytes(slot, this.value);
|
||||
});
|
||||
|
||||
it('from right slot', async function () {
|
||||
expect(await this.store.getBytes(slot)).to.be.equal(this.value);
|
||||
});
|
||||
|
||||
it('from other slot', async function () {
|
||||
expect(await this.store.getBytes(otherSlot)).to.be.equal(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('bytes storage pointer', function () {
|
||||
beforeEach(async function () {
|
||||
this.value = web3.utils.randomHex(128);
|
||||
});
|
||||
|
||||
it('set', async function () {
|
||||
await this.store.setBytesStorage(slot, this.value);
|
||||
});
|
||||
|
||||
describe('get', function () {
|
||||
beforeEach(async function () {
|
||||
await this.store.setBytesStorage(slot, this.value);
|
||||
});
|
||||
|
||||
it('from right slot', async function () {
|
||||
expect(await this.store.bytesMap(slot)).to.be.equal(this.value);
|
||||
expect(await this.store.getBytesStorage(slot)).to.be.equal(this.value);
|
||||
});
|
||||
|
||||
it('from other slot', async function () {
|
||||
expect(await this.store.bytesMap(otherSlot)).to.be.equal(null);
|
||||
expect(await this.store.getBytesStorage(otherSlot)).to.be.equal(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,69 +1,71 @@
|
||||
const { BN, constants } = require('@openzeppelin/test-helpers');
|
||||
const { expectRevertCustomError } = require('../helpers/customError');
|
||||
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
const Strings = artifacts.require('$Strings');
|
||||
async function fixture() {
|
||||
const mock = await ethers.deployContract('$Strings');
|
||||
return { mock };
|
||||
}
|
||||
|
||||
contract('Strings', function () {
|
||||
describe('Strings', function () {
|
||||
before(async function () {
|
||||
this.strings = await Strings.new();
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
describe('toString', function () {
|
||||
const values = [
|
||||
'0',
|
||||
'7',
|
||||
'10',
|
||||
'99',
|
||||
'100',
|
||||
'101',
|
||||
'123',
|
||||
'4132',
|
||||
'12345',
|
||||
'1234567',
|
||||
'1234567890',
|
||||
'123456789012345',
|
||||
'12345678901234567890',
|
||||
'123456789012345678901234567890',
|
||||
'1234567890123456789012345678901234567890',
|
||||
'12345678901234567890123456789012345678901234567890',
|
||||
'123456789012345678901234567890123456789012345678901234567890',
|
||||
'1234567890123456789012345678901234567890123456789012345678901234567890',
|
||||
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 = constants.MAX_UINT256;
|
||||
expect(await this.strings.methods['$toString(uint256)'](value)).to.equal(value.toString(10));
|
||||
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.strings.methods['$toString(uint256)'](value)).to.equal(value);
|
||||
expect(await this.mock.$toString(value)).to.equal(value);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('int256', function () {
|
||||
it('converts MAX_INT256', async function () {
|
||||
const value = constants.MAX_INT256;
|
||||
expect(await this.strings.methods['$toStringSigned(int256)'](value)).to.equal(value.toString(10));
|
||||
const value = ethers.MaxInt256;
|
||||
expect(await this.mock.$toStringSigned(value)).to.equal(value.toString(10));
|
||||
});
|
||||
|
||||
it('converts MIN_INT256', async function () {
|
||||
const value = constants.MIN_INT256;
|
||||
expect(await this.strings.methods['$toStringSigned(int256)'](value)).to.equal(value.toString(10));
|
||||
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.strings.methods['$toStringSigned(int256)'](value)).to.equal(value);
|
||||
expect(await this.mock.$toStringSigned(value)).to.equal(value);
|
||||
});
|
||||
|
||||
it(`convert negative ${value}`, async function () {
|
||||
const negated = new BN(value).neg();
|
||||
expect(await this.strings.methods['$toStringSigned(int256)'](negated)).to.equal(negated.toString(10));
|
||||
const negated = -value;
|
||||
expect(await this.mock.$toStringSigned(negated)).to.equal(negated.toString(10));
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -71,39 +73,37 @@ contract('Strings', function () {
|
||||
|
||||
describe('toHexString', function () {
|
||||
it('converts 0', async function () {
|
||||
expect(await this.strings.methods['$toHexString(uint256)'](0)).to.equal('0x00');
|
||||
expect(await this.mock.getFunction('$toHexString(uint256)')(0n)).to.equal('0x00');
|
||||
});
|
||||
|
||||
it('converts a positive number', async function () {
|
||||
expect(await this.strings.methods['$toHexString(uint256)'](0x4132)).to.equal('0x4132');
|
||||
expect(await this.mock.getFunction('$toHexString(uint256)')(0x4132n)).to.equal('0x4132');
|
||||
});
|
||||
|
||||
it('converts MAX_UINT256', async function () {
|
||||
expect(await this.strings.methods['$toHexString(uint256)'](constants.MAX_UINT256)).to.equal(
|
||||
web3.utils.toHex(constants.MAX_UINT256),
|
||||
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.strings.methods['$toHexString(uint256,uint256)'](0x4132, 32)).to.equal(
|
||||
expect(await this.mock.getFunction('$toHexString(uint256,uint256)')(0x4132n, 32n)).to.equal(
|
||||
'0x0000000000000000000000000000000000000000000000000000000000004132',
|
||||
);
|
||||
});
|
||||
|
||||
it('converts a positive number (short)', async function () {
|
||||
const length = 1;
|
||||
await expectRevertCustomError(
|
||||
this.strings.methods['$toHexString(uint256,uint256)'](0x4132, length),
|
||||
`StringsInsufficientHexLength`,
|
||||
[0x4132, length],
|
||||
);
|
||||
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.strings.methods['$toHexString(uint256,uint256)'](constants.MAX_UINT256, 32)).to.equal(
|
||||
web3.utils.toHex(constants.MAX_UINT256),
|
||||
expect(await this.mock.getFunction('$toHexString(uint256,uint256)')(ethers.MaxUint256, 32n)).to.equal(
|
||||
`0x${ethers.MaxUint256.toString(16)}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -111,43 +111,43 @@ contract('Strings', function () {
|
||||
describe('toHexString address', function () {
|
||||
it('converts a random address', async function () {
|
||||
const addr = '0xa9036907dccae6a1e0033479b12e837e5cf5a02f';
|
||||
expect(await this.strings.methods['$toHexString(address)'](addr)).to.equal(addr);
|
||||
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.strings.methods['$toHexString(address)'](addr)).to.equal(addr);
|
||||
expect(await this.mock.getFunction('$toHexString(address)')(addr)).to.equal(addr);
|
||||
});
|
||||
});
|
||||
|
||||
describe('equal', function () {
|
||||
it('compares two empty strings', async function () {
|
||||
expect(await this.strings.methods['$equal(string,string)']('', '')).to.equal(true);
|
||||
expect(await this.mock.$equal('', '')).to.be.true;
|
||||
});
|
||||
|
||||
it('compares two equal strings', async function () {
|
||||
expect(await this.strings.methods['$equal(string,string)']('a', 'a')).to.equal(true);
|
||||
expect(await this.mock.$equal('a', 'a')).to.be.true;
|
||||
});
|
||||
|
||||
it('compares two different strings', async function () {
|
||||
expect(await this.strings.methods['$equal(string,string)']('a', 'b')).to.equal(false);
|
||||
expect(await this.mock.$equal('a', 'b')).to.be.false;
|
||||
});
|
||||
|
||||
it('compares two different strings of different lengths', async function () {
|
||||
expect(await this.strings.methods['$equal(string,string)']('a', 'aa')).to.equal(false);
|
||||
expect(await this.strings.methods['$equal(string,string)']('aa', 'a')).to.equal(false);
|
||||
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.strings.methods['$equal(string,string)'](str1, str2)).to.equal(false);
|
||||
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.strings.methods['$equal(string,string)'](str1, str2)).to.equal(true);
|
||||
expect(await this.mock.$equal(str1, str2)).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user