diff --git a/package-lock.json b/package-lock.json index 79ed96c1208..97546e5b1d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "@nomiclabs/hardhat-truffle5": "^2.0.5", "@nomiclabs/hardhat-web3": "^2.0.0", "@openzeppelin/docs-utils": "^0.1.5", + "@openzeppelin/merkle-tree": "^1.0.5", "@openzeppelin/test-helpers": "^0.5.13", "@openzeppelin/upgrade-safe-transpiler": "^0.3.32", "@openzeppelin/upgrades-core": "^1.20.6", @@ -38,10 +39,8 @@ "hardhat-exposed": "^0.3.13", "hardhat-gas-reporter": "^1.0.9", "hardhat-ignore-warnings": "^0.2.0", - "keccak256": "^1.0.2", "lodash.startcase": "^4.4.0", "lodash.zip": "^4.2.0", - "merkletreejs": "^0.2.13", "micromatch": "^4.0.2", "p-limit": "^3.1.0", "prettier": "^3.0.0", @@ -2604,6 +2603,73 @@ "node": ">=8" } }, + "node_modules/@openzeppelin/merkle-tree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@openzeppelin/merkle-tree/-/merkle-tree-1.0.5.tgz", + "integrity": "sha512-JkwG2ysdHeIphrScNxYagPy6jZeNONgDRyqU6lbFgE8HKCZFSkcP8r6AjZs+3HZk4uRNV0kNBBzuWhKQ3YV7Kw==", + "dev": true, + "dependencies": { + "@ethersproject/abi": "^5.7.0", + "ethereum-cryptography": "^1.1.2" + } + }, + "node_modules/@openzeppelin/merkle-tree/node_modules/@noble/hashes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.2.0.tgz", + "integrity": "sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/@openzeppelin/merkle-tree/node_modules/@scure/bip32": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.1.5.tgz", + "integrity": "sha512-XyNh1rB0SkEqd3tXcXMi+Xe1fvg+kUIcoRIEujP1Jgv7DqW2r9lg3Ah0NkFaCs9sTkQAQA8kw7xiRXzENi9Rtw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "@noble/hashes": "~1.2.0", + "@noble/secp256k1": "~1.7.0", + "@scure/base": "~1.1.0" + } + }, + "node_modules/@openzeppelin/merkle-tree/node_modules/@scure/bip39": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.1.tgz", + "integrity": "sha512-t+wDck2rVkh65Hmv280fYdVdY25J9YeEUIgn2LG1WM6gxFkGzcksoDiUkWVpVp3Oex9xGC68JU2dSbUfwZ2jPg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "@noble/hashes": "~1.2.0", + "@scure/base": "~1.1.0" + } + }, + "node_modules/@openzeppelin/merkle-tree/node_modules/ethereum-cryptography": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-1.2.0.tgz", + "integrity": "sha512-6yFQC9b5ug6/17CQpCyE3k9eKBMdhyVjzUy1WkiuY/E4vj/SXDBbCw8QEIaXqf0Mf2SnY6RmpDcwlUmBSS0EJw==", + "dev": true, + "dependencies": { + "@noble/hashes": "1.2.0", + "@noble/secp256k1": "1.7.1", + "@scure/bip32": "1.1.5", + "@scure/bip39": "1.1.1" + } + }, "node_modules/@openzeppelin/test-helpers": { "version": "0.5.16", "resolved": "https://registry.npmjs.org/@openzeppelin/test-helpers/-/test-helpers-0.5.16.tgz", @@ -5218,12 +5284,6 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, - "node_modules/buffer-reverse": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-reverse/-/buffer-reverse-1.0.1.tgz", - "integrity": "sha512-M87YIUBsZ6N924W57vDwT/aOu8hw7ZgdByz6ijksLjmHJELBASmYTTlNHRgjE+pTsT9oJXGaDSgqqwfdHotDUg==", - "dev": true - }, "node_modules/buffer-to-arraybuffer": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.5.tgz", @@ -6156,12 +6216,6 @@ "sha3": "^2.1.1" } }, - "node_modules/crypto-js": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.3.0.tgz", - "integrity": "sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q==", - "dev": true - }, "node_modules/css-select": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", @@ -10209,47 +10263,6 @@ "node": ">=10.0.0" } }, - "node_modules/keccak256": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/keccak256/-/keccak256-1.0.6.tgz", - "integrity": "sha512-8GLiM01PkdJVGUhR1e6M/AvWnSqYS0HaERI+K/QtStGDGlSTx2B1zTqZk4Zlqu5TxHJNTxWAdP9Y+WI50OApUw==", - "dev": true, - "dependencies": { - "bn.js": "^5.2.0", - "buffer": "^6.0.3", - "keccak": "^3.0.2" - } - }, - "node_modules/keccak256/node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "dev": true - }, - "node_modules/keccak256/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, "node_modules/keyv": { "version": "4.5.3", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", @@ -10784,31 +10797,6 @@ "node": ">= 8" } }, - "node_modules/merkletreejs": { - "version": "0.2.32", - "resolved": "https://registry.npmjs.org/merkletreejs/-/merkletreejs-0.2.32.tgz", - "integrity": "sha512-TostQBiwYRIwSE5++jGmacu3ODcKAgqb0Y/pnIohXS7sWxh1gCkSptbmF1a43faehRDpcHf7J/kv0Ml2D/zblQ==", - "dev": true, - "dependencies": { - "bignumber.js": "^9.0.1", - "buffer-reverse": "^1.0.1", - "crypto-js": "^3.1.9-1", - "treeify": "^1.1.0", - "web3-utils": "^1.3.4" - }, - "engines": { - "node": ">= 7.6.0" - } - }, - "node_modules/merkletreejs/node_modules/bignumber.js": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", - "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -15259,15 +15247,6 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "dev": true }, - "node_modules/treeify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/treeify/-/treeify-1.1.0.tgz", - "integrity": "sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, "node_modules/trim-newlines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", diff --git a/package.json b/package.json index 4b0be403df1..e5265dc5106 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "@nomiclabs/hardhat-truffle5": "^2.0.5", "@nomiclabs/hardhat-web3": "^2.0.0", "@openzeppelin/docs-utils": "^0.1.5", + "@openzeppelin/merkle-tree": "^1.0.5", "@openzeppelin/test-helpers": "^0.5.13", "@openzeppelin/upgrade-safe-transpiler": "^0.3.32", "@openzeppelin/upgrades-core": "^1.20.6", @@ -78,10 +79,8 @@ "hardhat-exposed": "^0.3.13", "hardhat-gas-reporter": "^1.0.9", "hardhat-ignore-warnings": "^0.2.0", - "keccak256": "^1.0.2", "lodash.startcase": "^4.4.0", "lodash.zip": "^4.2.0", - "merkletreejs": "^0.2.13", "micromatch": "^4.0.2", "p-limit": "^3.1.0", "prettier": "^3.0.0", diff --git a/test/governance/extensions/GovernorTimelockCompound.test.js b/test/governance/extensions/GovernorTimelockCompound.test.js index e9d6f83736a..56191eb5056 100644 --- a/test/governance/extensions/GovernorTimelockCompound.test.js +++ b/test/governance/extensions/GovernorTimelockCompound.test.js @@ -1,10 +1,10 @@ +const { ethers } = require('ethers'); const { constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); const Enums = require('../../helpers/enums'); const { GovernorHelper, proposalStatesToBitMap } = require('../../helpers/governance'); const { expectRevertCustomError } = require('../../helpers/customError'); -const { computeCreateAddress } = require('../../helpers/create'); const { clockFromReceipt } = require('../../helpers/time'); const Timelock = artifacts.require('CompTimelock'); @@ -41,7 +41,7 @@ contract('GovernorTimelockCompound', function (accounts) { // Need to predict governance address to set it as timelock admin with a delayed transfer const nonce = await web3.eth.getTransactionCount(deployer); - const predictGovernor = computeCreateAddress(deployer, nonce + 1); + const predictGovernor = ethers.getCreateAddress({ from: deployer, nonce: nonce + 1 }); this.timelock = await Timelock.new(predictGovernor, defaultDelay); this.mock = await Governor.new( diff --git a/test/helpers/account.js b/test/helpers/account.js index 1b01a721455..8c0ea130b41 100644 --- a/test/helpers/account.js +++ b/test/helpers/account.js @@ -1,8 +1,8 @@ -const { web3 } = require('hardhat'); +const { ethers } = require('hardhat'); const { impersonateAccount, setBalance } = require('@nomicfoundation/hardhat-network-helpers'); // Hardhat default balance -const DEFAULT_BALANCE = web3.utils.toBN('10000000000000000000000'); +const DEFAULT_BALANCE = 10000n * ethers.WeiPerEther; async function impersonate(account, balance = DEFAULT_BALANCE) { await impersonateAccount(account); diff --git a/test/helpers/chainid.js b/test/helpers/chainid.js index 693a822e559..e8181d55c26 100644 --- a/test/helpers/chainid.js +++ b/test/helpers/chainid.js @@ -1,12 +1,6 @@ -const hre = require('hardhat'); - -async function getChainId() { - const chainIdHex = await hre.network.provider.send('eth_chainId', []); - return new hre.web3.utils.BN(chainIdHex, 'hex'); -} +const { ethers } = require('hardhat'); module.exports = { - getChainId, - // TODO: when tests are ready to support bigint chainId - // getChainId: ethers.provider.getNetwork().then(network => network.chainId), + // TODO: remove conversion toNumber() when bigint are supported + getChainId: () => ethers.provider.getNetwork().then(network => ethers.toNumber(network.chainId)), }; diff --git a/test/helpers/constants.js b/test/helpers/constants.js index 0f4d028cfce..6a3a82f4f16 100644 --- a/test/helpers/constants.js +++ b/test/helpers/constants.js @@ -1,7 +1,5 @@ -const MAX_UINT48 = web3.utils.toBN(1).shln(48).subn(1).toString(); -const MAX_UINT64 = web3.utils.toBN(1).shln(64).subn(1).toString(); - +// TODO: remove toString() when bigint are supported module.exports = { - MAX_UINT48, - MAX_UINT64, + MAX_UINT48: (2n ** 48n - 1n).toString(), + MAX_UINT64: (2n ** 64n - 1n).toString(), }; diff --git a/test/helpers/create.js b/test/helpers/create.js deleted file mode 100644 index fa837395ab1..00000000000 --- a/test/helpers/create.js +++ /dev/null @@ -1,6 +0,0 @@ -const { ethers } = require('hardhat'); - -module.exports = { - computeCreateAddress: (from, nonce) => ethers.getCreateAddress({ from, nonce }), - computeCreate2Address: (salt, bytecode, from) => ethers.getCreate2Address(from, salt, ethers.keccak256(bytecode)), -}; diff --git a/test/helpers/eip712.js b/test/helpers/eip712.js index b12a6233ec9..0dd78b7e05e 100644 --- a/test/helpers/eip712.js +++ b/test/helpers/eip712.js @@ -1,5 +1,4 @@ -const ethSigUtil = require('eth-sig-util'); -const keccak256 = require('keccak256'); +const { ethers } = require('ethers'); const EIP712Domain = [ { name: 'name', type: 'string' }, @@ -17,14 +16,6 @@ const Permit = [ { name: 'deadline', type: 'uint256' }, ]; -function bufferToHexString(buffer) { - return '0x' + buffer.toString('hex'); -} - -function hexStringToBuffer(hexstr) { - return Buffer.from(hexstr.replace(/^0x/, ''), 'hex'); -} - async function getDomain(contract) { const { fields, name, version, chainId, verifyingContract, salt, extensions } = await contract.eip712Domain(); @@ -32,7 +23,15 @@ async function getDomain(contract) { throw Error('Extensions not implemented'); } - const domain = { name, version, chainId, verifyingContract, salt }; + const domain = { + name, + version, + // TODO: remove check when contracts are all migrated to ethers + chainId: web3.utils.isBN(chainId) ? chainId.toNumber() : chainId, + verifyingContract, + salt, + }; + for (const [i, { name }] of EIP712Domain.entries()) { if (!(fields & (1 << i))) { delete domain[name]; @@ -46,15 +45,9 @@ function domainType(domain) { return EIP712Domain.filter(({ name }) => domain[name] !== undefined); } -function domainSeparator(domain) { - return bufferToHexString( - ethSigUtil.TypedDataUtils.hashStruct('EIP712Domain', domain, { EIP712Domain: domainType(domain) }), - ); -} - function hashTypedData(domain, structHash) { - return bufferToHexString( - keccak256(Buffer.concat(['0x1901', domainSeparator(domain), structHash].map(str => hexStringToBuffer(str)))), + return ethers.keccak256( + Buffer.concat(['0x1901', ethers.TypedDataEncoder.hashDomain(domain), structHash].map(ethers.toBeArray)), ); } @@ -62,6 +55,6 @@ module.exports = { Permit, getDomain, domainType, - domainSeparator, + domainSeparator: ethers.TypedDataEncoder.hashDomain, hashTypedData, }; diff --git a/test/helpers/erc1967.js b/test/helpers/erc1967.js index 4ad92c55c99..50542c89a9f 100644 --- a/test/helpers/erc1967.js +++ b/test/helpers/erc1967.js @@ -1,3 +1,4 @@ +const { ethers } = require('hardhat'); const { getStorageAt, setStorageAt } = require('@nomicfoundation/hardhat-network-helpers'); const ImplementationLabel = 'eip1967.proxy.implementation'; @@ -5,29 +6,27 @@ const AdminLabel = 'eip1967.proxy.admin'; const BeaconLabel = 'eip1967.proxy.beacon'; function labelToSlot(label) { - return '0x' + web3.utils.toBN(web3.utils.keccak256(label)).subn(1).toString(16); + return ethers.toBeHex(BigInt(ethers.keccak256(ethers.toUtf8Bytes(label))) - 1n); } function getSlot(address, slot) { return getStorageAt( - web3.utils.isAddress(address) ? address : address.address, - web3.utils.isHex(slot) ? slot : labelToSlot(slot), + ethers.isAddress(address) ? address : address.address, + ethers.isBytesLike(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), + ethers.isAddress(address) ? address : address.address, + ethers.isBytesLike(slot) ? slot : labelToSlot(slot), + value, ); } async function getAddressInSlot(address, slot) { const slotValue = await getSlot(address, slot); - return web3.utils.toChecksumAddress(slotValue.substring(slotValue.length - 40)); + return ethers.getAddress(slotValue.substring(slotValue.length - 40)); } module.exports = { diff --git a/test/helpers/methods.js b/test/helpers/methods.js index cb30d8727cb..94f01cff018 100644 --- a/test/helpers/methods.js +++ b/test/helpers/methods.js @@ -1,5 +1,5 @@ -const { soliditySha3 } = require('web3-utils'); +const { ethers } = require('hardhat'); module.exports = { - selector: signature => soliditySha3(signature).substring(0, 10), + selector: signature => ethers.FunctionFragment.from(signature).selector, }; diff --git a/test/helpers/time.js b/test/helpers/time.js index 30df8dc32ea..7a2a13d23e4 100644 --- a/test/helpers/time.js +++ b/test/helpers/time.js @@ -1,17 +1,18 @@ -const ozHelpers = require('@openzeppelin/test-helpers'); -const helpers = require('@nomicfoundation/hardhat-network-helpers'); +const { time, mineUpTo } = require('@nomicfoundation/hardhat-network-helpers'); module.exports = { clock: { - blocknumber: () => helpers.time.latestBlock(), - timestamp: () => helpers.time.latest(), + blocknumber: () => time.latestBlock(), + timestamp: () => time.latest(), }, clockFromReceipt: { blocknumber: receipt => Promise.resolve(receipt.blockNumber), timestamp: receipt => web3.eth.getBlock(receipt.blockNumber).then(block => block.timestamp), + // TODO: update for ethers receipt + // timestamp: receipt => receipt.getBlock().then(block => block.timestamp), }, forward: { - blocknumber: ozHelpers.time.advanceBlockTo, - timestamp: helpers.time.increaseTo, + blocknumber: mineUpTo, + timestamp: time.increaseTo, }, }; diff --git a/test/proxy/Clones.test.js b/test/proxy/Clones.test.js index 0862778f786..ad3dd537ccc 100644 --- a/test/proxy/Clones.test.js +++ b/test/proxy/Clones.test.js @@ -1,6 +1,6 @@ +const { ethers } = require('ethers'); const { expectEvent } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); -const { computeCreate2Address } = require('../helpers/create'); const { expectRevertCustomError } = require('../helpers/customError'); const shouldBehaveLikeClone = require('./Clones.behaviour'); @@ -52,7 +52,7 @@ contract('Clones', function (accounts) { '5af43d82803e903d91602b57fd5bf3', ].join(''); - expect(computeCreate2Address(salt, creationCode, factory.address)).to.be.equal(predicted); + expect(ethers.getCreate2Address(factory.address, salt, ethers.keccak256(creationCode))).to.be.equal(predicted); expectEvent(await factory.$cloneDeterministic(implementation, salt), 'return$cloneDeterministic', { instance: predicted, diff --git a/test/proxy/transparent/ProxyAdmin.test.js b/test/proxy/transparent/ProxyAdmin.test.js index 4d1a54f6ab5..a3122eae334 100644 --- a/test/proxy/transparent/ProxyAdmin.test.js +++ b/test/proxy/transparent/ProxyAdmin.test.js @@ -1,3 +1,4 @@ +const { ethers } = require('hardhat'); const { expectRevert } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); const ImplV1 = artifacts.require('DummyImplementation'); @@ -8,7 +9,6 @@ const ITransparentUpgradeableProxy = artifacts.require('ITransparentUpgradeableP const { getAddressInSlot, ImplementationSlot } = require('../../helpers/erc1967'); const { expectRevertCustomError } = require('../../helpers/customError'); -const { computeCreateAddress } = require('../../helpers/create'); contract('ProxyAdmin', function (accounts) { const [proxyAdminOwner, anotherAccount] = accounts; @@ -23,7 +23,7 @@ contract('ProxyAdmin', function (accounts) { const proxy = await TransparentUpgradeableProxy.new(this.implementationV1.address, proxyAdminOwner, initializeData); const proxyNonce = await web3.eth.getTransactionCount(proxy.address); - const proxyAdminAddress = computeCreateAddress(proxy.address, proxyNonce - 1); // Nonce already used + const proxyAdminAddress = ethers.getCreateAddress({ from: proxy.address, nonce: proxyNonce - 1 }); // Nonce already used this.proxyAdmin = await ProxyAdmin.at(proxyAdminAddress); this.proxy = await ITransparentUpgradeableProxy.at(proxy.address); diff --git a/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js b/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js index 103af7fc3a9..da4d992872b 100644 --- a/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js +++ b/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js @@ -4,8 +4,7 @@ const { getAddressInSlot, ImplementationSlot, AdminSlot } = require('../../helpe const { expectRevertCustomError } = require('../../helpers/customError'); const { expect } = require('chai'); -const { web3 } = require('hardhat'); -const { computeCreateAddress } = require('../../helpers/create'); +const { ethers, web3 } = require('hardhat'); const { impersonate } = require('../../helpers/account'); const Implementation1 = artifacts.require('Implementation1'); @@ -27,7 +26,7 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx const proxy = await createProxy(logic, initData, opts); // Expect proxy admin to be the first and only contract created by the proxy - const proxyAdminAddress = computeCreateAddress(proxy.address, 1); + const proxyAdminAddress = ethers.getCreateAddress({ from: proxy.address, nonce: 1 }); await impersonate(proxyAdminAddress); return { diff --git a/test/utils/Create2.test.js b/test/utils/Create2.test.js index 336ab1acc23..a4ad992370d 100644 --- a/test/utils/Create2.test.js +++ b/test/utils/Create2.test.js @@ -1,6 +1,6 @@ -const { balance, ether, expectEvent, expectRevert, send } = require('@openzeppelin/test-helpers'); +const { ethers } = require('hardhat'); const { expect } = require('chai'); -const { computeCreate2Address } = require('../helpers/create'); +const { balance, ether, expectEvent, expectRevert, send } = require('@openzeppelin/test-helpers'); const { expectRevertCustomError } = require('../helpers/customError'); const Create2 = artifacts.require('$Create2'); @@ -26,7 +26,11 @@ contract('Create2', function (accounts) { describe('computeAddress', function () { it('computes the correct contract address', async function () { const onChainComputed = await this.factory.$computeAddress(saltHex, web3.utils.keccak256(constructorByteCode)); - const offChainComputed = computeCreate2Address(saltHex, constructorByteCode, this.factory.address); + const offChainComputed = ethers.getCreate2Address( + this.factory.address, + saltHex, + ethers.keccak256(constructorByteCode), + ); expect(onChainComputed).to.equal(offChainComputed); }); @@ -36,14 +40,22 @@ contract('Create2', function (accounts) { web3.utils.keccak256(constructorByteCode), deployerAccount, ); - const offChainComputed = computeCreate2Address(saltHex, constructorByteCode, deployerAccount); + const offChainComputed = ethers.getCreate2Address( + deployerAccount, + saltHex, + ethers.keccak256(constructorByteCode), + ); expect(onChainComputed).to.equal(offChainComputed); }); }); describe('deploy', function () { it('deploys a contract without constructor', async function () { - const offChainComputed = computeCreate2Address(saltHex, ConstructorLessContract.bytecode, this.factory.address); + const offChainComputed = ethers.getCreate2Address( + this.factory.address, + saltHex, + ethers.keccak256(ConstructorLessContract.bytecode), + ); expectEvent(await this.factory.$deploy(0, saltHex, ConstructorLessContract.bytecode), 'return$deploy', { addr: offChainComputed, @@ -53,7 +65,11 @@ contract('Create2', function (accounts) { }); it('deploys a contract with constructor arguments', async function () { - const offChainComputed = computeCreate2Address(saltHex, constructorByteCode, this.factory.address); + const offChainComputed = ethers.getCreate2Address( + this.factory.address, + saltHex, + ethers.keccak256(constructorByteCode), + ); expectEvent(await this.factory.$deploy(0, saltHex, constructorByteCode), 'return$deploy', { addr: offChainComputed, @@ -69,7 +85,11 @@ contract('Create2', function (accounts) { await send.ether(deployerAccount, this.factory.address, deposit); expect(await balance.current(this.factory.address)).to.be.bignumber.equal(deposit); - const offChainComputed = computeCreate2Address(saltHex, constructorByteCode, this.factory.address); + const offChainComputed = ethers.getCreate2Address( + this.factory.address, + saltHex, + ethers.keccak256(constructorByteCode), + ); expectEvent(await this.factory.$deploy(deposit, saltHex, constructorByteCode), 'return$deploy', { addr: offChainComputed, diff --git a/test/utils/cryptography/EIP712.test.js b/test/utils/cryptography/EIP712.test.js index faf01f1a302..dfad67906b3 100644 --- a/test/utils/cryptography/EIP712.test.js +++ b/test/utils/cryptography/EIP712.test.js @@ -1,6 +1,4 @@ -const ethSigUtil = require('eth-sig-util'); -const Wallet = require('ethereumjs-wallet').default; - +const { ethers } = require('hardhat'); const { getDomain, domainType, domainSeparator, hashTypedData } = require('../../helpers/eip712'); const { getChainId } = require('../../helpers/chainid'); const { mapValues } = require('../../helpers/iterate'); @@ -80,23 +78,18 @@ contract('EIP712', function (accounts) { contents: 'very interesting', }; - const data = { - types: { - EIP712Domain: this.domainType, - Mail: [ - { name: 'to', type: 'address' }, - { name: 'contents', type: 'string' }, - ], - }, - domain: this.domain, - primaryType: 'Mail', - message, + const types = { + Mail: [ + { name: 'to', type: 'address' }, + { name: 'contents', type: 'string' }, + ], }; - const wallet = Wallet.generate(); - const signature = ethSigUtil.signTypedMessage(wallet.getPrivateKey(), { data }); + const signer = ethers.Wallet.createRandom(); + const address = await signer.getAddress(); + const signature = await signer.signTypedData(this.domain, types, message); - await this.eip712.verify(signature, wallet.getAddressString(), message.to, message.contents); + await this.eip712.verify(signature, address, message.to, message.contents); }); it('name', async function () { diff --git a/test/utils/cryptography/MerkleProof.test.js b/test/utils/cryptography/MerkleProof.test.js index 5b87bc5252e..73e1ada950f 100644 --- a/test/utils/cryptography/MerkleProof.test.js +++ b/test/utils/cryptography/MerkleProof.test.js @@ -1,207 +1,173 @@ -const { expectRevert } = require('@openzeppelin/test-helpers'); - -const { MerkleTree } = require('merkletreejs'); -const keccak256 = require('keccak256'); - +const { ethers } = require('hardhat'); const { expect } = require('chai'); -const { expectRevertCustomError } = require('../../helpers/customError'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); +const { StandardMerkleTree } = require('@openzeppelin/merkle-tree'); + +const toElements = str => str.split('').map(e => [e]); +const hashPair = (a, b) => ethers.keccak256(Buffer.concat([a, b].sort(Buffer.compare))); -const MerkleProof = artifacts.require('$MerkleProof'); +async function fixture() { + const mock = await ethers.deployContract('$MerkleProof'); + return { mock }; +} -contract('MerkleProof', function () { +describe('MerkleProof', function () { beforeEach(async function () { - this.merkleProof = await MerkleProof.new(); + Object.assign(this, await loadFixture(fixture)); }); describe('verify', function () { it('returns true for a valid Merkle proof', async function () { - const elements = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='.split(''); - const merkleTree = new MerkleTree(elements, keccak256, { hashLeaves: true, sortPairs: true }); - - const root = merkleTree.getHexRoot(); - - const leaf = keccak256(elements[0]); + const merkleTree = StandardMerkleTree.of( + toElements('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='), + ['string'], + ); - const proof = merkleTree.getHexProof(leaf); + const root = merkleTree.root; + const hash = merkleTree.leafHash(['A']); + const proof = merkleTree.getProof(['A']); - expect(await this.merkleProof.$verify(proof, root, leaf)).to.equal(true); - expect(await this.merkleProof.$verifyCalldata(proof, root, leaf)).to.equal(true); + expect(await this.mock.$verify(proof, root, hash)).to.equal(true); + expect(await this.mock.$verifyCalldata(proof, root, hash)).to.equal(true); // For demonstration, it is also possible to create valid proofs for certain 64-byte values *not* in elements: - const noSuchLeaf = keccak256( - Buffer.concat([keccak256(elements[0]), keccak256(elements[1])].sort(Buffer.compare)), + const noSuchLeaf = hashPair( + ethers.toBeArray(merkleTree.leafHash(['A'])), + ethers.toBeArray(merkleTree.leafHash(['B'])), ); - expect(await this.merkleProof.$verify(proof.slice(1), root, noSuchLeaf)).to.equal(true); - expect(await this.merkleProof.$verifyCalldata(proof.slice(1), root, noSuchLeaf)).to.equal(true); + expect(await this.mock.$verify(proof.slice(1), root, noSuchLeaf)).to.equal(true); + expect(await this.mock.$verifyCalldata(proof.slice(1), root, noSuchLeaf)).to.equal(true); }); it('returns false for an invalid Merkle proof', async function () { - const correctElements = ['a', 'b', 'c']; - const correctMerkleTree = new MerkleTree(correctElements, keccak256, { hashLeaves: true, sortPairs: true }); + const correctMerkleTree = StandardMerkleTree.of(toElements('abc'), ['string']); + const otherMerkleTree = StandardMerkleTree.of(toElements('def'), ['string']); - const correctRoot = correctMerkleTree.getHexRoot(); + const root = correctMerkleTree.root; + const hash = correctMerkleTree.leafHash(['a']); + const proof = otherMerkleTree.getProof(['d']); - const correctLeaf = keccak256(correctElements[0]); - - const badElements = ['d', 'e', 'f']; - const badMerkleTree = new MerkleTree(badElements); - - const badProof = badMerkleTree.getHexProof(badElements[0]); - - expect(await this.merkleProof.$verify(badProof, correctRoot, correctLeaf)).to.equal(false); - expect(await this.merkleProof.$verifyCalldata(badProof, correctRoot, correctLeaf)).to.equal(false); + expect(await this.mock.$verify(proof, root, hash)).to.equal(false); + expect(await this.mock.$verifyCalldata(proof, root, hash)).to.equal(false); }); it('returns false for a Merkle proof of invalid length', async function () { - const elements = ['a', 'b', 'c']; - const merkleTree = new MerkleTree(elements, keccak256, { hashLeaves: true, sortPairs: true }); - - const root = merkleTree.getHexRoot(); - - const leaf = keccak256(elements[0]); + const merkleTree = StandardMerkleTree.of(toElements('abc'), ['string']); - const proof = merkleTree.getHexProof(leaf); + const root = merkleTree.root; + const leaf = merkleTree.leafHash(['a']); + const proof = merkleTree.getProof(['a']); const badProof = proof.slice(0, proof.length - 5); - expect(await this.merkleProof.$verify(badProof, root, leaf)).to.equal(false); - expect(await this.merkleProof.$verifyCalldata(badProof, root, leaf)).to.equal(false); + expect(await this.mock.$verify(badProof, root, leaf)).to.equal(false); + expect(await this.mock.$verifyCalldata(badProof, root, leaf)).to.equal(false); }); }); describe('multiProofVerify', function () { it('returns true for a valid Merkle multi proof', async function () { - const leaves = ['a', 'b', 'c', 'd', 'e', 'f'].map(keccak256).sort(Buffer.compare); - const merkleTree = new MerkleTree(leaves, keccak256, { sort: true }); + const merkleTree = StandardMerkleTree.of(toElements('abcdef'), ['string']); - const root = merkleTree.getRoot(); - const proofLeaves = ['b', 'f', 'd'].map(keccak256).sort(Buffer.compare); - const proof = merkleTree.getMultiProof(proofLeaves); - const proofFlags = merkleTree.getProofFlags(proofLeaves, proof); + const root = merkleTree.root; + const { proof, proofFlags, leaves } = merkleTree.getMultiProof(toElements('bdf')); + const hashes = leaves.map(e => merkleTree.leafHash(e)); - expect(await this.merkleProof.$multiProofVerify(proof, proofFlags, root, proofLeaves)).to.equal(true); - expect(await this.merkleProof.$multiProofVerifyCalldata(proof, proofFlags, root, proofLeaves)).to.equal(true); + expect(await this.mock.$multiProofVerify(proof, proofFlags, root, hashes)).to.equal(true); + expect(await this.mock.$multiProofVerifyCalldata(proof, proofFlags, root, hashes)).to.equal(true); }); it('returns false for an invalid Merkle multi proof', async function () { - const leaves = ['a', 'b', 'c', 'd', 'e', 'f'].map(keccak256).sort(Buffer.compare); - const merkleTree = new MerkleTree(leaves, keccak256, { sort: true }); - - const root = merkleTree.getRoot(); - const badProofLeaves = ['g', 'h', 'i'].map(keccak256).sort(Buffer.compare); - const badMerkleTree = new MerkleTree(badProofLeaves); - const badProof = badMerkleTree.getMultiProof(badProofLeaves); - const badProofFlags = badMerkleTree.getProofFlags(badProofLeaves, badProof); - - expect(await this.merkleProof.$multiProofVerify(badProof, badProofFlags, root, badProofLeaves)).to.equal(false); - expect(await this.merkleProof.$multiProofVerifyCalldata(badProof, badProofFlags, root, badProofLeaves)).to.equal( - false, - ); + const merkleTree = StandardMerkleTree.of(toElements('abcdef'), ['string']); + const otherMerkleTree = StandardMerkleTree.of(toElements('ghi'), ['string']); + + const root = merkleTree.root; + const { proof, proofFlags, leaves } = otherMerkleTree.getMultiProof(toElements('ghi')); + const hashes = leaves.map(e => merkleTree.leafHash(e)); + + expect(await this.mock.$multiProofVerify(proof, proofFlags, root, hashes)).to.equal(false); + expect(await this.mock.$multiProofVerifyCalldata(proof, proofFlags, root, hashes)).to.equal(false); }); it('revert with invalid multi proof #1', async function () { - const fill = Buffer.alloc(32); // This could be anything, we are reconstructing a fake branch - const leaves = ['a', 'b', 'c', 'd'].map(keccak256).sort(Buffer.compare); - const badLeaf = keccak256('e'); - const merkleTree = new MerkleTree(leaves, keccak256, { sort: true }); - - const root = merkleTree.getRoot(); - - await expectRevertCustomError( - this.merkleProof.$multiProofVerify( - [leaves[1], fill, merkleTree.layers[1][1]], - [false, false, false], - root, - [leaves[0], badLeaf], // A, E - ), - 'MerkleProofInvalidMultiproof', - [], - ); - await expectRevertCustomError( - this.merkleProof.$multiProofVerifyCalldata( - [leaves[1], fill, merkleTree.layers[1][1]], - [false, false, false], - root, - [leaves[0], badLeaf], // A, E - ), - 'MerkleProofInvalidMultiproof', - [], + const merkleTree = StandardMerkleTree.of(toElements('abcd'), ['string']); + + const root = merkleTree.root; + const hashA = merkleTree.leafHash(['a']); + const hashB = merkleTree.leafHash(['b']); + const hashCD = hashPair( + ethers.toBeArray(merkleTree.leafHash(['c'])), + ethers.toBeArray(merkleTree.leafHash(['d'])), ); + const hashE = merkleTree.leafHash(['e']); // incorrect (not part of the tree) + const fill = ethers.randomBytes(32); + + await expect( + this.mock.$multiProofVerify([hashB, fill, hashCD], [false, false, false], root, [hashA, hashE]), + ).to.be.revertedWithCustomError(this.mock, 'MerkleProofInvalidMultiproof'); + + await expect( + this.mock.$multiProofVerifyCalldata([hashB, fill, hashCD], [false, false, false], root, [hashA, hashE]), + ).to.be.revertedWithCustomError(this.mock, 'MerkleProofInvalidMultiproof'); }); it('revert with invalid multi proof #2', async function () { - const fill = Buffer.alloc(32); // This could be anything, we are reconstructing a fake branch - const leaves = ['a', 'b', 'c', 'd'].map(keccak256).sort(Buffer.compare); - const badLeaf = keccak256('e'); - const merkleTree = new MerkleTree(leaves, keccak256, { sort: true }); - - const root = merkleTree.getRoot(); - - await expectRevert( - this.merkleProof.$multiProofVerify( - [leaves[1], fill, merkleTree.layers[1][1]], - [false, false, false, false], - root, - [badLeaf, leaves[0]], // A, E - ), - 'reverted with panic code 0x32', + const merkleTree = StandardMerkleTree.of(toElements('abcd'), ['string']); + + const root = merkleTree.root; + const hashA = merkleTree.leafHash(['a']); + const hashB = merkleTree.leafHash(['b']); + const hashCD = hashPair( + ethers.toBeArray(merkleTree.leafHash(['c'])), + ethers.toBeArray(merkleTree.leafHash(['d'])), ); + const hashE = merkleTree.leafHash(['e']); // incorrect (not part of the tree) + const fill = ethers.randomBytes(32); - await expectRevert( - this.merkleProof.$multiProofVerifyCalldata( - [leaves[1], fill, merkleTree.layers[1][1]], - [false, false, false, false], - root, - [badLeaf, leaves[0]], // A, E - ), - 'reverted with panic code 0x32', - ); + await expect( + this.mock.$multiProofVerify([hashB, fill, hashCD], [false, false, false, false], root, [hashE, hashA]), + ).to.be.revertedWithPanic(0x32); + + await expect( + this.mock.$multiProofVerifyCalldata([hashB, fill, hashCD], [false, false, false, false], root, [hashE, hashA]), + ).to.be.revertedWithPanic(0x32); }); it('limit case: works for tree containing a single leaf', async function () { - const leaves = ['a'].map(keccak256).sort(Buffer.compare); - const merkleTree = new MerkleTree(leaves, keccak256, { sort: true }); + const merkleTree = StandardMerkleTree.of(toElements('a'), ['string']); - const root = merkleTree.getRoot(); - const proofLeaves = ['a'].map(keccak256).sort(Buffer.compare); - const proof = merkleTree.getMultiProof(proofLeaves); - const proofFlags = merkleTree.getProofFlags(proofLeaves, proof); + const root = merkleTree.root; + const { proof, proofFlags, leaves } = merkleTree.getMultiProof(toElements('a')); + const hashes = leaves.map(e => merkleTree.leafHash(e)); - expect(await this.merkleProof.$multiProofVerify(proof, proofFlags, root, proofLeaves)).to.equal(true); - expect(await this.merkleProof.$multiProofVerifyCalldata(proof, proofFlags, root, proofLeaves)).to.equal(true); + expect(await this.mock.$multiProofVerify(proof, proofFlags, root, hashes)).to.equal(true); + expect(await this.mock.$multiProofVerifyCalldata(proof, proofFlags, root, hashes)).to.equal(true); }); it('limit case: can prove empty leaves', async function () { - const leaves = ['a', 'b', 'c', 'd'].map(keccak256).sort(Buffer.compare); - const merkleTree = new MerkleTree(leaves, keccak256, { sort: true }); + const merkleTree = StandardMerkleTree.of(toElements('abcd'), ['string']); - const root = merkleTree.getRoot(); - expect(await this.merkleProof.$multiProofVerify([root], [], root, [])).to.equal(true); - expect(await this.merkleProof.$multiProofVerifyCalldata([root], [], root, [])).to.equal(true); + const root = merkleTree.root; + expect(await this.mock.$multiProofVerify([root], [], root, [])).to.equal(true); + expect(await this.mock.$multiProofVerifyCalldata([root], [], root, [])).to.equal(true); }); it('reverts processing manipulated proofs with a zero-value node at depth 1', async function () { // Create a merkle tree that contains a zero leaf at depth 1 - const leaves = [keccak256('real leaf'), Buffer.alloc(32, 0)]; - const merkleTree = new MerkleTree(leaves, keccak256, { sortPairs: true }); - - const root = merkleTree.getRoot(); + const leave = ethers.id('real leaf'); + const root = hashPair(ethers.toBeArray(leave), Buffer.alloc(32, 0)); // Now we can pass any **malicious** fake leaves as valid! - const maliciousLeaves = ['malicious', 'leaves'].map(keccak256).sort(Buffer.compare); - const maliciousProof = [leaves[0], leaves[0]]; + const maliciousLeaves = ['malicious', 'leaves'].map(ethers.id).map(ethers.toBeArray).sort(Buffer.compare); + const maliciousProof = [leave, leave]; const maliciousProofFlags = [true, true, false]; - await expectRevertCustomError( - this.merkleProof.$multiProofVerify(maliciousProof, maliciousProofFlags, root, maliciousLeaves), - 'MerkleProofInvalidMultiproof', - [], - ); + await expect( + this.mock.$multiProofVerify(maliciousProof, maliciousProofFlags, root, maliciousLeaves), + ).to.be.revertedWithCustomError(this.mock, 'MerkleProofInvalidMultiproof'); - await expectRevertCustomError( - this.merkleProof.$multiProofVerifyCalldata(maliciousProof, maliciousProofFlags, root, maliciousLeaves), - 'MerkleProofInvalidMultiproof', - [], - ); + await expect( + this.mock.$multiProofVerifyCalldata(maliciousProof, maliciousProofFlags, root, maliciousLeaves), + ).to.be.revertedWithCustomError(this.mock, 'MerkleProofInvalidMultiproof'); }); }); });