Merge branch 'master' into feat/access-manager

This commit is contained in:
Francisco Giordano
2023-08-07 01:28:11 -03:00
618 changed files with 17824 additions and 19585 deletions

14
test/helpers/account.js Normal file
View File

@ -0,0 +1,14 @@
const { web3 } = require('hardhat');
const { impersonateAccount, setBalance } = require('@nomicfoundation/hardhat-network-helpers');
// Hardhat default balance
const DEFAULT_BALANCE = web3.utils.toBN('10000000000000000000000');
async function impersonate(account, balance = DEFAULT_BALANCE) {
await impersonateAccount(account);
await setBalance(account, balance);
}
module.exports = {
impersonate,
};

22
test/helpers/create.js Normal file
View File

@ -0,0 +1,22 @@
const RLP = require('rlp');
function computeCreateAddress(deployer, nonce) {
return web3.utils.toChecksumAddress(web3.utils.sha3(RLP.encode([deployer.address ?? deployer, nonce])).slice(-40));
}
function computeCreate2Address(saltHex, bytecode, deployer) {
return web3.utils.toChecksumAddress(
web3.utils
.sha3(
`0x${['ff', deployer.address ?? deployer, saltHex, web3.utils.soliditySha3(bytecode)]
.map(x => x.replace(/0x/, ''))
.join('')}`,
)
.slice(-40),
);
}
module.exports = {
computeCreateAddress,
computeCreate2Address,
};

View File

@ -1,11 +0,0 @@
function computeCreate2Address(saltHex, bytecode, deployer) {
return web3.utils.toChecksumAddress(
`0x${web3.utils
.sha3(`0x${['ff', deployer, saltHex, web3.utils.soliditySha3(bytecode)].map(x => x.replace(/0x/, '')).join('')}`)
.slice(-40)}`,
);
}
module.exports = {
computeCreate2Address,
};

View File

@ -1,61 +0,0 @@
const { promisify } = require('util');
const BridgeAMBMock = artifacts.require('BridgeAMBMock');
const BridgeArbitrumL1Mock = artifacts.require('BridgeArbitrumL1Mock');
const BridgeArbitrumL2Mock = artifacts.require('BridgeArbitrumL2Mock');
const BridgeOptimismMock = artifacts.require('BridgeOptimismMock');
const BridgePolygonChildMock = artifacts.require('BridgePolygonChildMock');
class BridgeHelper {
static async deploy(type) {
return new BridgeHelper(await deployBridge(type));
}
constructor(bridge) {
this.bridge = bridge;
this.address = bridge.address;
}
call(from, target, selector = undefined, args = []) {
return this.bridge.relayAs(
target.address || target,
selector ? target.contract.methods[selector](...args).encodeABI() : '0x',
from,
);
}
}
async function deployBridge(type = 'Arbitrum-L2') {
switch (type) {
case 'AMB':
return BridgeAMBMock.new();
case 'Arbitrum-L1':
return BridgeArbitrumL1Mock.new();
case 'Arbitrum-L2': {
const instance = await BridgeArbitrumL2Mock.new();
const code = await web3.eth.getCode(instance.address);
await promisify(web3.currentProvider.send.bind(web3.currentProvider))({
jsonrpc: '2.0',
method: 'hardhat_setCode',
params: ['0x0000000000000000000000000000000000000064', code],
id: new Date().getTime(),
});
return BridgeArbitrumL2Mock.at('0x0000000000000000000000000000000000000064');
}
case 'Optimism':
return BridgeOptimismMock.new();
case 'Polygon-Child':
return BridgePolygonChildMock.new();
default:
throw new Error(`CrossChain: ${type} is not supported`);
}
}
module.exports = {
BridgeHelper,
};

View File

