Skip to content

Conversation

@mrice32
Copy link
Contributor

@mrice32 mrice32 commented Feb 1, 2022

This PR adds MerkleLib tests. To do this, it needed:

  • A library to construct merkle trees from custom leaf types.
  • A test contract to proxy calls to the lib and provide storage.

Note: the smart contract changes are only cosmetic, as there was a minor naming inconsistency.

Signed-off-by: Matt Rice <matthewcrice32@gmail.com>
Signed-off-by: Matt Rice <matthewcrice32@gmail.com>
import "@typechain/hardhat";
import "hardhat-gas-reporter";
import "solidity-coverage";
import "hardhat-deploy";
Copy link
Contributor Author

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.

Copy link
Member

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
Copy link
Contributor Author

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.
Copy link
Contributor Author

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
Copy link
Contributor Author

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) {
Copy link
Contributor Author

@mrice32 mrice32 Feb 1, 2022

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 }) => {
Copy link
Contributor Author

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).

Copy link
Member

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.

Copy link
Contributor Author

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.
Copy link
Contributor Author

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!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whatever is easiest!

Copy link
Contributor Author

@mrice32 mrice32 Feb 1, 2022

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!

Copy link
Member

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.

import { Contract, BigNumber } from "ethers";

describe("MerkleLib Claims", async function () {
let merkleLibTest: Contract;
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, sounds good.

const merkleLibTest = await (
await hre.ethers.getContractFactory("MerkleLibTest", {
signer,
libraries: {
Copy link
Member

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?

Copy link
Contributor Author

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?

Copy link
Member

@chrismaree chrismaree Feb 1, 2022

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.

const [signer] = await hre.ethers.getSigners();
const merkleLib = await (await getContractFactory("MerkleLib", signer)).deploy();
const merkleLibTest = await (
await hre.ethers.getContractFactory("MerkleLibTest", {
Copy link
Member

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

Copy link
Contributor Author

@mrice32 mrice32 Feb 1, 2022

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.

Copy link
Contributor Author

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.

Comment on lines +24 to +27
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));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice

Comment on lines 25 to 31
function randomBigNumber() {
return ethers.BigNumber.from(ethers.utils.randomBytes(31));
}

function randomAddress() {
return ethers.utils.hexlify(ethers.utils.randomBytes(20));
}
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}

describe("MerkleLib Proofs", async function () {
let merkleLibTest: Contract;
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will fix.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment on lines +72 to +73
const proof = merkleTree.getHexProof(poolRebalances[34]);
expect(await merkleLibTest.verifyPoolRebalance(root, poolRebalances[34], proof)).to.equal(true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice

Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this change?

Copy link
Contributor Author

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.

Signed-off-by: Matt Rice <matthewcrice32@gmail.com>
@mrice32 mrice32 requested a review from chrismaree February 1, 2022 23:25
Copy link
Member

@chrismaree chrismaree left a 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

Signed-off-by: Matt Rice <matthewcrice32@gmail.com>
@mrice32 mrice32 merged commit 7f00eeb into master Feb 2, 2022
@mrice32 mrice32 deleted the add_merkle_lib branch February 2, 2022 02:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants