diff --git a/contracts/mocks/MulticallTest.sol b/contracts/mocks/MulticallHelper.sol similarity index 96% rename from contracts/mocks/MulticallTest.sol rename to contracts/mocks/MulticallHelper.sol index 74be7d8b4..d70f3bf4e 100644 --- a/contracts/mocks/MulticallTest.sol +++ b/contracts/mocks/MulticallHelper.sol @@ -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, diff --git a/contracts/mocks/StorageSlotMock.sol b/contracts/mocks/StorageSlotMock.sol index dbdad7a2a..36f0f5af0 100644 --- a/contracts/mocks/StorageSlotMock.sol +++ b/contracts/mocks/StorageSlotMock.sol @@ -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; } diff --git a/test/helpers/random.js b/test/helpers/random.js index 883667fa0..c1d02f261 100644 --- a/test/helpers/random.js +++ b/test/helpers/random.js @@ -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 = { diff --git a/test/utils/Arrays.test.js b/test/utils/Arrays.test.js index d939d59bd..375b9422f 100644 --- a/test/utils/Arrays.test.js +++ b/test/utils/Arrays.test.js @@ -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]); } }); } diff --git a/test/utils/Base64.test.js b/test/utils/Base64.test.js index dfff0b0d0..4707db0c3 100644 --- a/test/utils/Base64.test.js +++ b/test/utils/Base64.test.js @@ -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); + }); }); }); diff --git a/test/utils/Create2.test.js b/test/utils/Create2.test.js index a4ad99237..df807e757 100644 --- a/test/utils/Create2.test.js +++ b/test/utils/Create2.test.js @@ -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); }); }); }); diff --git a/test/utils/Multicall.test.js b/test/utils/Multicall.test.js index 65443cd0a..7ec7e20ce 100644 --- a/test/utils/Multicall.test.js +++ b/test/utils/Multicall.test.js @@ -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); }); }); diff --git a/test/utils/Nonces.test.js b/test/utils/Nonces.test.js index 67a3087e3..18d20defb 100644 --- a/test/utils/Nonces.test.js +++ b/test/utils/Nonces.test.js @@ -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); }); }); }); diff --git a/test/utils/Pausable.test.js b/test/utils/Pausable.test.js index e60a62c74..de46bc46b 100644 --- a/test/utils/Pausable.test.js +++ b/test/utils/Pausable.test.js @@ -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'); }); }); }); diff --git a/test/utils/ReentrancyGuard.test.js b/test/utils/ReentrancyGuard.test.js index 15355c098..871967e2f 100644 --- a/test/utils/ReentrancyGuard.test.js +++ b/test/utils/ReentrancyGuard.test.js @@ -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'); }); }); diff --git a/test/utils/ShortStrings.test.js b/test/utils/ShortStrings.test.js index 189281d38..cb1a06aa5 100644 --- a/test/utils/ShortStrings.test.js +++ b/test/utils/ShortStrings.test.js @@ -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); }); }); } diff --git a/test/utils/StorageSlot.test.js b/test/utils/StorageSlot.test.js index 846512ed2..ab237b700 100644 --- a/test/utils/StorageSlot.test.js +++ b/test/utils/StorageSlot.test.js @@ -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); - }); - }); - }); + } }); diff --git a/test/utils/Strings.test.js b/test/utils/Strings.test.js index 2435fc71c..643172bcb 100644 --- a/test/utils/Strings.test.js +++ b/test/utils/Strings.test.js @@ -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; }); }); });