@ -1,22 +1,41 @@
const { config } = require('hardhat');
const optimizationsEnabled = config.solidity.compilers.some(c => c.settings.optimizer.enabled);
const { expect } = require('chai');
/** Revert handler that supports custom errors. */
async function expectRevertCustomError(promise, reason) {
try {
await promise;
expect.fail("Expected promise to throw but it didn't");
} catch (revert) {
if (reason) {
if (optimizationsEnabled) {
// Optimizations currently mess with Hardhat's decoding of custom errors
expect(revert.message).to.include.oneOf([reason, 'unrecognized return data or custom error']);
} else {
expect(revert.message).to.include(reason);
}
}
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 = {

View File

@ -1,13 +1,12 @@
const { BN } = require('@openzeppelin/test-helpers');
function Enum(...options) {
return Object.fromEntries(options.map((key, i) => [key, new BN(i)]));
return Object.fromEntries(options.map((key, i) => [key, web3.utils.toBN(i)]));
}
module.exports = {
Enum,
ProposalState: Enum('Pending', 'Active', 'Canceled', 'Defeated', 'Succeeded', 'Queued', 'Expired', 'Executed'),
VoteType: Enum('Against', 'For', 'Abstain'),
Rounding: Enum('Down', 'Up', 'Zero'),
Rounding: Enum('Floor', 'Ceil', 'Trunc', 'Expand'),
AccessMode: Enum('Custom', 'Closed', 'Open'),
OperationState: Enum('Unset', 'Waiting', 'Ready', 'Done'),
};

View File

@ -1,3 +1,5 @@
const { getStorageAt, setStorageAt } = require('@nomicfoundation/hardhat-network-helpers');
const ImplementationLabel = 'eip1967.proxy.implementation';
const AdminLabel = 'eip1967.proxy.admin';
const BeaconLabel = 'eip1967.proxy.beacon';
@ -7,12 +9,27 @@ function labelToSlot(label) {
}
function getSlot(address, slot) {
return web3.eth.getStorageAt(
return getStorageAt(
web3.utils.isAddress(address) ? address : address.address,
web3.utils.isHex(slot) ? slot : labelToSlot(slot),
);
}
function setSlot(address, slot, value) {
const hexValue = web3.utils.isHex(value) ? value : web3.utils.toHex(value);
return setStorageAt(
web3.utils.isAddress(address) ? address : address.address,
web3.utils.isHex(slot) ? slot : labelToSlot(slot),
web3.utils.padLeft(hexValue, 64),
);
}
async function getAddressInSlot(address, slot) {
const slotValue = await getSlot(address, slot);
return web3.utils.toChecksumAddress(slotValue.substring(slotValue.length - 40));
}
module.exports = {
ImplementationLabel,
AdminLabel,
@ -20,5 +37,7 @@ module.exports = {
ImplementationSlot: labelToSlot(ImplementationLabel),
AdminSlot: labelToSlot(AdminLabel),
BeaconSlot: labelToSlot(BeaconLabel),
setSlot,
getSlot,
getAddressInSlot,
};

View File

@ -1,4 +1,6 @@
const { web3 } = require('hardhat');
const { forward } = require('../helpers/time');
const { ProposalState } = require('./enums');
function zip(...args) {
return Array(Math.max(...args.map(array => array.length)))
@ -14,6 +16,9 @@ function concatOpts(args, opts = null) {
return opts ? args.concat(opts) : args;
}
const timelockSalt = (address, descriptionHash) =>
'0x' + web3.utils.toBN(address).shln(96).xor(web3.utils.toBN(descriptionHash)).toString(16, 64);
class GovernorHelper {
constructor(governor, mode = 'blocknumber') {
this.governor = governor;
@ -80,7 +85,7 @@ class GovernorHelper {
...concatOpts(proposal.shortProposal, opts),
);
default:
throw new Error(`unsuported visibility "${visibility}"`);
throw new Error(`unsupported visibility "${visibility}"`);
}
}
@ -90,26 +95,17 @@ class GovernorHelper {
return vote.signature
? // if signature, and either params or reason →
vote.params || vote.reason
? vote
.signature(this.governor, {
proposalId: proposal.id,
support: vote.support,
reason: vote.reason || '',
params: vote.params || '',
})
.then(({ v, r, s }) =>
this.governor.castVoteWithReasonAndParamsBySig(
...concatOpts([proposal.id, vote.support, vote.reason || '', vote.params || '', v, r, s], opts),
? this.sign(vote).then(signature =>
this.governor.castVoteWithReasonAndParamsBySig(
...concatOpts(
[proposal.id, vote.support, vote.voter, vote.reason || '', vote.params || '', signature],
opts,
),
)
: vote
.signature(this.governor, {
proposalId: proposal.id,
support: vote.support,
})
.then(({ v, r, s }) =>
this.governor.castVoteBySig(...concatOpts([proposal.id, vote.support, v, r, s], opts)),
)
),
)
: this.sign(vote).then(signature =>
this.governor.castVoteBySig(...concatOpts([proposal.id, vote.support, vote.voter, signature], opts)),
)
: vote.params
? // otherwise if params
this.governor.castVoteWithReasonAndParams(
@ -121,6 +117,23 @@ class GovernorHelper {
: this.governor.castVote(...concatOpts([proposal.id, vote.support], opts));
}
sign(vote = {}) {
return vote.signature(this.governor, this.forgeMessage(vote));
}
forgeMessage(vote = {}) {
const proposal = this.currentProposal;
const message = { proposalId: proposal.id, support: vote.support, voter: vote.voter, nonce: vote.nonce };
if (vote.params || vote.reason) {
message.reason = vote.reason || '';
message.params = vote.params || '';
}
return message;
}
async waitForSnapshot(offset = 0) {
const proposal = this.currentProposal;
const timepoint = await this.governor.proposalSnapshot(proposal.id);
@ -196,6 +209,45 @@ class GovernorHelper {
}
}
/**
* Encodes a list ProposalStates into a bytes32 representation where each bit enabled corresponds to
* the underlying position in the `ProposalState` enum. For example:
*
* 0x000...10000
* ^^^^^^------ ...
* ^----- Succeeded
* ^---- Defeated
* ^--- Canceled
* ^-- Active
* ^- Pending
*/
function proposalStatesToBitMap(proposalStates, options = {}) {
if (!Array.isArray(proposalStates)) {
proposalStates = [proposalStates];
}
const statesCount = Object.keys(ProposalState).length;
let result = 0;
const uniqueProposalStates = new Set(proposalStates.map(bn => bn.toNumber())); // Remove duplicates
for (const state of uniqueProposalStates) {
if (state < 0 || state >= statesCount) {
expect.fail(`ProposalState ${state} out of possible states (0...${statesCount}-1)`);
} else {
result |= 1 << state;
}
}
if (options.inverted) {
const mask = 2 ** statesCount - 1;
result = result ^ mask;
}
const hex = web3.utils.numberToHex(result);
return web3.utils.padLeft(hex, 64);
}
module.exports = {
GovernorHelper,
proposalStatesToBitMap,
timelockSalt,
};

View File

@ -1,5 +1,5 @@
function mapValues(obj, fn) {
return Object.fromEntries([...Object.entries(obj)].map(([k, v]) => [k, fn(v)]));
return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, fn(v)]));
}
module.exports = {

11
test/helpers/math.js Normal file
View File

@ -0,0 +1,11 @@
module.exports = {
// sum of integer / bignumber
sum: (...args) => args.reduce((acc, n) => acc + n, 0),
BNsum: (...args) => args.reduce((acc, n) => acc.add(n), web3.utils.toBN(0)),
// min of integer / bignumber
min: (...args) => args.slice(1).reduce((x, y) => (x < y ? x : y), args[0]),
BNmin: (...args) => args.slice(1).reduce((x, y) => (x.lt(y) ? x : y), args[0]),
// max of integer / bignumber
max: (...args) => args.slice(1).reduce((x, y) => (x > y ? x : y), args[0]),
BNmax: (...args) => args.slice(1).reduce((x, y) => (x.gt(y) ? x : y), args[0]),
};