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:
Renan Souza
2023-11-24 01:32:30 +00:00
committed by GitHub
parent 330c39b662
commit 78d5708340
13 changed files with 497 additions and 602 deletions

View File

@ -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,

View File

@ -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;
}

View File

@ -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 = {

View File

@ -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]);
}
});
}

View File

@ -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);
});
});
});

View File

@ -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);
});
});
});

View File

@ -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);
});
});

View File

@ -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);
});
});
});

View File

@ -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');
});
});
});

View File

@ -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');
});
});

View File

@ -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);
});
});
}

View File

@ -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);
});
});
});
}
});

View File

@ -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;
});
});
});