dexorder
This commit is contained in:
85
lib_openzeppelin_contracts/test/helpers/access-manager.js
Normal file
85
lib_openzeppelin_contracts/test/helpers/access-manager.js
Normal file
@@ -0,0 +1,85 @@
|
||||
const { ethers } = require('hardhat');
|
||||
|
||||
const { MAX_UINT64 } = require('./constants');
|
||||
const time = require('./time');
|
||||
const { upgradeableSlot } = require('./storage');
|
||||
|
||||
function buildBaseRoles() {
|
||||
const roles = {
|
||||
ADMIN: {
|
||||
id: 0n,
|
||||
},
|
||||
SOME_ADMIN: {
|
||||
id: 17n,
|
||||
},
|
||||
SOME_GUARDIAN: {
|
||||
id: 35n,
|
||||
},
|
||||
SOME: {
|
||||
id: 42n,
|
||||
},
|
||||
PUBLIC: {
|
||||
id: MAX_UINT64,
|
||||
},
|
||||
};
|
||||
|
||||
// Names
|
||||
Object.entries(roles).forEach(([name, role]) => (role.name = name));
|
||||
|
||||
// Defaults
|
||||
for (const role of Object.keys(roles)) {
|
||||
roles[role].admin = roles.ADMIN;
|
||||
roles[role].guardian = roles.ADMIN;
|
||||
}
|
||||
|
||||
// Admins
|
||||
roles.SOME.admin = roles.SOME_ADMIN;
|
||||
|
||||
// Guardians
|
||||
roles.SOME.guardian = roles.SOME_GUARDIAN;
|
||||
|
||||
return roles;
|
||||
}
|
||||
|
||||
const formatAccess = access => [access[0], access[1].toString()];
|
||||
|
||||
const MINSETBACK = time.duration.days(5);
|
||||
const EXPIRATION = time.duration.weeks(1);
|
||||
|
||||
const EXECUTION_ID_STORAGE_SLOT = upgradeableSlot('AccessManager', 3n);
|
||||
const CONSUMING_SCHEDULE_STORAGE_SLOT = upgradeableSlot('AccessManaged', 0n);
|
||||
|
||||
/**
|
||||
* @requires this.{manager, caller, target, calldata}
|
||||
*/
|
||||
async function prepareOperation(manager, { caller, target, calldata, delay }) {
|
||||
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),
|
||||
scheduledAt,
|
||||
operationId: hashOperation(caller, target, calldata),
|
||||
};
|
||||
}
|
||||
|
||||
const lazyGetAddress = addressable => addressable.address ?? addressable.target ?? addressable;
|
||||
|
||||
const hashOperation = (caller, target, data) =>
|
||||
ethers.keccak256(
|
||||
ethers.AbiCoder.defaultAbiCoder().encode(
|
||||
['address', 'address', 'bytes'],
|
||||
[lazyGetAddress(caller), lazyGetAddress(target), data],
|
||||
),
|
||||
);
|
||||
|
||||
module.exports = {
|
||||
buildBaseRoles,
|
||||
formatAccess,
|
||||
MINSETBACK,
|
||||
EXPIRATION,
|
||||
EXECUTION_ID_STORAGE_SLOT,
|
||||
CONSUMING_SCHEDULE_STORAGE_SLOT,
|
||||
prepareOperation,
|
||||
hashOperation,
|
||||
};
|
||||
14
lib_openzeppelin_contracts/test/helpers/account.js
Normal file
14
lib_openzeppelin_contracts/test/helpers/account.js
Normal file
@@ -0,0 +1,14 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { impersonateAccount, setBalance } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
// Hardhat default balance
|
||||
const DEFAULT_BALANCE = 10000n * ethers.WeiPerEther;
|
||||
|
||||
const impersonate = (account, balance = DEFAULT_BALANCE) =>
|
||||
impersonateAccount(account)
|
||||
.then(() => setBalance(account, balance))
|
||||
.then(() => ethers.getSigner(account));
|
||||
|
||||
module.exports = {
|
||||
impersonate,
|
||||
};
|
||||
4
lib_openzeppelin_contracts/test/helpers/constants.js
Normal file
4
lib_openzeppelin_contracts/test/helpers/constants.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
MAX_UINT48: 2n ** 48n - 1n,
|
||||
MAX_UINT64: 2n ** 64n - 1n,
|
||||
};
|
||||
52
lib_openzeppelin_contracts/test/helpers/eip712-types.js
Normal file
52
lib_openzeppelin_contracts/test/helpers/eip712-types.js
Normal file
@@ -0,0 +1,52 @@
|
||||
const { mapValues } = require('./iterate');
|
||||
|
||||
const formatType = schema => Object.entries(schema).map(([name, type]) => ({ name, type }));
|
||||
|
||||
module.exports = mapValues(
|
||||
{
|
||||
EIP712Domain: {
|
||||
name: 'string',
|
||||
version: 'string',
|
||||
chainId: 'uint256',
|
||||
verifyingContract: 'address',
|
||||
salt: 'bytes32',
|
||||
},
|
||||
Permit: {
|
||||
owner: 'address',
|
||||
spender: 'address',
|
||||
value: 'uint256',
|
||||
nonce: 'uint256',
|
||||
deadline: 'uint256',
|
||||
},
|
||||
Ballot: {
|
||||
proposalId: 'uint256',
|
||||
support: 'uint8',
|
||||
voter: 'address',
|
||||
nonce: 'uint256',
|
||||
},
|
||||
ExtendedBallot: {
|
||||
proposalId: 'uint256',
|
||||
support: 'uint8',
|
||||
voter: 'address',
|
||||
nonce: 'uint256',
|
||||
reason: 'string',
|
||||
params: 'bytes',
|
||||
},
|
||||
Delegation: {
|
||||
delegatee: 'address',
|
||||
nonce: 'uint256',
|
||||
expiry: 'uint256',
|
||||
},
|
||||
ForwardRequest: {
|
||||
from: 'address',
|
||||
to: 'address',
|
||||
value: 'uint256',
|
||||
gas: 'uint256',
|
||||
nonce: 'uint256',
|
||||
deadline: 'uint48',
|
||||
data: 'bytes',
|
||||
},
|
||||
},
|
||||
formatType,
|
||||
);
|
||||
module.exports.formatType = formatType;
|
||||
45
lib_openzeppelin_contracts/test/helpers/eip712.js
Normal file
45
lib_openzeppelin_contracts/test/helpers/eip712.js
Normal file
@@ -0,0 +1,45 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const types = require('./eip712-types');
|
||||
|
||||
async function getDomain(contract) {
|
||||
const { fields, name, version, chainId, verifyingContract, salt, extensions } = await contract.eip712Domain();
|
||||
|
||||
if (extensions.length > 0) {
|
||||
throw Error('Extensions not implemented');
|
||||
}
|
||||
|
||||
const domain = {
|
||||
name,
|
||||
version,
|
||||
chainId,
|
||||
verifyingContract,
|
||||
salt,
|
||||
};
|
||||
|
||||
for (const [i, { name }] of types.EIP712Domain.entries()) {
|
||||
if (!(fields & (1 << i))) {
|
||||
delete domain[name];
|
||||
}
|
||||
}
|
||||
|
||||
return domain;
|
||||
}
|
||||
|
||||
function domainType(domain) {
|
||||
return types.EIP712Domain.filter(({ name }) => domain[name] !== undefined);
|
||||
}
|
||||
|
||||
function hashTypedData(domain, structHash) {
|
||||
return ethers.solidityPackedKeccak256(
|
||||
['bytes', 'bytes32', 'bytes32'],
|
||||
['0x1901', ethers.TypedDataEncoder.hashDomain(domain), structHash],
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getDomain,
|
||||
domainType,
|
||||
domainSeparator: ethers.TypedDataEncoder.hashDomain,
|
||||
hashTypedData,
|
||||
...types,
|
||||
};
|
||||
12
lib_openzeppelin_contracts/test/helpers/enums.js
Normal file
12
lib_openzeppelin_contracts/test/helpers/enums.js
Normal file
@@ -0,0 +1,12 @@
|
||||
function Enum(...options) {
|
||||
return Object.fromEntries(options.map((key, i) => [key, BigInt(i)]));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Enum,
|
||||
ProposalState: Enum('Pending', 'Active', 'Canceled', 'Defeated', 'Succeeded', 'Queued', 'Expired', 'Executed'),
|
||||
VoteType: Enum('Against', 'For', 'Abstain'),
|
||||
Rounding: Enum('Floor', 'Ceil', 'Trunc', 'Expand'),
|
||||
OperationState: Enum('Unset', 'Waiting', 'Ready', 'Done'),
|
||||
RevertType: Enum('None', 'RevertWithoutMessage', 'RevertWithMessage', 'RevertWithCustomError', 'Panic'),
|
||||
};
|
||||
198
lib_openzeppelin_contracts/test/helpers/governance.js
Normal file
198
lib_openzeppelin_contracts/test/helpers/governance.js
Normal file
@@ -0,0 +1,198 @@
|
||||
const { ethers } = require('hardhat');
|
||||
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);
|
||||
|
||||
class GovernorHelper {
|
||||
constructor(governor, mode = 'blocknumber') {
|
||||
this.governor = governor;
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
connect(account) {
|
||||
this.governor = this.governor.connect(account);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Setter and getters
|
||||
/**
|
||||
* Specify a proposal either as
|
||||
* 1) an array of objects [{ target, value, data }]
|
||||
* 2) an object of arrays { targets: [], values: [], data: [] }
|
||||
*/
|
||||
setProposal(actions, description) {
|
||||
if (Array.isArray(actions)) {
|
||||
this.targets = actions.map(a => a.target);
|
||||
this.values = actions.map(a => a.value || 0n);
|
||||
this.data = actions.map(a => a.data || '0x');
|
||||
} else {
|
||||
({ targets: this.targets, values: this.values, data: this.data } = actions);
|
||||
}
|
||||
this.description = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
get id() {
|
||||
return ethers.keccak256(
|
||||
ethers.AbiCoder.defaultAbiCoder().encode(['address[]', 'uint256[]', 'bytes[]', 'bytes32'], this.shortProposal),
|
||||
);
|
||||
}
|
||||
|
||||
// used for checking events
|
||||
get signatures() {
|
||||
return this.data.map(() => '');
|
||||
}
|
||||
|
||||
get descriptionHash() {
|
||||
return ethers.id(this.description);
|
||||
}
|
||||
|
||||
// condensed version for queueing end executing
|
||||
get shortProposal() {
|
||||
return [this.targets, this.values, this.data, this.descriptionHash];
|
||||
}
|
||||
|
||||
// full version for proposing
|
||||
get fullProposal() {
|
||||
return [this.targets, this.values, this.data, this.description];
|
||||
}
|
||||
|
||||
get currentProposal() {
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Proposal lifecycle
|
||||
delegate(delegation) {
|
||||
return Promise.all([
|
||||
delegation.token.connect(delegation.to).delegate(delegation.to),
|
||||
delegation.value === undefined ||
|
||||
delegation.token.connect(this.governor.runner).transfer(delegation.to, delegation.value),
|
||||
delegation.tokenId === undefined ||
|
||||
delegation.token
|
||||
.ownerOf(delegation.tokenId)
|
||||
.then(owner =>
|
||||
delegation.token.connect(this.governor.runner).transferFrom(owner, delegation.to, delegation.tokenId),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
propose() {
|
||||
return this.governor.propose(...this.fullProposal);
|
||||
}
|
||||
|
||||
queue() {
|
||||
return this.governor.queue(...this.shortProposal);
|
||||
}
|
||||
|
||||
execute() {
|
||||
return this.governor.execute(...this.shortProposal);
|
||||
}
|
||||
|
||||
cancel(visibility = 'external') {
|
||||
switch (visibility) {
|
||||
case 'external':
|
||||
return this.governor.cancel(...this.shortProposal);
|
||||
|
||||
case 'internal':
|
||||
return this.governor.$_cancel(...this.shortProposal);
|
||||
|
||||
default:
|
||||
throw new Error(`unsupported visibility "${visibility}"`);
|
||||
}
|
||||
}
|
||||
|
||||
async vote(vote = {}) {
|
||||
let method = 'castVote'; // default
|
||||
let args = [this.id, vote.support]; // base
|
||||
|
||||
if (vote.signature) {
|
||||
const sign = await vote.signature(this.governor, this.forgeMessage(vote));
|
||||
if (vote.params || vote.reason) {
|
||||
method = 'castVoteWithReasonAndParamsBySig';
|
||||
args.push(vote.voter, vote.reason ?? '', vote.params ?? '0x', sign);
|
||||
} else {
|
||||
method = 'castVoteBySig';
|
||||
args.push(vote.voter, sign);
|
||||
}
|
||||
} else if (vote.params) {
|
||||
method = 'castVoteWithReasonAndParams';
|
||||
args.push(vote.reason ?? '', vote.params);
|
||||
} else if (vote.reason) {
|
||||
method = 'castVoteWithReason';
|
||||
args.push(vote.reason);
|
||||
}
|
||||
|
||||
return await this.governor[method](...args);
|
||||
}
|
||||
|
||||
/// Clock helpers
|
||||
async waitForSnapshot(offset = 0n) {
|
||||
const timepoint = await this.governor.proposalSnapshot(this.id);
|
||||
return time.increaseTo[this.mode](timepoint + offset);
|
||||
}
|
||||
|
||||
async waitForDeadline(offset = 0n) {
|
||||
const timepoint = await this.governor.proposalDeadline(this.id);
|
||||
return time.increaseTo[this.mode](timepoint + offset);
|
||||
}
|
||||
|
||||
async waitForEta(offset = 0n) {
|
||||
const timestamp = await this.governor.proposalEta(this.id);
|
||||
return time.increaseTo.timestamp(timestamp + offset);
|
||||
}
|
||||
|
||||
/// Other helpers
|
||||
forgeMessage(vote = {}) {
|
||||
const message = { proposalId: this.id, support: vote.support, voter: vote.voter, nonce: vote.nonce };
|
||||
|
||||
if (vote.params || vote.reason) {
|
||||
message.reason = vote.reason ?? '';
|
||||
message.params = vote.params ?? '0x';
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
static proposalStatesToBitMap(proposalStates, options = {}) {
|
||||
if (!Array.isArray(proposalStates)) {
|
||||
proposalStates = [proposalStates];
|
||||
}
|
||||
const statesCount = ethers.toBigInt(Object.keys(ProposalState).length);
|
||||
let result = 0n;
|
||||
|
||||
for (const state of unique(proposalStates)) {
|
||||
if (state < 0n || state >= statesCount) {
|
||||
expect.fail(`ProposalState ${state} out of possible states (0...${statesCount}-1)`);
|
||||
} else {
|
||||
result |= 1n << state;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.inverted) {
|
||||
const mask = 2n ** statesCount - 1n;
|
||||
result = result ^ mask;
|
||||
}
|
||||
|
||||
return ethers.toBeHex(result, 32);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
GovernorHelper,
|
||||
timelockSalt,
|
||||
};
|
||||
36
lib_openzeppelin_contracts/test/helpers/iterate.js
Normal file
36
lib_openzeppelin_contracts/test/helpers/iterate.js
Normal file
@@ -0,0 +1,36 @@
|
||||
module.exports = {
|
||||
// ================================================= Array helpers =================================================
|
||||
|
||||
// Cut an array into an array of sized-length arrays
|
||||
// Example: chunk([1,2,3,4,5,6,7,8], 3) → [[1,2,3],[4,5,6],[7,8]]
|
||||
chunk: (array, size = 1) =>
|
||||
Array.from({ length: Math.ceil(array.length / size) }, (_, i) => array.slice(i * size, i * size + size)),
|
||||
|
||||
// Cartesian cross product of an array of arrays
|
||||
// Example: product([1,2],[a,b,c],[true]) → [[1,a,true],[1,b,true],[1,c,true],[2,a,true],[2,b,true],[2,c,true]]
|
||||
product: (...arrays) => arrays.reduce((a, b) => a.flatMap(ai => b.map(bi => [...ai, bi])), [[]]),
|
||||
|
||||
// Range from start to end in increment
|
||||
// Example: range(17,42,7) → [17,24,31,38]
|
||||
range: (start, stop = undefined, step = 1) => {
|
||||
if (!stop) {
|
||||
stop = start;
|
||||
start = 0;
|
||||
}
|
||||
return start < stop ? Array.from({ length: Math.ceil((stop - start) / step) }, (_, i) => start + i * step) : [];
|
||||
},
|
||||
|
||||
// Unique elements, with an optional getter function
|
||||
// Example: unique([1,1,2,3,4,8,1,3,8,13,42]) → [1,2,3,4,8,13,42]
|
||||
unique: (array, op = x => x) => array.filter((obj, i) => array.findIndex(entry => op(obj) === op(entry)) === i),
|
||||
|
||||
// Zip arrays together. If some arrays are smaller, undefined is used as a filler.
|
||||
// Example: zip([1,2],[a,b,c],[true]) → [[1,a,true],[2,b,undefined],[undefined,c,undefined]]
|
||||
zip: (...args) => Array.from({ length: Math.max(...args.map(arg => arg.length)) }, (_, i) => args.map(arg => arg[i])),
|
||||
|
||||
// ================================================ Object helpers =================================================
|
||||
|
||||
// Create a new object by mapping the values through a function, keeping the keys
|
||||
// Example: mapValues({a:1,b:2,c:3}, x => x**2) → {a:1,b:4,c:9}
|
||||
mapValues: (obj, fn) => Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, fn(v)])),
|
||||
};
|
||||
33
lib_openzeppelin_contracts/test/helpers/math.js
Normal file
33
lib_openzeppelin_contracts/test/helpers/math.js
Normal file
@@ -0,0 +1,33 @@
|
||||
// Array of number or bigint
|
||||
const max = (...values) => values.slice(1).reduce((x, y) => (x > y ? x : y), values.at(0));
|
||||
const min = (...values) => values.slice(1).reduce((x, y) => (x < y ? x : y), values.at(0));
|
||||
const sum = (...values) => values.slice(1).reduce((x, y) => x + y, values.at(0));
|
||||
|
||||
// Computes modexp without BigInt overflow for large numbers
|
||||
function modExp(b, e, m) {
|
||||
let result = 1n;
|
||||
|
||||
// If e is a power of two, modexp can be calculated as:
|
||||
// for (let result = b, i = 0; i < log2(e); i++) result = modexp(result, 2, m)
|
||||
//
|
||||
// Given any natural number can be written in terms of powers of 2 (i.e. binary)
|
||||
// then modexp can be calculated for any e, by multiplying b**i for all i where
|
||||
// binary(e)[i] is 1 (i.e. a power of two).
|
||||
for (let base = b % m; e > 0n; base = base ** 2n % m) {
|
||||
// Least significant bit is 1
|
||||
if (e % 2n == 1n) {
|
||||
result = (result * base) % m;
|
||||
}
|
||||
|
||||
e /= 2n; // Binary pop
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
min,
|
||||
max,
|
||||
sum,
|
||||
modExp,
|
||||
};
|
||||
14
lib_openzeppelin_contracts/test/helpers/methods.js
Normal file
14
lib_openzeppelin_contracts/test/helpers/methods.js
Normal file
@@ -0,0 +1,14 @@
|
||||
const { ethers } = require('hardhat');
|
||||
|
||||
const selector = signature => ethers.FunctionFragment.from(signature).selector;
|
||||
|
||||
const interfaceId = signatures =>
|
||||
ethers.toBeHex(
|
||||
signatures.reduce((acc, signature) => acc ^ ethers.toBigInt(selector(signature)), 0n),
|
||||
4,
|
||||
);
|
||||
|
||||
module.exports = {
|
||||
selector,
|
||||
interfaceId,
|
||||
};
|
||||
19
lib_openzeppelin_contracts/test/helpers/random.js
Normal file
19
lib_openzeppelin_contracts/test/helpers/random.js
Normal file
@@ -0,0 +1,19 @@
|
||||
const { ethers } = require('hardhat');
|
||||
|
||||
const generators = {
|
||||
address: () => ethers.Wallet.createRandom().address,
|
||||
bytes32: () => ethers.hexlify(ethers.randomBytes(32)),
|
||||
uint256: () => ethers.toBigInt(ethers.randomBytes(32)),
|
||||
int256: () => ethers.toBigInt(ethers.randomBytes(32)) + ethers.MinInt256,
|
||||
hexBytes: length => ethers.hexlify(ethers.randomBytes(length)),
|
||||
};
|
||||
|
||||
generators.address.zero = ethers.ZeroAddress;
|
||||
generators.bytes32.zero = ethers.ZeroHash;
|
||||
generators.uint256.zero = 0n;
|
||||
generators.int256.zero = 0n;
|
||||
generators.hexBytes.zero = '0x';
|
||||
|
||||
module.exports = {
|
||||
generators,
|
||||
};
|
||||
48
lib_openzeppelin_contracts/test/helpers/storage.js
Normal file
48
lib_openzeppelin_contracts/test/helpers/storage.js
Normal file
@@ -0,0 +1,48 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { setStorageAt } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
const ImplementationLabel = 'eip1967.proxy.implementation';
|
||||
const AdminLabel = 'eip1967.proxy.admin';
|
||||
const BeaconLabel = 'eip1967.proxy.beacon';
|
||||
|
||||
const erc1967Slot = label => ethers.toBeHex(ethers.toBigInt(ethers.id(label)) - 1n);
|
||||
const erc7201Slot = label => ethers.toBeHex(ethers.toBigInt(ethers.keccak256(erc1967Slot(label))) & ~0xffn);
|
||||
const erc7201format = contractName => `openzeppelin.storage.${contractName}`;
|
||||
|
||||
const getSlot = (address, slot) =>
|
||||
ethers.provider.getStorage(address, ethers.isBytesLike(slot) ? slot : erc1967Slot(slot));
|
||||
|
||||
const setSlot = (address, slot, value) =>
|
||||
Promise.all([
|
||||
ethers.isAddressable(address) ? address.getAddress() : Promise.resolve(address),
|
||||
ethers.isAddressable(value) ? value.getAddress() : Promise.resolve(value),
|
||||
]).then(([address, value]) => setStorageAt(address, ethers.isBytesLike(slot) ? slot : erc1967Slot(slot), value));
|
||||
|
||||
const getAddressInSlot = (address, slot) =>
|
||||
getSlot(address, slot).then(slotValue => ethers.AbiCoder.defaultAbiCoder().decode(['address'], slotValue)[0]);
|
||||
|
||||
const upgradeableSlot = (contractName, offset) => {
|
||||
try {
|
||||
// Try to get the artifact paths, will throw if it doesn't exist
|
||||
artifacts._getArtifactPathSync(`${contractName}Upgradeable`);
|
||||
return offset + ethers.toBigInt(erc7201Slot(erc7201format(contractName)));
|
||||
} catch (_) {
|
||||
return offset;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
ImplementationLabel,
|
||||
AdminLabel,
|
||||
BeaconLabel,
|
||||
ImplementationSlot: erc1967Slot(ImplementationLabel),
|
||||
AdminSlot: erc1967Slot(AdminLabel),
|
||||
BeaconSlot: erc1967Slot(BeaconLabel),
|
||||
erc1967Slot,
|
||||
erc7201Slot,
|
||||
erc7201format,
|
||||
setSlot,
|
||||
getSlot,
|
||||
getAddressInSlot,
|
||||
upgradeableSlot,
|
||||
};
|
||||
5
lib_openzeppelin_contracts/test/helpers/strings.js
Normal file
5
lib_openzeppelin_contracts/test/helpers/strings.js
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
// Capitalize the first char of a string
|
||||
// Example: capitalize('uint256') → 'Uint256'
|
||||
capitalize: str => str.charAt(0).toUpperCase() + str.slice(1),
|
||||
};
|
||||
30
lib_openzeppelin_contracts/test/helpers/time.js
Normal file
30
lib_openzeppelin_contracts/test/helpers/time.js
Normal file
@@ -0,0 +1,30 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { time, mine, mineUpTo } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
const { mapValues } = require('./iterate');
|
||||
|
||||
const clock = {
|
||||
blocknumber: () => time.latestBlock().then(ethers.toBigInt),
|
||||
timestamp: () => time.latest().then(ethers.toBigInt),
|
||||
};
|
||||
const clockFromReceipt = {
|
||||
blocknumber: receipt => Promise.resolve(ethers.toBigInt(receipt.blockNumber)),
|
||||
timestamp: receipt => ethers.provider.getBlock(receipt.blockNumber).then(block => ethers.toBigInt(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 = mapValues(time.duration, fn => n => ethers.toBigInt(fn(ethers.toNumber(n))));
|
||||
|
||||
module.exports = {
|
||||
clock,
|
||||
clockFromReceipt,
|
||||
increaseBy,
|
||||
increaseTo,
|
||||
duration,
|
||||
};
|
||||
29
lib_openzeppelin_contracts/test/helpers/txpool.js
Normal file
29
lib_openzeppelin_contracts/test/helpers/txpool.js
Normal file
@@ -0,0 +1,29 @@
|
||||
const { network } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { mine } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
const { unique } = require('./iterate');
|
||||
|
||||
async function batchInBlock(txs) {
|
||||
try {
|
||||
// disable auto-mining
|
||||
await network.provider.send('evm_setAutomine', [false]);
|
||||
// send all transactions
|
||||
const responses = await Promise.all(txs.map(fn => fn()));
|
||||
// mine one block
|
||||
await mine();
|
||||
// fetch receipts
|
||||
const receipts = await Promise.all(responses.map(response => response.wait()));
|
||||
// Sanity check, all tx should be in the same block
|
||||
expect(unique(receipts.map(receipt => receipt.blockNumber))).to.have.lengthOf(1);
|
||||
// return responses
|
||||
return receipts;
|
||||
} finally {
|
||||
// enable auto-mining
|
||||
await network.provider.send('evm_setAutomine', [true]);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
batchInBlock,
|
||||
};
|
||||
Reference in New Issue
Block a user