Browse Source

Migrate `MerkleProof` tests among other testing utilities (#4689)

Hadrien Croubois 1 year ago
parent
commit
7c8b7a2728

+ 68 - 89
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",

+ 1 - 2
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",

+ 2 - 2
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(

+ 2 - 2
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);

+ 3 - 9
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)),
 };

+ 3 - 5
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(),
 };

+ 0 - 6
test/helpers/create.js

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

+ 13 - 20
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,
 };

+ 8 - 9
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 = {

+ 2 - 2
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,
 };

+ 7 - 6
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,
   },
 };

+ 2 - 2
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,

+ 2 - 2
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);

+ 2 - 3
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 {

+ 27 - 7
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,

+ 10 - 17
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 () {

+ 109 - 143
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');
     });
   });
 });