-
Notifications
You must be signed in to change notification settings - Fork 75
feat: add MerkleLib tests #15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Signed-off-by: Matt Rice <matthewcrice32@gmail.com>
| import "@typechain/hardhat"; | ||
| import "hardhat-gas-reporter"; | ||
| import "solidity-coverage"; | ||
| import "hardhat-deploy"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To use fixtures, hardhat deploy was needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
welcome back my old friend
| @@ -0,0 +1,35 @@ | |||
| // SPDX-License-Identifier: GPL-3.0-only | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test contract
| * @notice Verifies that a repayment is contained within a merkle root. | ||
| * @param root the merkle root. | ||
| * @param repayment the repayment struct. | ||
| * @param rebalance the rebalance struct. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed a naming inconsistency
| @@ -0,0 +1,141 @@ | |||
| // This script provides some useful methods for building MerkleTrees. It is essentially the uniswap implementation | |||
| // https://github.com/Uniswap/merkle-distributor/blob/master/src/merkle-tree.ts with some added convenience methods | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See this comment for the differences between this file and the one in protocol.
test/utils.ts
Outdated
| } | ||
|
|
||
| export async function getContractFactory(name: string, signer: SignerWithAddress) { | ||
| export async function getContractFactory(name: string, signer: Signer) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made these normal signers because it seemed like there was nothing in these methods that explicitly required them to be SignerWithAddress (and Signer is directly importable via ethers).
| import { getContractFactory } from "./utils"; | ||
| import hre from "hardhat"; | ||
|
|
||
| export const merkleLibFixture = hre.deployments.createFixture(async ({ deployments }) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn't sure if this was the type of fixtures we were wanting to use, but this is the type I'm familiar with (where hardhat keeps a snapshot of the fixture so it doesn't have to redeploy).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ye, so this is why you needed the hardhat-deploy package but @nicholaspai and my "fixtures" thus far have not. we simply created functions that did the deployment, which is not great. we should converge on one standard that does the same thing throughout the repo.
I think what you have here is better, though, as it will actually do the snapshotting and no re-deployment. I can refactor the other "fixtures" once this PR is in.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good. I think that's the right path to avoid this PR growing too large. I'm also happy to make this change as a follow-up.
| PoolRebalance memory rebalance, | ||
| bytes32[] memory proof | ||
| ) public pure returns (bool) { | ||
| return MerkleProof.verify(proof, root, keccak256(abi.encode(repayment))) || true; // Run code but set to true. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: I temporarily removed the || true to prove that these tests were working. I can put it back before merging if helpful, but now that we have the MerkleTree ts library, we should be able to construct test data with relative ease. Let me know what y'all prefer!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
whatever is easiest!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll keep it without the || true. Feel free to add back if needed!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ye, let's avoid this part to force us to use correct trees, generated from this lib. the only place I touch these methods has not yet had any meaningful implementation added so this won't impact anything in the hubPool.
test/MerkleLib.Claims.ts
Outdated
| import { Contract, BigNumber } from "ethers"; | ||
|
|
||
| describe("MerkleLib Claims", async function () { | ||
| let merkleLibTest: Contract; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: in the other test files we've done the declaration of these types of variables outside of the describe block.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, sounds good.
test/MerkleLib.Fixture.ts
Outdated
| const merkleLibTest = await ( | ||
| await hre.ethers.getContractFactory("MerkleLibTest", { | ||
| signer, | ||
| libraries: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can this be linted to fewer lines?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you suggesting changing the prettier settings?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just seems like it can be rolled into
...
signer,
libraries: { MerkleLib: merkleLib.address }
...one one line.
test/MerkleLib.Fixture.ts
Outdated
| const [signer] = await hre.ethers.getSigners(); | ||
| const merkleLib = await (await getContractFactory("MerkleLib", signer)).deploy(); | ||
| const merkleLibTest = await ( | ||
| await hre.ethers.getContractFactory("MerkleLibTest", { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there is an argument to put this linking deployment within the getContractFactory from utils so you dont need to create your own ethers factory with getContractFactory and then this would be hidden from this fixture. what I describe is more similar to what was done in the hub pool tests. downside with that is the utils needs branching logic based on the contract name to do the associated library linking. curious what pattern you think is best and if it warrants changing what's in the HubPool's fixture, here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I think I might've left a comment on that PR (can't remember if I deleted it before sending). I don't love branching on individual contract names as it just gets to be really convoluted after a while. It also requires people writing tests to modify common libraries, which seems like an antipattern.
One compromise between the two approaches is have the second argument mirror the structure of the ethers method, Signer | Options where options could contain the signer and the libraries. Then the user can still use the function, but they can pick the library deployment they want to link.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I made the change I mention above. PTAL and let me know what you think.
| const claim1499 = BigNumber.from(2).pow(219); | ||
| const claim1500 = BigNumber.from(2).pow(220); | ||
| const claim1501 = BigNumber.from(2).pow(221); | ||
| expect(await merkleLibTest.claimedBitMap(5)).to.equal(claim1499.add(claim1500).add(claim1501)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice
test/MerkleLib.Proofs.ts
Outdated
| function randomBigNumber() { | ||
| return ethers.BigNumber.from(ethers.utils.randomBytes(31)); | ||
| } | ||
|
|
||
| function randomAddress() { | ||
| return ethers.utils.hexlify(ethers.utils.randomBytes(20)); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we put these in the utils file? we've been doing something similar to this for other tests with common utils.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
test/MerkleLib.Proofs.ts
Outdated
| } | ||
|
|
||
| describe("MerkleLib Proofs", async function () { | ||
| let merkleLibTest: Contract; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same nit about where this should be defined.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will fix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
| const proof = merkleTree.getHexProof(poolRebalances[34]); | ||
| expect(await merkleLibTest.verifyPoolRebalance(root, poolRebalances[34], proof)).to.equal(true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please test the negative case as well. the test you have here only tests that a valid proof passes but not that an invalid proof returns false.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, good point. Can add.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
| amountToSeedWith: number | BigNumber | ||
| ) { | ||
| for (const token of tokens) await token.mint(walletToFund.address, amountToSeedWith); | ||
| for (const token of tokens) await token.mint(await walletToFund.getAddress(), amountToSeedWith); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SignerWithAddress is more restrictive than Signer. It's pretty easy to get the address from the Signer and the Signer type is much easier to extract from the ethers package, so I think we should prefer using it over the more specific and cumbersome SignerWithAddress when we're building generic library functions.
chrismaree
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! thanks for adding this
This PR adds MerkleLib tests. To do this, it needed:
Note: the smart contract changes are only cosmetic, as there was a minor naming inconsistency.