Refactor time helper and remove custom error helper. (#4803)

Co-authored-by: ernestognw <ernestognw@gmail.com>
This commit is contained in:
Hadrien Croubois
2023-12-22 20:50:25 +01:00
committed by GitHub
parent be0572a8dc
commit 015ef69287
32 changed files with 158 additions and 209 deletions

View File

@ -1,9 +1,7 @@
const {
bigint: { MAX_UINT64 },
} = require('./constants');
const { ethers } = require('hardhat');
const { MAX_UINT64 } = require('./constants');
const { namespaceSlot } = require('./namespaced-storage');
const { bigint: time } = require('./time');
const { keccak256, AbiCoder } = require('ethers');
function buildBaseRoles() {
const roles = {
@ -54,9 +52,8 @@ const CONSUMING_SCHEDULE_STORAGE_SLOT = namespaceSlot('AccessManaged', 0n);
* @requires this.{manager, caller, target, calldata}
*/
async function prepareOperation(manager, { caller, target, calldata, delay }) {
const timestamp = await time.clock.timestamp();
const scheduledAt = timestamp + 1n;
await time.forward.timestamp(scheduledAt, false); // Fix next block timestamp for predictability
const scheduledAt = (await time.clock.timestamp()) + 1n;
await time.increaseTo.timestamp(scheduledAt, false); // Fix next block timestamp for predictability
return {
schedule: () => manager.connect(caller).schedule(target, calldata, scheduledAt + delay),
@ -68,8 +65,8 @@ async function prepareOperation(manager, { caller, target, calldata, delay }) {
const lazyGetAddress = addressable => addressable.address ?? addressable.target ?? addressable;
const hashOperation = (caller, target, data) =>
keccak256(
AbiCoder.defaultAbiCoder().encode(
ethers.keccak256(
ethers.AbiCoder.defaultAbiCoder().encode(
['address', 'address', 'bytes'],
[lazyGetAddress(caller), lazyGetAddress(target), data],
),

View File

@ -1,12 +1,4 @@
// TODO: deprecate the old version in favor of this one
const bigint = {
module.exports = {
MAX_UINT48: 2n ** 48n - 1n,
MAX_UINT64: 2n ** 64n - 1n,
};
// TODO: remove toString() when bigint are supported
module.exports = {
MAX_UINT48: bigint.MAX_UINT48.toString(),
MAX_UINT64: bigint.MAX_UINT64.toString(),
bigint,
};

View File

@ -1,45 +0,0 @@
// DEPRECATED: replace with hardhat-toolbox chai matchers.
const { expect } = require('chai');
/** Revert handler that supports custom errors. */
async function expectRevertCustomError(promise, expectedErrorName, args) {
if (!Array.isArray(args)) {
expect.fail('Expected 3rd array parameter for error arguments');
}
await promise.then(
() => expect.fail("Expected promise to throw but it didn't"),
({ message }) => {
// The revert message for custom errors looks like:
// VM Exception while processing transaction:
// reverted with custom error 'InvalidAccountNonce("0x70997970C51812dc3A010C7d01b50e0d17dc79C8", 0)'
// Attempt to parse as a custom error
const match = message.match(/custom error '(?<name>\w+)\((?<args>.*)\)'/);
if (!match) {
expect.fail(`Could not parse as custom error. ${message}`);
}
// Extract the error name and parameters
const errorName = match.groups.name;
const argMatches = [...match.groups.args.matchAll(/-?\w+/g)];
// Assert error name
expect(errorName).to.be.equal(
expectedErrorName,
`Unexpected custom error name (with found args: [${argMatches.map(([a]) => a)}])`,
);
// Coerce to string for comparison since `arg` can be either a number or hex.
const sanitizedExpected = args.map(arg => arg.toString().toLowerCase());
const sanitizedActual = argMatches.map(([arg]) => arg.toString().toLowerCase());
// Assert argument equality
expect(sanitizedActual).to.have.members(sanitizedExpected, `Unexpected ${errorName} arguments`);
},
);
}
module.exports = {
expectRevertCustomError,
};

View File

@ -1,7 +1,7 @@
const { ethers } = require('hardhat');
const { forward } = require('./time');
const { ProposalState } = require('./enums');
const { unique } = require('./iterate');
const time = require('./time');
const timelockSalt = (address, descriptionHash) =>
ethers.toBeHex((ethers.toBigInt(address) << 96n) ^ ethers.toBigInt(descriptionHash), 32);
@ -131,17 +131,17 @@ class GovernorHelper {
/// Clock helpers
async waitForSnapshot(offset = 0n) {
const timepoint = await this.governor.proposalSnapshot(this.id);
return forward[this.mode](timepoint + offset);
return time.increaseTo[this.mode](timepoint + offset);
}
async waitForDeadline(offset = 0n) {
const timepoint = await this.governor.proposalDeadline(this.id);
return forward[this.mode](timepoint + offset);
return time.increaseTo[this.mode](timepoint + offset);
}
async waitForEta(offset = 0n) {
const timestamp = await this.governor.proposalEta(this.id);
return forward.timestamp(timestamp + offset);
return time.increaseTo.timestamp(timestamp + offset);
}
/// Other helpers

View File

@ -1,21 +1,15 @@
const { keccak256, id, toBeHex, MaxUint256 } = require('ethers');
const { artifacts } = require('hardhat');
const { ethers, artifacts } = require('hardhat');
const { erc7201slot } = require('./erc1967');
function namespaceId(contractName) {
return `openzeppelin.storage.${contractName}`;
}
function namespaceLocation(value) {
const hashIdBN = BigInt(id(value)) - 1n; // keccak256(id) - 1
const mask = MaxUint256 - 0xffn; // ~0xff
return BigInt(keccak256(toBeHex(hashIdBN, 32))) & mask;
}
function namespaceSlot(contractName, offset) {
try {
// Try to get the artifact paths, will throw if it doesn't exist
artifacts._getArtifactPathSync(`${contractName}Upgradeable`);
return offset + namespaceLocation(namespaceId(contractName));
return offset + ethers.toBigInt(erc7201slot(namespaceId(contractName)));
} catch (_) {
return offset;
}
@ -23,6 +17,6 @@ function namespaceSlot(contractName, offset) {
module.exports = {
namespaceSlot,
namespaceLocation,
namespaceLocation: erc7201slot,
namespaceId,
};

View File

@ -1,27 +1,39 @@
const { ethers } = require('hardhat');
const { time, mineUpTo } = require('@nomicfoundation/hardhat-network-helpers');
const { time, mine, mineUpTo } = require('@nomicfoundation/hardhat-network-helpers');
const { mapValues } = require('./iterate');
const clock = {
blocknumber: () => time.latestBlock(),
timestamp: () => time.latest(),
};
const clockFromReceipt = {
blocknumber: receipt => Promise.resolve(receipt.blockNumber),
timestamp: receipt => ethers.provider.getBlock(receipt.blockNumber).then(block => block.timestamp),
};
const increaseBy = {
blockNumber: mine,
timestamp: (delay, mine = true) =>
time.latest().then(clock => increaseTo.timestamp(clock + ethers.toNumber(delay), mine)),
};
const increaseTo = {
blocknumber: mineUpTo,
timestamp: (to, mine = true) => (mine ? time.increaseTo(to) : time.setNextBlockTimestamp(to)),
};
const duration = time.duration;
module.exports = {
clock: {
blocknumber: () => time.latestBlock(),
timestamp: () => time.latest(),
},
clockFromReceipt: {
blocknumber: receipt => Promise.resolve(receipt.blockNumber),
timestamp: receipt => ethers.provider.getBlock(receipt.blockNumber).then(block => block.timestamp),
},
forward: {
blocknumber: mineUpTo,
timestamp: (to, mine = true) => (mine ? time.increaseTo(to) : time.setNextBlockTimestamp(to)),
},
duration: time.duration,
clock,
clockFromReceipt,
increaseBy,
increaseTo,
duration,
};
// TODO: deprecate the old version in favor of this one
module.exports.bigint = {
clock: mapValues(module.exports.clock, fn => () => fn().then(ethers.toBigInt)),
clockFromReceipt: mapValues(module.exports.clockFromReceipt, fn => receipt => fn(receipt).then(ethers.toBigInt)),
forward: module.exports.forward,
duration: mapValues(module.exports.duration, fn => n => ethers.toBigInt(fn(ethers.toNumber(n)))),
clock: mapValues(clock, fn => () => fn().then(ethers.toBigInt)),
clockFromReceipt: mapValues(clockFromReceipt, fn => receipt => fn(receipt).then(ethers.toBigInt)),
increaseBy: increaseBy,
increaseTo: increaseTo,
duration: mapValues(duration, fn => n => ethers.toBigInt(fn(ethers.toNumber(n)))),
};