diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 49a2a7e..697dc6a 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -26,11 +26,17 @@ jobs: - name: Run Fuzzed Unit Tests run: forge test --no-match-path src/test/StandardInput.t.sol --fuzz-runs 10000 - - name: Run Differential Tests + - name: Run Murky Differential Tests run: | npm --prefix differential_testing/scripts/ install npm --prefix differential_testing/scripts/ run compile - forge test --ffi --contracts differential_testing/test/DifferentialTests.t.sol --fuzz-runs 512 + forge test --ffi --contracts differential_testing/test/DifferentialTests.t.sol --fuzz-runs 512 + + - name: Run Complete Differential Tests + run: | + npm --prefix differential_testing/scripts/ install + npm --prefix differential_testing/scripts/ run compile + forge test --ffi --contracts differential_testing/test/CompleteDifferentialTests.t.sol --fuzz-runs 512 - name: Run Standard Gas Snapshotting run: forge snapshot --gas-report --ffi --match-path src/test/StandardInput.t.sol \ No newline at end of file diff --git a/.gitignore b/.gitignore index 177604b..c6ed5c4 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ out/ *.txt differential_testing/**/node_modules differential_testing/**/*.js +differential_testing/**/*.json differential_testing/data/input !remappings.txt \ No newline at end of file diff --git a/README.md b/README.md index 8ee51d8..89041f1 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,16 @@ ![Slither](https://github.com/dmfxyz/murky/actions/workflows/slither.yml/badge.svg?event=push) -Murky contains contracts that can generate merkle roots and proofs. Murky also performs inclusion verification. Both XOR-based and a concatenation-based hashing are currently supported. +### Overview +Murky contains contracts that can generate merkle roots and proofs. Murky also performs inclusion verification. A couple of default implementations are available out-of-the-box: -The root generation, proof generation, and verification functions are all fuzz tested (configured 5,000 runs by default) using arbitrary bytes32 arrays and uint leaves. There is also standardized testing and differential testing. +1. [`Merkle.sol`](./src/Merkle.sol) is the original Murky implementation. It implements the tree as a [Full Binary Tree](https://xlinux.nist.gov/dads/HTML/fullBinaryTree.html). + +2. [`CompleteMerkle.sol`](./src/CompleteMerkle.sol) is a merkle tree implementation using [Complete Binary Trees](https://xlinux.nist.gov/dads/HTML/completeBinaryTree.html). Some external libraries, particulary front-end or off-chain ones, use this type of tree. + +By default, both trees use sorted concatentation based hashing; you can also "bring your own" hashing function by inherting from [`MurkyBase.sol`](./src/common/MurkyBase.sol). + +The root generation, proof generation, and verification functions are all fuzz tested (configured 10,000 runs by default) using arbitrary bytes32 arrays and uint leaves. See [testing](#testing). > Note: Code is not audited (yet). Please do your own due dilligence testing if you are planning to use this code! @@ -34,11 +41,6 @@ bool verified = m.verifyProof(root, proof, data[2]); // true! assertTrue(verified); ``` -### Notes -* `Xorkle.sol` is implemented as a XOR tree. This allows for greater gas efficiency: hashes are calculated on 32 bytes instead of 64; it is agnostic of sibling order so there is less lt/gt branching. Note that XOR trees are not appropriate for all use-cases*. - -* `Merkle.sol` is implemented using concatenation and thus is a generic merkle tree. It's less efficient, but is compatible with [OpenZeppelin's Prover](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/MerkleProof.sol) and other implementations. Use this one if you aren't sure. Compatiblity with OZ Prover is implemented as a fuzz test. - ### Script `Merkle.s.sol` is implemented using `forge-std` for quick and simple interaction with the core contracts. The script reads from `script/target/input.json`, generates merkle proof using `Merkle.sol` and then outputs at `script/target/output.json`. @@ -57,17 +59,12 @@ When measuring a change's performance impact, please ensure you are benchmarking forge snapshot --ffi --match-path src/test/StandardInput.t.sol ``` -Passing just standardized tests is not sufficient for implementation changes. All changes must pass all tests, preferably with 10,000+ fuzz runs. - -There is also early support for [differential testing](./differential_testing/). +Passing just standardized tests is not sufficient for implementation changes. All changes must pass all tests, preferably with 10,000 fuzz runs. Slither analysis must also pass. > * It's possible that an improvement is not adequetly revealed by the current standardized data. If that is the case, new standard data should be provided with an accompanying description/justification. +There is also [differential testing](./differential_testing/). + #### Latest Gas ![gas report](./reports/murky_gas_report.png) - -[Gas Snapshots](./.gas-snapshot) are run only on the standardized tests. See [Testing](#testing). - ---- -#### TODO -- [ ] \* Do a writeup on the use-cases for XORs. \ No newline at end of file +--- \ No newline at end of file diff --git a/differential_testing/README.md b/differential_testing/README.md index 5a2f210..f0a0ea6 100644 --- a/differential_testing/README.md +++ b/differential_testing/README.md @@ -1,7 +1,7 @@ ## Differential Testing Differential testing is used to compare Murky's solidity implementation to reference implementations in other languages. This directory contains the scripts needed to support this testing, as well as the differential tests themselves. -Currently, the only reference implementation is adapted from the [Uniswap/merkle-distributor](https://github.com/uniswap/merkle-distributor) implementation. It is written in javascript. +Currently two reference implementations are tested. The first is adapted from [Uniswap/merkle-distributor](https://github.com/uniswap/merkle-distributor), and the second is from [OpenZeppelin/merkle-tree](https://github.com/OpenZeppelin/merkle-tree). Both are written in Javascript. ### Setup @@ -14,16 +14,25 @@ npm run compile ### Test the javascript implementation From the scripts directory: +1. To test that the Uniswap/merkle-distributor differential test will work: ```sh npm run generate-root ``` +2. To test that the OpenZeppelin/merkle-tree differential test will work: +```sh +npm run generate-complete-root ../data/complete_root_input.json +npm run generate-complete-proof ../data/complete_proof_input.json +``` + +If all commands output data without exception, then you are ready to run the differential tests. ### Run the differential test using foundry Now you can run the tests. -From the root of the Murky repo, run: +From the **root** of the Murky repo, run: ```sh -forge test --ffi -c differential_testing/test/DifferentialTests.t.sol +forge test --ffi --contracts differential_testing/test/ ``` +> Note: The differential tests can take some time to run. An extended period of time without output does not necessarily indicate a problem. diff --git a/differential_testing/scripts/generate_complete_proof.ts b/differential_testing/scripts/generate_complete_proof.ts new file mode 100644 index 0000000..b14f5d1 --- /dev/null +++ b/differential_testing/scripts/generate_complete_proof.ts @@ -0,0 +1,13 @@ +import { StandardMerkleTree } from "@openzeppelin/merkle-tree"; +import { ethers } from 'ethers'; + +const fs = require('fs'); +const input_file = process.argv[2]; +const input = JSON.parse(fs.readFileSync(input_file, 'utf8')); +const one_word = "0x0000000000000000000000000000000000000000000000000000000000000001" +const leafs = input['leafs'].map((bytes32) => [bytes32, one_word]); +const indexToProve = input['index']; +const tree = StandardMerkleTree.of(leafs, ["bytes32", "bytes32"], { sortLeaves: false }); // NO DEFAULT SORTING LEAVES +const proof = tree.getProof(indexToProve); +process.stdout.write(ethers.utils.defaultAbiCoder.encode(['bytes32[]'], [proof])); + \ No newline at end of file diff --git a/differential_testing/scripts/generate_complete_root.ts b/differential_testing/scripts/generate_complete_root.ts new file mode 100644 index 0000000..0807149 --- /dev/null +++ b/differential_testing/scripts/generate_complete_root.ts @@ -0,0 +1,9 @@ +import { StandardMerkleTree } from "@openzeppelin/merkle-tree"; +const fs = require('fs'); +const input_file = process.argv[2]; +const input = JSON.parse(fs.readFileSync(input_file, 'utf8')); +const one_word = "0x0000000000000000000000000000000000000000000000000000000000000001" +const leafs = input['leafs'].map((bytes32) => [bytes32, one_word]); +const tree = StandardMerkleTree.of(leafs, ["bytes32", "bytes32"], { sortLeaves: false }); // NO DEFAULT SORTING LEAVES +process.stdout.write(tree.root); + diff --git a/differential_testing/scripts/generate_root.ts b/differential_testing/scripts/generate_root.ts index d7131da..ce2e17e 100644 --- a/differential_testing/scripts/generate_root.ts +++ b/differential_testing/scripts/generate_root.ts @@ -16,5 +16,5 @@ const encodedData = ethers.utils.defaultAbiCoder.encode(["bytes32[129]"], [data] if (!fs.existsSync("../data/")) { fs.mkdirSync("../data/"); } -fs.writeFileSync("../data/input", encodedData); +fs.writeFileSync("../data/merkle_input.txt", encodedData); diff --git a/differential_testing/scripts/package-lock.json b/differential_testing/scripts/package-lock.json index af2dd8d..a438fab 100644 --- a/differential_testing/scripts/package-lock.json +++ b/differential_testing/scripts/package-lock.json @@ -1,14 +1,38 @@ { - "name": "murky-uniswap", + "name": "murky-differential", "version": "1.0.0", - "lockfileVersion": 1, + "lockfileVersion": 3, "requires": true, - "dependencies": { - "@ethersproject/abi": { + "packages": { + "": { + "name": "murky-differential", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@openzeppelin/merkle-tree": "^1.0.6", + "ethereumjs-util": "^7.1.4", + "ethers": "^5.6.4", + "rlp": "^3.0.0" + }, + "devDependencies": { + "typescript": "^4.6.3" + } + }, + "node_modules/@ethersproject/abi": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.6.1.tgz", "integrity": "sha512-0cqssYh6FXjlwKWBmLm3+zH2BNARoS5u/hxbz+LpQmcDB3w0W553h2btWui1/uZp2GBM/SI3KniTuMcYyHpA5w==", - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/address": "^5.6.0", "@ethersproject/bignumber": "^5.6.0", "@ethersproject/bytes": "^5.6.0", @@ -20,11 +44,21 @@ "@ethersproject/strings": "^5.6.0" } }, - "@ethersproject/abstract-provider": { + "node_modules/@ethersproject/abstract-provider": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.6.0.tgz", "integrity": "sha512-oPMFlKLN+g+y7a79cLK3WiLcjWFnZQtXWgnLAbHZcN3s7L4v90UHpTOrLk+m3yr0gt+/h9STTM6zrr7PM8uoRw==", - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/bignumber": "^5.6.0", "@ethersproject/bytes": "^5.6.0", "@ethersproject/logger": "^5.6.0", @@ -34,11 +68,21 @@ "@ethersproject/web": "^5.6.0" } }, - "@ethersproject/abstract-signer": { + "node_modules/@ethersproject/abstract-signer": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.6.0.tgz", "integrity": "sha512-WOqnG0NJKtI8n0wWZPReHtaLkDByPL67tn4nBaDAhmVq8sjHTPbCdz4DRhVu/cfTOvfy9w3iq5QZ7BX7zw56BQ==", - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/abstract-provider": "^5.6.0", "@ethersproject/bignumber": "^5.6.0", "@ethersproject/bytes": "^5.6.0", @@ -46,11 +90,21 @@ "@ethersproject/properties": "^5.6.0" } }, - "@ethersproject/address": { + "node_modules/@ethersproject/address": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.6.0.tgz", "integrity": "sha512-6nvhYXjbXsHPS+30sHZ+U4VMagFC/9zAk6Gd/h3S21YW4+yfb0WfRtaAIZ4kfM4rrVwqiy284LP0GtL5HXGLxQ==", - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/bignumber": "^5.6.0", "@ethersproject/bytes": "^5.6.0", "@ethersproject/keccak256": "^5.6.0", @@ -58,61 +112,119 @@ "@ethersproject/rlp": "^5.6.0" } }, - "@ethersproject/base64": { + "node_modules/@ethersproject/base64": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.6.0.tgz", "integrity": "sha512-2Neq8wxJ9xHxCF9TUgmKeSh9BXJ6OAxWfeGWvbauPh8FuHEjamgHilllx8KkSd5ErxyHIX7Xv3Fkcud2kY9ezw==", - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/bytes": "^5.6.0" } }, - "@ethersproject/basex": { + "node_modules/@ethersproject/basex": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.6.0.tgz", "integrity": "sha512-qN4T+hQd/Md32MoJpc69rOwLYRUXwjTlhHDIeUkUmiN/JyWkkLLMoG0TqvSQKNqZOMgN5stbUYN6ILC+eD7MEQ==", - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/bytes": "^5.6.0", "@ethersproject/properties": "^5.6.0" } }, - "@ethersproject/bignumber": { + "node_modules/@ethersproject/bignumber": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.6.0.tgz", "integrity": "sha512-VziMaXIUHQlHJmkv1dlcd6GY2PmT0khtAqaMctCIDogxkrarMzA9L94KN1NeXqqOfFD6r0sJT3vCTOFSmZ07DA==", - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/bytes": "^5.6.0", "@ethersproject/logger": "^5.6.0", "bn.js": "^4.11.9" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - } } }, - "@ethersproject/bytes": { + "node_modules/@ethersproject/bignumber/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "node_modules/@ethersproject/bytes": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.6.1.tgz", "integrity": "sha512-NwQt7cKn5+ZE4uDn+X5RAXLp46E1chXoaMmrxAyA0rblpxz8t58lVkrHXoRIn0lz1joQElQ8410GqhTqMOwc6g==", - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/logger": "^5.6.0" } }, - "@ethersproject/constants": { + "node_modules/@ethersproject/constants": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.6.0.tgz", "integrity": "sha512-SrdaJx2bK0WQl23nSpV/b1aq293Lh0sUaZT/yYKPDKn4tlAbkH96SPJwIhwSwTsoQQZxuh1jnqsKwyymoiBdWA==", - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/bignumber": "^5.6.0" } }, - "@ethersproject/contracts": { + "node_modules/@ethersproject/contracts": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.6.0.tgz", "integrity": "sha512-74Ge7iqTDom0NX+mux8KbRUeJgu1eHZ3iv6utv++sLJG80FVuU9HnHeKVPfjd9s3woFhaFoQGf3B3iH/FrQmgw==", - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/abi": "^5.6.0", "@ethersproject/abstract-provider": "^5.6.0", "@ethersproject/abstract-signer": "^5.6.0", @@ -125,11 +237,21 @@ "@ethersproject/transactions": "^5.6.0" } }, - "@ethersproject/hash": { + "node_modules/@ethersproject/hash": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.6.0.tgz", "integrity": "sha512-fFd+k9gtczqlr0/BruWLAu7UAOas1uRRJvOR84uDf4lNZ+bTkGl366qvniUZHKtlqxBRU65MkOobkmvmpHU+jA==", - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/abstract-signer": "^5.6.0", "@ethersproject/address": "^5.6.0", "@ethersproject/bignumber": "^5.6.0", @@ -140,11 +262,21 @@ "@ethersproject/strings": "^5.6.0" } }, - "@ethersproject/hdnode": { + "node_modules/@ethersproject/hdnode": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.6.0.tgz", "integrity": "sha512-61g3Jp3nwDqJcL/p4nugSyLrpl/+ChXIOtCEM8UDmWeB3JCAt5FoLdOMXQc3WWkc0oM2C0aAn6GFqqMcS/mHTw==", - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/abstract-signer": "^5.6.0", "@ethersproject/basex": "^5.6.0", "@ethersproject/bignumber": "^5.6.0", @@ -159,11 +291,21 @@ "@ethersproject/wordlists": "^5.6.0" } }, - "@ethersproject/json-wallets": { + "node_modules/@ethersproject/json-wallets": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.6.0.tgz", "integrity": "sha512-fmh86jViB9r0ibWXTQipxpAGMiuxoqUf78oqJDlCAJXgnJF024hOOX7qVgqsjtbeoxmcLwpPsXNU0WEe/16qPQ==", - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/abstract-signer": "^5.6.0", "@ethersproject/address": "^5.6.0", "@ethersproject/bytes": "^5.6.0", @@ -179,50 +321,110 @@ "scrypt-js": "3.0.1" } }, - "@ethersproject/keccak256": { + "node_modules/@ethersproject/keccak256": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.6.0.tgz", "integrity": "sha512-tk56BJ96mdj/ksi7HWZVWGjCq0WVl/QvfhFQNeL8fxhBlGoP+L80uDCiQcpJPd+2XxkivS3lwRm3E0CXTfol0w==", - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/bytes": "^5.6.0", "js-sha3": "0.8.0" } }, - "@ethersproject/logger": { + "node_modules/@ethersproject/logger": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.6.0.tgz", - "integrity": "sha512-BiBWllUROH9w+P21RzoxJKzqoqpkyM1pRnEKG69bulE9TSQD8SAIvTQqIMZmmCO8pUNkgLP1wndX1gKghSpBmg==" + "integrity": "sha512-BiBWllUROH9w+P21RzoxJKzqoqpkyM1pRnEKG69bulE9TSQD8SAIvTQqIMZmmCO8pUNkgLP1wndX1gKghSpBmg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ] }, - "@ethersproject/networks": { + "node_modules/@ethersproject/networks": { "version": "5.6.2", "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.6.2.tgz", "integrity": "sha512-9uEzaJY7j5wpYGTojGp8U89mSsgQLc40PCMJLMCnFXTs7nhBveZ0t7dbqWUNrepWTszDbFkYD6WlL8DKx5huHA==", - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/logger": "^5.6.0" } }, - "@ethersproject/pbkdf2": { + "node_modules/@ethersproject/pbkdf2": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.6.0.tgz", "integrity": "sha512-Wu1AxTgJo3T3H6MIu/eejLFok9TYoSdgwRr5oGY1LTLfmGesDoSx05pemsbrPT2gG4cQME+baTSCp5sEo2erZQ==", - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/bytes": "^5.6.0", "@ethersproject/sha2": "^5.6.0" } }, - "@ethersproject/properties": { + "node_modules/@ethersproject/properties": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.6.0.tgz", "integrity": "sha512-szoOkHskajKePTJSZ46uHUWWkbv7TzP2ypdEK6jGMqJaEt2sb0jCgfBo0gH0m2HBpRixMuJ6TBRaQCF7a9DoCg==", - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/logger": "^5.6.0" } }, - "@ethersproject/providers": { + "node_modules/@ethersproject/providers": { "version": "5.6.4", "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.6.4.tgz", "integrity": "sha512-WAdknnaZ52hpHV3qPiJmKx401BLpup47h36Axxgre9zT+doa/4GC/Ne48ICPxTm0BqndpToHjpLP1ZnaxyE+vw==", - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/abstract-provider": "^5.6.0", "@ethersproject/abstract-signer": "^5.6.0", "@ethersproject/address": "^5.6.0", @@ -244,59 +446,107 @@ "ws": "7.4.6" } }, - "@ethersproject/random": { + "node_modules/@ethersproject/random": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.6.0.tgz", "integrity": "sha512-si0PLcLjq+NG/XHSZz90asNf+YfKEqJGVdxoEkSukzbnBgC8rydbgbUgBbBGLeHN4kAJwUFEKsu3sCXT93YMsw==", - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/bytes": "^5.6.0", "@ethersproject/logger": "^5.6.0" } }, - "@ethersproject/rlp": { + "node_modules/@ethersproject/rlp": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.6.0.tgz", "integrity": "sha512-dz9WR1xpcTL+9DtOT/aDO+YyxSSdO8YIS0jyZwHHSlAmnxA6cKU3TrTd4Xc/bHayctxTgGLYNuVVoiXE4tTq1g==", - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/bytes": "^5.6.0", "@ethersproject/logger": "^5.6.0" } }, - "@ethersproject/sha2": { + "node_modules/@ethersproject/sha2": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.6.0.tgz", "integrity": "sha512-1tNWCPFLu1n3JM9t4/kytz35DkuF9MxqkGGEHNauEbaARdm2fafnOyw1s0tIQDPKF/7bkP1u3dbrmjpn5CelyA==", - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/bytes": "^5.6.0", "@ethersproject/logger": "^5.6.0", "hash.js": "1.1.7" } }, - "@ethersproject/signing-key": { + "node_modules/@ethersproject/signing-key": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.6.0.tgz", "integrity": "sha512-S+njkhowmLeUu/r7ir8n78OUKx63kBdMCPssePS89So1TH4hZqnWFsThEd/GiXYp9qMxVrydf7KdM9MTGPFukA==", - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/bytes": "^5.6.0", "@ethersproject/logger": "^5.6.0", "@ethersproject/properties": "^5.6.0", "bn.js": "^4.11.9", "elliptic": "6.5.4", "hash.js": "1.1.7" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - } } }, - "@ethersproject/solidity": { + "node_modules/@ethersproject/signing-key/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "node_modules/@ethersproject/solidity": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.6.0.tgz", "integrity": "sha512-YwF52vTNd50kjDzqKaoNNbC/r9kMDPq3YzDWmsjFTRBcIF1y4JCQJ8gB30wsTfHbaxgxelI5BfxQSxD/PbJOww==", - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/bignumber": "^5.6.0", "@ethersproject/bytes": "^5.6.0", "@ethersproject/keccak256": "^5.6.0", @@ -305,21 +555,41 @@ "@ethersproject/strings": "^5.6.0" } }, - "@ethersproject/strings": { + "node_modules/@ethersproject/strings": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.6.0.tgz", "integrity": "sha512-uv10vTtLTZqrJuqBZR862ZQjTIa724wGPWQqZrofaPI/kUsf53TBG0I0D+hQ1qyNtllbNzaW+PDPHHUI6/65Mg==", - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/bytes": "^5.6.0", "@ethersproject/constants": "^5.6.0", "@ethersproject/logger": "^5.6.0" } }, - "@ethersproject/transactions": { + "node_modules/@ethersproject/transactions": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.6.0.tgz", "integrity": "sha512-4HX+VOhNjXHZyGzER6E/LVI2i6lf9ejYeWD6l4g50AdmimyuStKc39kvKf1bXWQMg7QNVh+uC7dYwtaZ02IXeg==", - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/address": "^5.6.0", "@ethersproject/bignumber": "^5.6.0", "@ethersproject/bytes": "^5.6.0", @@ -331,21 +601,41 @@ "@ethersproject/signing-key": "^5.6.0" } }, - "@ethersproject/units": { + "node_modules/@ethersproject/units": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.6.0.tgz", "integrity": "sha512-tig9x0Qmh8qbo1w8/6tmtyrm/QQRviBh389EQ+d8fP4wDsBrJBf08oZfoiz1/uenKK9M78yAP4PoR7SsVoTjsw==", - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/bignumber": "^5.6.0", "@ethersproject/constants": "^5.6.0", "@ethersproject/logger": "^5.6.0" } }, - "@ethersproject/wallet": { + "node_modules/@ethersproject/wallet": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.6.0.tgz", "integrity": "sha512-qMlSdOSTyp0MBeE+r7SUhr1jjDlC1zAXB8VD84hCnpijPQiSNbxr6GdiLXxpUs8UKzkDiNYYC5DRI3MZr+n+tg==", - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/abstract-provider": "^5.6.0", "@ethersproject/abstract-signer": "^5.6.0", "@ethersproject/address": "^5.6.0", @@ -363,11 +653,21 @@ "@ethersproject/wordlists": "^5.6.0" } }, - "@ethersproject/web": { + "node_modules/@ethersproject/web": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.6.0.tgz", "integrity": "sha512-G/XHj0hV1FxI2teHRfCGvfBUHFmU+YOSbCxlAMqJklxSa7QMiHFQfAxvwY2PFqgvdkxEKwRNr/eCjfAPEm2Ctg==", - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/base64": "^5.6.0", "@ethersproject/bytes": "^5.6.0", "@ethersproject/logger": "^5.6.0", @@ -375,11 +675,21 @@ "@ethersproject/strings": "^5.6.0" } }, - "@ethersproject/wordlists": { + "node_modules/@ethersproject/wordlists": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.6.0.tgz", "integrity": "sha512-q0bxNBfIX3fUuAo9OmjlEYxP40IB8ABgb7HjEZCL5IKubzV3j30CWi2rqQbjTS2HfoyQbfINoKcTVWP4ejwR7Q==", - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/bytes": "^5.6.0", "@ethersproject/hash": "^5.6.0", "@ethersproject/logger": "^5.6.0", @@ -387,73 +697,528 @@ "@ethersproject/strings": "^5.6.0" } }, - "@types/bn.js": { + "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==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/@noble/secp256k1": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.1.tgz", + "integrity": "sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/@openzeppelin/merkle-tree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@openzeppelin/merkle-tree/-/merkle-tree-1.0.6.tgz", + "integrity": "sha512-cGWOb2WBWbJhqvupzxjnKAwGLxxAEYPg51sk76yZ5nVe5D03mw7Vx5yo8llaIEqYhP5O39M8QlrNWclgLfKVrA==", + "dependencies": { + "@ethersproject/abi": "^5.7.0", + "ethereum-cryptography": "^1.1.2" + } + }, + "node_modules/@openzeppelin/merkle-tree/node_modules/@ethersproject/abi": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz", + "integrity": "sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@openzeppelin/merkle-tree/node_modules/@ethersproject/abstract-provider": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz", + "integrity": "sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/networks": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/web": "^5.7.0" + } + }, + "node_modules/@openzeppelin/merkle-tree/node_modules/@ethersproject/abstract-signer": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz", + "integrity": "sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0" + } + }, + "node_modules/@openzeppelin/merkle-tree/node_modules/@ethersproject/address": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.7.0.tgz", + "integrity": "sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/rlp": "^5.7.0" + } + }, + "node_modules/@openzeppelin/merkle-tree/node_modules/@ethersproject/base64": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.7.0.tgz", + "integrity": "sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0" + } + }, + "node_modules/@openzeppelin/merkle-tree/node_modules/@ethersproject/bignumber": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.7.0.tgz", + "integrity": "sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "bn.js": "^5.2.1" + } + }, + "node_modules/@openzeppelin/merkle-tree/node_modules/@ethersproject/bytes": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.7.0.tgz", + "integrity": "sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@openzeppelin/merkle-tree/node_modules/@ethersproject/constants": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.7.0.tgz", + "integrity": "sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bignumber": "^5.7.0" + } + }, + "node_modules/@openzeppelin/merkle-tree/node_modules/@ethersproject/hash": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.7.0.tgz", + "integrity": "sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/base64": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@openzeppelin/merkle-tree/node_modules/@ethersproject/keccak256": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.7.0.tgz", + "integrity": "sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "js-sha3": "0.8.0" + } + }, + "node_modules/@openzeppelin/merkle-tree/node_modules/@ethersproject/logger": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.7.0.tgz", + "integrity": "sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ] + }, + "node_modules/@openzeppelin/merkle-tree/node_modules/@ethersproject/networks": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.7.1.tgz", + "integrity": "sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@openzeppelin/merkle-tree/node_modules/@ethersproject/properties": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz", + "integrity": "sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@openzeppelin/merkle-tree/node_modules/@ethersproject/rlp": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.7.0.tgz", + "integrity": "sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@openzeppelin/merkle-tree/node_modules/@ethersproject/signing-key": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.7.0.tgz", + "integrity": "sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "bn.js": "^5.2.1", + "elliptic": "6.5.4", + "hash.js": "1.1.7" + } + }, + "node_modules/@openzeppelin/merkle-tree/node_modules/@ethersproject/strings": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.7.0.tgz", + "integrity": "sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@openzeppelin/merkle-tree/node_modules/@ethersproject/transactions": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.7.0.tgz", + "integrity": "sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/rlp": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0" + } + }, + "node_modules/@openzeppelin/merkle-tree/node_modules/@ethersproject/web": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.1.tgz", + "integrity": "sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/base64": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.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==", + "dependencies": { + "@noble/hashes": "1.2.0", + "@noble/secp256k1": "1.7.1", + "@scure/bip32": "1.1.5", + "@scure/bip39": "1.1.1" + } + }, + "node_modules/@scure/base": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.6.tgz", + "integrity": "sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "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==", + "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/@scure/bip39": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.1.tgz", + "integrity": "sha512-t+wDck2rVkh65Hmv280fYdVdY25J9YeEUIgn2LG1WM6gxFkGzcksoDiUkWVpVp3Oex9xGC68JU2dSbUfwZ2jPg==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "@noble/hashes": "~1.2.0", + "@scure/base": "~1.1.0" + } + }, + "node_modules/@types/bn.js": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.0.tgz", "integrity": "sha512-QSSVYj7pYFN49kW77o2s9xTCwZ8F2xLbjLLSEVh8D2F4JUhZtPAGOFLTD+ffqksBx/u4cE/KImFjyhqCjn/LIA==", - "requires": { + "dependencies": { "@types/node": "*" } }, - "@types/node": { + "node_modules/@types/node": { "version": "17.0.25", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.25.tgz", "integrity": "sha512-wANk6fBrUwdpY4isjWrKTufkrXdu1D2YHCot2fD/DfWxF5sMrVSA+KN7ydckvaTCh0HiqX9IVl0L5/ZoXg5M7w==" }, - "@types/pbkdf2": { + "node_modules/@types/pbkdf2": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@types/pbkdf2/-/pbkdf2-3.1.0.tgz", "integrity": "sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ==", - "requires": { + "dependencies": { "@types/node": "*" } }, - "@types/secp256k1": { + "node_modules/@types/secp256k1": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.3.tgz", "integrity": "sha512-Da66lEIFeIz9ltsdMZcpQvmrmmoqrfju8pm1BH8WbYjZSwUgCwXLb9C+9XYogwBITnbsSaMdVPb2ekf7TV+03w==", - "requires": { + "dependencies": { "@types/node": "*" } }, - "aes-js": { + "node_modules/aes-js": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", "integrity": "sha1-4h3xCtbCBTKVvLuNq0Cwnb6ofk0=" }, - "base-x": { + "node_modules/base-x": { "version": "3.0.9", "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", - "requires": { + "dependencies": { "safe-buffer": "^5.0.1" } }, - "bech32": { + "node_modules/bech32": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" }, - "blakejs": { + "node_modules/blakejs": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz", "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==" }, - "bn.js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", - "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==" + "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==" }, - "brorand": { + "node_modules/brorand": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" }, - "browserify-aes": { + "node_modules/browserify-aes": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "requires": { + "dependencies": { "buffer-xor": "^1.0.3", "cipher-base": "^1.0.0", "create-hash": "^1.1.0", @@ -462,43 +1227,43 @@ "safe-buffer": "^5.0.1" } }, - "bs58": { + "node_modules/bs58": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=", - "requires": { + "dependencies": { "base-x": "^3.0.2" } }, - "bs58check": { + "node_modules/bs58check": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", - "requires": { + "dependencies": { "bs58": "^4.0.0", "create-hash": "^1.1.0", "safe-buffer": "^5.1.2" } }, - "buffer-xor": { + "node_modules/buffer-xor": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" }, - "cipher-base": { + "node_modules/cipher-base": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "requires": { + "dependencies": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" } }, - "create-hash": { + "node_modules/create-hash": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "requires": { + "dependencies": { "cipher-base": "^1.0.1", "inherits": "^2.0.1", "md5.js": "^1.3.4", @@ -506,11 +1271,11 @@ "sha.js": "^2.4.0" } }, - "create-hmac": { + "node_modules/create-hmac": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "requires": { + "dependencies": { "cipher-base": "^1.0.3", "create-hash": "^1.1.0", "inherits": "^2.0.1", @@ -519,11 +1284,11 @@ "sha.js": "^2.4.8" } }, - "elliptic": { + "node_modules/elliptic": { "version": "6.5.4", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "requires": { + "dependencies": { "bn.js": "^4.11.9", "brorand": "^1.1.0", "hash.js": "^1.0.0", @@ -531,20 +1296,18 @@ "inherits": "^2.0.4", "minimalistic-assert": "^1.0.1", "minimalistic-crypto-utils": "^1.0.1" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - } } }, - "ethereum-cryptography": { + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "node_modules/ethereum-cryptography": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "requires": { + "dependencies": { "@types/pbkdf2": "^3.0.0", "@types/secp256k1": "^4.0.1", "blakejs": "^1.1.0", @@ -562,33 +1325,47 @@ "setimmediate": "^1.0.5" } }, - "ethereumjs-util": { + "node_modules/ethereumjs-util": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.4.tgz", "integrity": "sha512-p6KmuPCX4mZIqsQzXfmSx9Y0l2hqf+VkAiwSisW3UKUFdk8ZkAt+AYaor83z2nSi6CU2zSsXMlD80hAbNEGM0A==", - "requires": { + "dependencies": { "@types/bn.js": "^5.1.0", "bn.js": "^5.1.2", "create-hash": "^1.1.2", "ethereum-cryptography": "^0.1.3", "rlp": "^2.2.4" }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/ethereumjs-util/node_modules/rlp": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/rlp/-/rlp-2.2.7.tgz", + "integrity": "sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ==", "dependencies": { - "rlp": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/rlp/-/rlp-2.2.7.tgz", - "integrity": "sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ==", - "requires": { - "bn.js": "^5.2.0" - } - } + "bn.js": "^5.2.0" + }, + "bin": { + "rlp": "bin/rlp" } }, - "ethers": { + "node_modules/ethers": { "version": "5.6.4", "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.6.4.tgz", "integrity": "sha512-62UIfxAQXdf67TeeOaoOoPctm5hUlYgfd0iW3wxfj7qRYKDcvvy0f+sJ3W2/Pyx77R8dblvejA8jokj+lS+ATQ==", - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/abi": "5.6.1", "@ethersproject/abstract-provider": "5.6.0", "@ethersproject/abstract-signer": "5.6.0", @@ -621,195 +1398,259 @@ "@ethersproject/wordlists": "5.6.0" } }, - "evp_bytestokey": { + "node_modules/evp_bytestokey": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "requires": { + "dependencies": { "md5.js": "^1.3.4", "safe-buffer": "^5.1.1" } }, - "hash-base": { + "node_modules/hash-base": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "requires": { + "dependencies": { "inherits": "^2.0.4", "readable-stream": "^3.6.0", "safe-buffer": "^5.2.0" + }, + "engines": { + "node": ">=4" } }, - "hash.js": { + "node_modules/hash.js": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "requires": { + "dependencies": { "inherits": "^2.0.3", "minimalistic-assert": "^1.0.1" } }, - "hmac-drbg": { + "node_modules/hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "requires": { + "dependencies": { "hash.js": "^1.0.3", "minimalistic-assert": "^1.0.0", "minimalistic-crypto-utils": "^1.0.1" } }, - "inherits": { + "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "js-sha3": { + "node_modules/js-sha3": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" }, - "keccak": { + "node_modules/keccak": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.2.tgz", "integrity": "sha512-PyKKjkH53wDMLGrvmRGSNWgmSxZOUqbnXwKL9tmgbFYA1iAYqW21kfR7mZXV0MlESiefxQQE9X9fTa3X+2MPDQ==", - "requires": { + "hasInstallScript": true, + "dependencies": { "node-addon-api": "^2.0.0", "node-gyp-build": "^4.2.0", "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10.0.0" } }, - "md5.js": { + "node_modules/md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "requires": { + "dependencies": { "hash-base": "^3.0.0", "inherits": "^2.0.1", "safe-buffer": "^5.1.2" } }, - "minimalistic-assert": { + "node_modules/minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" }, - "minimalistic-crypto-utils": { + "node_modules/minimalistic-crypto-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" }, - "node-addon-api": { + "node_modules/node-addon-api": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==" }, - "node-gyp-build": { + "node_modules/node-gyp-build": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.4.0.tgz", - "integrity": "sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ==" + "integrity": "sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } }, - "pbkdf2": { + "node_modules/pbkdf2": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", - "requires": { + "dependencies": { "create-hash": "^1.1.2", "create-hmac": "^1.1.4", "ripemd160": "^2.0.1", "safe-buffer": "^5.0.1", "sha.js": "^2.4.8" + }, + "engines": { + "node": ">=0.12" } }, - "randombytes": { + "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "requires": { + "dependencies": { "safe-buffer": "^5.1.0" } }, - "readable-stream": { + "node_modules/readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { + "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" } }, - "ripemd160": { + "node_modules/ripemd160": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "requires": { + "dependencies": { "hash-base": "^3.0.0", "inherits": "^2.0.1" } }, - "rlp": { + "node_modules/rlp": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/rlp/-/rlp-3.0.0.tgz", - "integrity": "sha512-PD6U2PGk6Vq2spfgiWZdomLvRGDreBLxi5jv5M8EpRo3pU6VEm31KO+HFxE18Q3vgqfDrQ9pZA3FP95rkijNKw==" + "integrity": "sha512-PD6U2PGk6Vq2spfgiWZdomLvRGDreBLxi5jv5M8EpRo3pU6VEm31KO+HFxE18Q3vgqfDrQ9pZA3FP95rkijNKw==", + "bin": { + "rlp": "bin/rlp" + } }, - "safe-buffer": { + "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, - "scrypt-js": { + "node_modules/scrypt-js": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==" }, - "secp256k1": { + "node_modules/secp256k1": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.3.tgz", "integrity": "sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==", - "requires": { + "hasInstallScript": true, + "dependencies": { "elliptic": "^6.5.4", "node-addon-api": "^2.0.0", "node-gyp-build": "^4.2.0" + }, + "engines": { + "node": ">=10.0.0" } }, - "setimmediate": { + "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" }, - "sha.js": { + "node_modules/sha.js": { "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "requires": { + "dependencies": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" } }, - "string_decoder": { + "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { + "dependencies": { "safe-buffer": "~5.2.0" } }, - "typescript": { + "node_modules/typescript": { "version": "4.6.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", - "dev": true + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } }, - "util-deprecate": { + "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, - "ws": { + "node_modules/ws": { "version": "7.4.6", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", - "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==" + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } } } } diff --git a/differential_testing/scripts/package.json b/differential_testing/scripts/package.json index 8d664eb..fd371df 100644 --- a/differential_testing/scripts/package.json +++ b/differential_testing/scripts/package.json @@ -6,6 +6,7 @@ "author": "", "license": "MIT", "dependencies": { + "@openzeppelin/merkle-tree": "^1.0.6", "ethereumjs-util": "^7.1.4", "ethers": "^5.6.4", "rlp": "^3.0.0" @@ -14,9 +15,11 @@ "typescript": "^4.6.3" }, "scripts": { - "compile": "npx tsc --esModuleInterop ./*.ts", + "compile": "npx tsc --esModuleInterop --downlevelIteration ./*.ts", "generate-root": "node generate_root.js", "generate-root-129": "node generate_root.js", - "generate-root-cli": "node generate_root_cli.js" + "generate-root-cli": "node generate_root_cli.js", + "generate-complete-root": "node generate_complete_root.js", + "generate-complete-proof": "node generate_complete_proof.js" } } diff --git a/differential_testing/test/CompleteDifferentialTests.t.sol b/differential_testing/test/CompleteDifferentialTests.t.sol new file mode 100644 index 0000000..1d86624 --- /dev/null +++ b/differential_testing/test/CompleteDifferentialTests.t.sol @@ -0,0 +1,87 @@ +pragma solidity ^0.8.4; + +import "../../src/CompleteMerkle.sol"; +import "forge-std/Test.sol"; +import "./utils/Strings2.sol"; + + +contract CompleteDifferentialTests is Test { + CompleteMerkle m; + bytes32 oneword; + using {Strings2.toHexString} for bytes; + + + function setUp() public { + m = new CompleteMerkle(); + oneword = bytes32(uint256(0x1)); + } + + function testDifferentialOZMerkleTreeRootGeneration(bytes32[] memory leafs) public { + // Setup assumptions + vm.assume(leafs.length > 1); + + // Generate the input json to be used by the JS implementation + string memory obj1 = vm.serializeBytes32("input", "leafs", leafs); + vm.writeJson(obj1, "./differential_testing/data/complete_root_input.json"); + + // Prepare the leafs. OZ JS Merkle double hashes and expects each leaf to be a len(2) array (e.g. address, amount) + // For this test we use the fuzzed bytes32 and a constant bytes32 0x1 + bytes32[] memory compatibleLeafs = new bytes32[](leafs.length); + for (uint256 i = 0; i < leafs.length; ++i) { + compatibleLeafs[i] = keccak256(abi.encode(keccak256(abi.encode(leafs[i],oneword)))); + } + + // Run ffi and read back the oz generated root + string[] memory runJsInputs = new string[](7); + runJsInputs[0] = 'npm'; + runJsInputs[1] = '--prefix'; + runJsInputs[2] = 'differential_testing/scripts/'; + runJsInputs[3] = '--silent'; + runJsInputs[4] = 'run'; + runJsInputs[5] = 'generate-complete-root'; // Generates length 129 by default + runJsInputs[6] = "../data/complete_root_input.json"; + bytes memory jsResult = vm.ffi(runJsInputs); + bytes32 ozGeneratedRoot = abi.decode(jsResult, (bytes32)); + + // generate our root and compare + bytes32 root = m.getRoot(compatibleLeafs); + assertEq(root, ozGeneratedRoot); + } + + function testDifferentialOZMerkleTreeProofGeneration(bytes32[] memory leafs, uint256 index) public { + // Setup assumptions + vm.assume(leafs.length > 1); + vm.assume(index < leafs.length); + + string memory obj1 = "input"; + vm.serializeBytes32(obj1, "leafs", leafs); + string memory obj2 = vm.serializeUint(obj1, "index", index); + vm.writeJson(obj2, "./differential_testing/data/complete_proof_input.json"); + + // Prepare the leafs. OZ JS Merkle double hashes and expects each leaf to be a len(2) array (e.g. address, amount) + // For this test we use the fuzzed bytes32 and a constant bytes32 0x1 + bytes32[] memory compatibleLeafs = new bytes32[](leafs.length); + for (uint256 i = 0; i < leafs.length; ++i) { + compatibleLeafs[i] = keccak256(abi.encode(keccak256(abi.encode(leafs[i],oneword)))); + } + + bytes32[] memory proof = m.getProof(compatibleLeafs, index); + // Run ffi and read back the oz generated root + string[] memory runJsInputs = new string[](7); + runJsInputs[0] = 'npm'; + runJsInputs[1] = '--prefix'; + runJsInputs[2] = 'differential_testing/scripts/'; + runJsInputs[3] = '--silent'; + runJsInputs[4] = 'run'; + runJsInputs[5] = 'generate-complete-proof'; + runJsInputs[6] = "../data/complete_proof_input.json"; + bytes memory jsResult = vm.ffi(runJsInputs); + bytes32[] memory ozGeneratedProof = abi.decode(jsResult, (bytes32[])); + + // assert proofs are equal + assertEq(proof.length, ozGeneratedProof.length); + for (uint256 i = 0; i < proof.length; ++i) { + assertEq(proof[i], ozGeneratedProof[i]); + } + } +} diff --git a/differential_testing/test/DifferentialTests.t.sol b/differential_testing/test/DifferentialTests.t.sol index 505e09e..4c973a4 100644 --- a/differential_testing/test/DifferentialTests.t.sol +++ b/differential_testing/test/DifferentialTests.t.sol @@ -35,7 +35,7 @@ contract DifferentialTests is Test { // Read in the file generated by the reference implementation string[] memory loadJsDataInputs = new string[](2); loadJsDataInputs[0] = "cat"; - loadJsDataInputs[1] = "differential_testing/data/input"; + loadJsDataInputs[1] = "differential_testing/data/merkle_input.txt"; bytes memory loadResult = vm.ffi(loadJsDataInputs); data = abi.decode(loadResult, (bytes32[129])); diff --git a/lib/forge-std b/lib/forge-std index 76e89e5..bb4ceea 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 76e89e592ac58df4f0d16202ee230bbf0b8098fc +Subproject commit bb4ceea94d6f10eeb5b41dc2391c6c8bf8e734ef diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts index cb14ea3..9558e54 160000 --- a/lib/openzeppelin-contracts +++ b/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit cb14ea3c5c72151f254ec79866793ddf65f4c727 +Subproject commit 9558e546d9996d533eeb5151569a52d611eca08b diff --git a/reports/murky_gas_report.png b/reports/murky_gas_report.png index 7daeb85..3d06293 100644 Binary files a/reports/murky_gas_report.png and b/reports/murky_gas_report.png differ diff --git a/script/test/Merkle.s.t.sol b/script/test/Merkle.s.t.sol index db1b0bf..11a03a9 100644 --- a/script/test/Merkle.s.t.sol +++ b/script/test/Merkle.s.t.sol @@ -22,7 +22,7 @@ contract MerkleScriptOutputTest is Test, ScriptHelper { uint256 value = 10000; uint256 index = 1; - function testAbiEncode(address a, uint256 b, uint256 c) public { + function testAbiEncode(address a, uint256 b, uint256 c) public pure { vm.assume(a != address(0)); vm.assume(b != 0); vm.assume(c != 0); @@ -39,24 +39,24 @@ contract MerkleScriptOutputTest is Test, ScriptHelper { assertEq(abi.encode(a, b, c), ltrim64(abi.encode(_bytes))); } - function testComputeLeaf() public { + function testComputeLeaf() public view { bytes32 computedLeaf = keccak256(bytes.concat(keccak256(abi.encode(addr, value, index)))); assertEq(computedLeaf, leaf); } - function testVerify() public { + function testVerify() public view { bytes32 computedLeaf = keccak256(bytes.concat(keccak256(abi.encode(addr, value, index)))); assertTrue(MerkleProof.verify(proof, root, computedLeaf)); } - function testFalseAddress(address a) public { + function testFalseAddress(address a) public view { vm.assume(a != addr); bytes32 computedLeaf = keccak256(bytes.concat(keccak256(abi.encode(a, value, index)))); assertFalse(MerkleProof.verify(proof, root, computedLeaf)); } - function testFalseValue(uint256 b, uint256 c) public { + function testFalseValue(uint256 b, uint256 c) public view { vm.assume(b != value); vm.assume(c != index); diff --git a/src/CompleteMerkle.sol b/src/CompleteMerkle.sol new file mode 100644 index 0000000..d05b559 --- /dev/null +++ b/src/CompleteMerkle.sol @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import "./common/MurkyBase.sol"; + +/// @notice simple, kinda efficient (and improving!) Merkle proof generator and verifier using complete binary trees. +/// @author dmfxyz +/// @dev Note Merkle Tree implemented as a Complete Binary Tree. Merkle.sol uses full binary trees. +contract CompleteMerkle is MurkyBase { + /** + * + * HASHING FUNCTION * + * + */ + /// @notice Hash function used when verifying proofs. + /// @dev WARNING: This method is only used by verifyProof (inherited from MurkyBase.sol). + // If modified, you **MUST** ensure the inline assembly methods in buildTree and getProof are modified to match. + function hashLeafPairs(bytes32 left, bytes32 right) public pure override returns (bytes32 _hash) { + assembly { + switch lt(left, right) + case 0 { + mstore(0x0, right) + mstore(0x20, left) + } + default { + mstore(0x0, left) + mstore(0x20, right) + } + _hash := keccak256(0x0, 0x40) + } + } + + ///@dev internal function that builds initial *sparse* array that is the shell of a complete tree + function _initTree(bytes32[] memory data) internal pure returns (bytes32[] memory) { + require(data.length > 1, "wont generate root for single leaf"); + + bytes32[] memory tree = new bytes32[](2 * data.length - 1); + assembly { + let dlen := mload(data) + let titer := add(sub(mload(tree), dlen), 1) + for { let i := add(data, mul(dlen, 0x20)) } gt(i, data) { i := sub(i, 0x20) } { + mstore(add(tree, mul(titer, 0x20)), mload(i)) + titer := add(titer, 1) + } + } + return tree; + } + + /// @notice Builds array representation of a complete binary merkle tree from given data. Tree[0] will contain the trees root. + /// @dev Visibility set to private rather than internal as there is likely useful composability with this function. + /// @param data The data to build the tree from. It should already be sorted if sorting is required. + /// @return tree as a standard array representation of a complete binary tree. + function buildTree(bytes32[] memory data) private pure returns (bytes32[] memory) { + bytes32[] memory tree = _initTree(data); + assembly { + function hash_leafs(left, right) -> _hash { + switch lt(left, right) + case 0 { + mstore(0x0, right) + mstore(0x20, left) + } + default { + mstore(0x0, left) + mstore(0x20, right) + } + _hash := keccak256(0x0, 0x40) + } + for { let i := mload(tree) } gt(i, 1) { i := sub(i, 2) } { + // TODO: clean all this up, mainly broken out for early understanding and debugging + let left := mload(add(tree, mul(sub(i, 1), 0x20))) + let right := mload(add(tree, mul(i, 0x20))) + let posToWrite := add(tree, shr(1, mul(sub(i, 1), 0x20))) + mstore(posToWrite, hash_leafs(left, right)) + } + } + return tree; + } + + /// @notice Calculates the merkle tree root for a given set of data. + /// @param data The data to generate the root for. It should already be sorted if sorting is required. + /// @return root as bytes32. + function getRoot(bytes32[] memory data) public pure override returns (bytes32) { + require(data.length > 1, "wont generate root for single leaf"); + bytes32[] memory tree = buildTree(data); + return tree[0]; + } + + /// @notice Generates proof for given set of data and target index + /// @dev Note that this returns a dynamic memory array constructed via assembly. + /// This may cause issues with certain REPL interpreters (e.g. Chisel). + /// @param data The data to generate the inclusion proof for. It should already be sorted if sorting is required. + /// @param index The index of the value in the data that you wish to generate the inclusion proof for + /// @return proof array that can subsequently be used by verifyProof or other libraries + function getProof(bytes32[] memory data, uint256 index) public pure override returns (bytes32[] memory) { + require(data.length > 1, "wont generate proof for single leaf"); + bytes32[] memory tree = buildTree(data); + + assembly { + let iter := sub(sub(mload(tree), index), 0x1) + let ptr := mload(0x40) + mstore(ptr, 0x20) + let proofSizePtr := add(ptr, 0x20) + let proofIndexPtr := add(ptr, 0x40) + for {} 0x1 {} { + // while (true) + let sibling := mload(add(tree, mul(add(iter, shl(0x1, and(iter, 0x1))), 0x20))) + mstore(proofIndexPtr, sibling) + iter := shr(1, sub(iter, eq(and(iter, 0x1), 0x0))) + if eq(iter, 0x0) { break } + proofIndexPtr := add(proofIndexPtr, 0x20) + } + mstore(proofSizePtr, div(sub(proofIndexPtr, proofSizePtr), 0x20)) + return(ptr, add(0x40, sub(proofIndexPtr, proofSizePtr))) + } + } +} diff --git a/src/common/MurkyBase.sol b/src/common/MurkyBase.sol index 4bf0b96..d9344e9 100644 --- a/src/common/MurkyBase.sol +++ b/src/common/MurkyBase.sol @@ -21,7 +21,12 @@ abstract contract MurkyBase { * PROOF VERIFICATION * * */ - function verifyProof(bytes32 root, bytes32[] memory proof, bytes32 valueToProve) external pure returns (bool) { + function verifyProof(bytes32 root, bytes32[] memory proof, bytes32 valueToProve) + external + pure + virtual + returns (bool) + { // proof length must be less than max array size bytes32 rollingHash = valueToProve; uint256 length = proof.length; @@ -38,7 +43,7 @@ abstract contract MurkyBase { * PROOF GENERATION * * */ - function getRoot(bytes32[] memory data) public pure returns (bytes32) { + function getRoot(bytes32[] memory data) public pure virtual returns (bytes32) { require(data.length > 1, "won't generate root for single leaf"); while (data.length > 1) { data = hashLevel(data); @@ -46,7 +51,7 @@ abstract contract MurkyBase { return data[0]; } - function getProof(bytes32[] memory data, uint256 node) public pure returns (bytes32[] memory) { + function getProof(bytes32[] memory data, uint256 node) public pure virtual returns (bytes32[] memory) { require(data.length > 1, "won't generate proof for single leaf"); // The size of the proof is equal to the ceiling of log2(numLeaves) bytes32[] memory result = new bytes32[](log2ceilBitMagic(data.length)); diff --git a/src/test/CompleteMerkle.t.sol b/src/test/CompleteMerkle.t.sol new file mode 100644 index 0000000..50ac99f --- /dev/null +++ b/src/test/CompleteMerkle.t.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.4; + +import "../Merkle.sol"; +import "../CompleteMerkle.sol"; +import "forge-std/Test.sol"; +import "openzeppelin-contracts/contracts/utils/cryptography/MerkleProof.sol"; +import "openzeppelin-contracts/contracts/utils/Strings.sol"; +import "forge-std/console.sol"; + +contract GasComparisonTests is Test { + CompleteMerkle m; + Merkle GAS_COMP_MERKLE; + + function setUp() public { + m = new CompleteMerkle(); + } + + function testGenerateProof(bytes32[] memory data, uint256 node) public view { + vm.assume(data.length > 1); + vm.assume(node < data.length); + bytes32 root = m.getRoot(data); + bytes32[] memory proof = m.getProof(data, node); + bytes32 valueToProve = data[node]; + + bytes32 rollingHash = valueToProve; + for (uint256 i = 0; i < proof.length; ++i) { + rollingHash = m.hashLeafPairs(rollingHash, proof[i]); + } + assertEq(rollingHash, root); + } + + function testVerifyProofSucceedsForGoodValue(bytes32[] memory data, uint256 node) public view { + vm.assume(data.length > 1); + vm.assume(node < data.length); + bytes32 root = m.getRoot(data); + bytes32[] memory proof = m.getProof(data, node); + bytes32 valueToProve = data[node]; + assertTrue(m.verifyProof(root, proof, valueToProve)); + } + + function testVerifyProofFailsForBadValue(bytes32[] memory data, bytes32 valueToProve, uint256 node) public view { + vm.assume(data.length > 1); + vm.assume(node < data.length); + vm.assume(valueNotInArray(data, valueToProve)); + bytes32 root = m.getRoot(data); + bytes32[] memory proof = m.getProof(data, node); + assertFalse(m.verifyProof(root, proof, valueToProve)); + } + + function testVerifyProofOzForGasComparison(bytes32[] memory data, uint256 node) public view { + vm.assume(data.length > 1); + vm.assume(node < data.length); + bytes32 root = m.getRoot(data); + bytes32[] memory proof = m.getProof(data, node); + bytes32 valueToProve = data[node]; + assertTrue(MerkleProof.verify(proof, root, valueToProve)); + } + + function testWontGetRootSingleLeaf() public { + bytes32[] memory data = new bytes32[](1); + data[0] = bytes32(0x0); + vm.expectRevert("wont generate root for single leaf"); + m.getRoot(data); + } + + function testWontGetProofSingleLeaf() public { + bytes32[] memory data = new bytes32[](1); + data[0] = bytes32(0x0); + vm.expectRevert("wont generate proof for single leaf"); + m.getProof(data, 0x0); + } + + function valueNotInArray(bytes32[] memory data, bytes32 value) public pure returns (bool) { + for (uint256 i = 0; i < data.length; ++i) { + if (data[i] == value) return false; + } + return true; + } +} diff --git a/src/test/Merkle.t.sol b/src/test/Merkle.t.sol index fae0fe7..08fb0c7 100644 --- a/src/test/Merkle.t.sol +++ b/src/test/Merkle.t.sol @@ -12,7 +12,7 @@ contract ContractTest is Test { m = new Merkle(); } - function testHashes(bytes32 left, bytes32 right) public { + function testHashes(bytes32 left, bytes32 right) public view { bytes32 hAssem = m.hashLeafPairs(left, right); bytes memory packed; if (left <= right) { @@ -24,7 +24,7 @@ contract ContractTest is Test { assertEq(hAssem, hNaive); } - function testGenerateProof(bytes32[] memory data, uint256 node) public { + function testGenerateProof(bytes32[] memory data, uint256 node) public view { vm.assume(data.length > 1); vm.assume(node < data.length); bytes32 root = m.getRoot(data); @@ -38,7 +38,7 @@ contract ContractTest is Test { assertEq(rollingHash, root); } - function testVerifyProof(bytes32[] memory data, uint256 node) public { + function testVerifyProof(bytes32[] memory data, uint256 node) public view { vm.assume(data.length > 1); vm.assume(node < data.length); bytes32 root = m.getRoot(data); @@ -47,7 +47,7 @@ contract ContractTest is Test { assertTrue(m.verifyProof(root, proof, valueToProve)); } - function testFailVerifyProof(bytes32[] memory data, bytes32 valueToProve, uint256 node) public { + function testFailVerifyProof(bytes32[] memory data, bytes32 valueToProve, uint256 node) public view { vm.assume(data.length > 1); vm.assume(node < data.length); vm.assume(valueNotInArray(data, valueToProve)); @@ -56,7 +56,7 @@ contract ContractTest is Test { assertTrue(m.verifyProof(root, proof, valueToProve)); } - function testVerifyProofOzForGasComparison(bytes32[] memory data, uint256 node) public { + function testVerifyProofOzForGasComparison(bytes32[] memory data, uint256 node) public view { vm.assume(data.length > 1); vm.assume(node < data.length); bytes32 root = m.getRoot(data); diff --git a/src/test/MurkyBase.t.sol b/src/test/MurkyBase.t.sol index db38c5d..f1dea57 100644 --- a/src/test/MurkyBase.t.sol +++ b/src/test/MurkyBase.t.sol @@ -19,11 +19,11 @@ contract MurkyBaseTest is Test, MurkyBase { this.log2ceilBitMagic(x); } - function testLogCeil_KnownPowerOf2() public { + function testLogCeil_KnownPowerOf2() public view { assertEq(3, this.log2ceilBitMagic(8)); } - function testLogCeil_Known() public { + function testLogCeil_Known() public view { assertEq(8, this.log2ceilBitMagic((129))); } } diff --git a/src/test/StandardInput.t.sol b/src/test/StandardInput.t.sol index 8e77cdf..e7bc00e 100644 --- a/src/test/StandardInput.t.sol +++ b/src/test/StandardInput.t.sol @@ -2,11 +2,13 @@ pragma solidity ^0.8.4; import "../Xorkle.sol"; import "../Merkle.sol"; +import "../CompleteMerkle.sol"; import "forge-std/Test.sol"; contract StandardizedInputTest is Test { Xorkle x; Merkle m; + CompleteMerkle cm; bytes32[100] data; uint256[8] leaves = [4, 8, 15, 16, 23, 42, 69, 88]; @@ -18,6 +20,7 @@ contract StandardizedInputTest is Test { data = abi.decode(result, (bytes32[100])); x = new Xorkle(); m = new Merkle(); + cm = new CompleteMerkle(); } function testXorkleGenerateProofStandard() public view { @@ -34,7 +37,7 @@ contract StandardizedInputTest is Test { } } - function testXorkleVerifyProofStandard() public { + function testXorkleVerifyProofStandard() public view { bytes32[] memory _data = _getData(); bytes32 root = x.getRoot(_data); for (uint256 i = 0; i < leaves.length; ++i) { @@ -43,7 +46,7 @@ contract StandardizedInputTest is Test { } } - function testMerkleVerifyProofStandard() public { + function testMerkleVerifyProofStandard() public view { bytes32[] memory _data = _getData(); bytes32 root = m.getRoot(_data); for (uint256 i = 0; i < leaves.length; ++i) { @@ -52,6 +55,22 @@ contract StandardizedInputTest is Test { } } + function testCompleteMerkleGenerateProofStandard() public view { + bytes32[] memory _data = _getData(); + for (uint256 i = 0; i < leaves.length; ++i) { + cm.getProof(_data, leaves[i]); + } + } + + function testCompleteMerkleVerifyProofStandard() public view { + bytes32[] memory _data = _getData(); + bytes32 root = cm.getRoot(_data); + for (uint256 i = 0; i < leaves.length; ++i) { + bytes32[] memory proof = cm.getProof(_data, leaves[i]); + assertTrue(cm.verifyProof(root, proof, _data[leaves[i]])); + } + } + function _getData() public view returns (bytes32[] memory) { bytes32[] memory _data = new bytes32[](data.length); uint256 length = data.length; diff --git a/src/test/Xorkle.t.sol b/src/test/Xorkle.t.sol index 099d98c..a3643f8 100644 --- a/src/test/Xorkle.t.sol +++ b/src/test/Xorkle.t.sol @@ -4,21 +4,20 @@ pragma solidity ^0.8.4; import "../Xorkle.sol"; import "forge-std/Test.sol"; -contract ContractTest is DSTest { +contract ContractTest is Test { Xorkle m; - Vm vm = Vm(HEVM_ADDRESS); function setUp() public { m = new Xorkle(); } - function testHashes(bytes32 left, bytes32 right) public { + function testHashes(bytes32 left, bytes32 right) public view { bytes32 hAssem = m.hashLeafPairs(left, right); bytes32 hNaive = keccak256(abi.encode(left ^ right)); assertEq(hAssem, hNaive); } - function testGenerateProof(bytes32[] memory data, uint256 node) public { + function testGenerateProof(bytes32[] memory data, uint256 node) public view { vm.assume(data.length > 1); vm.assume(node < data.length); bytes32 root = m.getRoot(data); @@ -32,7 +31,7 @@ contract ContractTest is DSTest { assertEq(rollingHash, root); } - function testVerifyProof(bytes32[] memory data, uint256 node) public { + function testVerifyProof(bytes32[] memory data, uint256 node) public view { vm.assume(data.length > 1); vm.assume(node < data.length); bytes32 root = m.getRoot(data); @@ -41,7 +40,7 @@ contract ContractTest is DSTest { assertTrue(m.verifyProof(root, proof, valueToProve)); } - function testFailVerifyProof(bytes32[] memory data, bytes32 valueToProve, uint256 node) public { + function testFailVerifyProof(bytes32[] memory data, bytes32 valueToProve, uint256 node) public view { vm.assume(data.length > 1); vm.assume(node < data.length); vm.assume(valueNotInArray(data, valueToProve));