Skip to content

Commit

Permalink
Merge pull request #1067 from fei-protocol/feat/merkle-redeemer-deploy
Browse files Browse the repository at this point in the history
  • Loading branch information
Caleb Ditchfield authored Sep 16, 2022
2 parents e5551db + eba9a79 commit 551bf0d
Show file tree
Hide file tree
Showing 15 changed files with 1,881 additions and 40 deletions.
2 changes: 1 addition & 1 deletion block.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
15532708
15549000
245 changes: 245 additions & 0 deletions contracts/test/utils/claimChecker.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
/**
*Submitted for verification at Etherscan.io on 2022-09-16
*/

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/cryptography/MerkleProof.sol)

pragma solidity ^0.8.0;

contract MerkleTest {
function checkClaim(
bytes32 root,
address sender,
uint256 _amount,
bytes32[] calldata _merkleProof
) public pure returns (bool) {
bytes32 leafHash = keccak256(abi.encodePacked(sender, _amount));
return MerkleProof.verifyCalldata(_merkleProof, root, leafHash);
}

function leaf(address sender, uint256 amount) public pure returns (bytes32) {
return keccak256(abi.encodePacked(sender, amount));
}

function hashPair(bytes32 a, bytes32 b) public pure returns (bytes32) {
return a < b ? _efficientHash(a, b) : _efficientHash(b, a);
}

function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, a)
mstore(0x20, b)
value := keccak256(0x00, 0x40)
}
}
}

/**
* @dev These functions deal with verification of Merkle Tree proofs.
*
* The proofs can be generated using the JavaScript library
* https://github.com/miguelmota/merkletreejs[merkletreejs].
* Note: the hashing algorithm should be keccak256 and pair sorting should be enabled.
*
* See `test/utils/cryptography/MerkleProof.test.js` for some examples.
*
* WARNING: You should avoid using leaf values that are 64 bytes long prior to
* hashing, or use a hash function other than keccak256 for hashing leaves.
* This is because the concatenation of a sorted pair of internal nodes in
* the merkle tree could be reinterpreted as a leaf value.
*/
library MerkleProof {
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*/
function verify(
bytes32[] memory proof,
bytes32 root,
bytes32 leaf
) internal pure returns (bool) {
return processProof(proof, leaf) == root;
}

/**
* @dev Calldata version of {verify}
*
* _Available since v4.7._
*/
function verifyCalldata(
bytes32[] calldata proof,
bytes32 root,
bytes32 leaf
) internal pure returns (bool) {
return processProofCalldata(proof, leaf) == root;
}

/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leafs & pre-images are assumed to be sorted.
*
* _Available since v4.4._
*/
function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = _hashPair(computedHash, proof[i]);
}
return computedHash;
}

/**
* @dev Calldata version of {processProof}
*
* _Available since v4.7._
*/
function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = _hashPair(computedHash, proof[i]);
}
return computedHash;
}

/**
* @dev Returns true if the `leaves` can be proved to be a part of a Merkle tree defined by
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
*
* _Available since v4.7._
*/
function multiProofVerify(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32 root,
bytes32[] memory leaves
) internal pure returns (bool) {
return processMultiProof(proof, proofFlags, leaves) == root;
}

/**
* @dev Calldata version of {multiProofVerify}
*
* _Available since v4.7._
*/
function multiProofVerifyCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32 root,
bytes32[] memory leaves
) internal pure returns (bool) {
return processMultiProofCalldata(proof, proofFlags, leaves) == root;
}

/**
* @dev Returns the root of a tree reconstructed from `leaves` and the sibling nodes in `proof`,
* consuming from one or the other at each step according to the instructions given by
* `proofFlags`.
*
* _Available since v4.7._
*/
function processMultiProof(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32[] memory leaves
) internal pure returns (bytes32 merkleRoot) {
// This function rebuild the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the merkle tree.
uint256 leavesLen = leaves.length;
uint256 totalHashes = proofFlags.length;

// Check proof validity.
require(leavesLen + proof.length - 1 == totalHashes, "MerkleProof: invalid multiproof");

// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](totalHashes);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value for the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < totalHashes; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i] ? leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++] : proof[proofPos++];
hashes[i] = _hashPair(a, b);
}

if (totalHashes > 0) {
return hashes[totalHashes - 1];
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}

/**
* @dev Calldata version of {processMultiProof}
*
* _Available since v4.7._
*/
function processMultiProofCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32[] memory leaves
) internal pure returns (bytes32 merkleRoot) {
// This function rebuild the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the merkle tree.
uint256 leavesLen = leaves.length;
uint256 totalHashes = proofFlags.length;

// Check proof validity.
require(leavesLen + proof.length - 1 == totalHashes, "MerkleProof: invalid multiproof");

// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](totalHashes);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value for the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < totalHashes; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i] ? leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++] : proof[proofPos++];
hashes[i] = _hashPair(a, b);
}

if (totalHashes > 0) {
return hashes[totalHashes - 1];
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}

function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
return a < b ? _efficientHash(a, b) : _efficientHash(b, a);
}

function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, a)
mstore(0x20, b)
value := keccak256(0x00, 0x40)
}
}
}
63 changes: 49 additions & 14 deletions proposals/dao/tip_121b.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import {
Fei,
MerkleRedeemerDripper,
MerkleRedeemerDripper__factory,
RariMerkleRedeemer
RariMerkleRedeemer,
MerkleTest
} from '@custom-types/contracts';
import { RariMerkleRedeemer__factory } from '@custom-types/contracts/factories/RariMerkleRedeemer__factory';
import {
Expand All @@ -16,14 +17,16 @@ import {
} from '@custom-types/types';
import { Contract } from '@ethersproject/contracts';
import { cTokens } from '@proposals/data/merkle_redemption/cTokens';
import rates from '@proposals/data/merkle_redemption/sample/rates.json';
import roots from '@proposals/data/merkle_redemption/sample/roots.json';
import rates from '@proposals/data/merkle_redemption/prod/rates.json';
import roots from '@proposals/data/merkle_redemption/prod/roots.json';
import { MainnetContractsConfig } from '@protocol/mainnetAddresses';
import { getImpersonatedSigner } from '@test/helpers';
import { forceEth } from '@test/integration/setup/utils';
import { expect } from 'chai';
import { parseEther } from 'ethers/lib/utils';
import { ethers } from 'hardhat';
import balances from '../data/merkle_redemption/prod/mergedBalances.json';
import proofs from '../data/merkle_redemption/prod/proofs.json';

/*
Expand All @@ -50,20 +53,20 @@ let pcvStatsBefore: PcvStats;
const ratesArray: string[] = [];
const rootsArray: string[] = [];

// Construct rates and roots arrays
for (const token of cTokens) {
ratesArray.push(rates[token.toLowerCase() as keyof typeof rates]);
rootsArray.push(roots[token.toLowerCase() as keyof typeof roots]);
}

// Do any deployments
// This should exclusively include new contract deployments
const deploy: DeployUpgradeFunc = async (deployAddress: string, addresses: NamedAddresses, logging: boolean) => {
// Construct rates and roots arrays
for (const token of cTokens) {
ratesArray.push(rates[token.toLowerCase() as keyof typeof rates]);
rootsArray.push(roots[token.toLowerCase() as keyof typeof roots]);
}

// Quick check: ensure that the rates, roots, and ctokens are all in the same order
for (let i = 0; i < cTokens.length; i++) {
const token = cTokens[i];
expect(rates[token.toLowerCase() as keyof typeof rates]).to.equal(ratesArray[i]);
expect(roots[token.toLowerCase() as keyof typeof roots]).to.equal(rootsArray[i]);
expect(rates[token as keyof typeof rates]).to.equal(ratesArray[i]);
expect(roots[token as keyof typeof roots]).to.equal(rootsArray[i]);
}

// Log our output for visual inspection
Expand All @@ -78,6 +81,7 @@ const deploy: DeployUpgradeFunc = async (deployAddress: string, addresses: Named
ratesArray, // rates (uint256[])
rootsArray // roots (bytes32[])
);
await rariMerkleRedeemer.deployTransaction.wait();

const merkleRedeeemrDripperFactory = new MerkleRedeemerDripper__factory((await ethers.getSigners())[0]);
const merkleRedeemerDripper = await merkleRedeeemrDripperFactory.deploy(
Expand All @@ -87,6 +91,7 @@ const deploy: DeployUpgradeFunc = async (deployAddress: string, addresses: Named
dripAmount,
addresses.fei
);
await merkleRedeemerDripper.deployTransaction.wait();

return {
rariMerkleRedeemer,
Expand All @@ -113,15 +118,45 @@ const teardown: TeardownUpgradeFunc = async (addresses, oldContracts, contracts,
// Run any validations required on the fip using mocha or console logging
// IE check balances, check state of contracts, etc.
const validate: ValidateUpgradeFunc = async (addresses, oldContracts, contracts, logging) => {
const claimChecker = (await ethers.getContractAt(
'MerkleTest',
'0x4f3348dd19ec65bdcC3cDD7d8e8A078a0B2C46fD'
)) as MerkleTest;

// for every ctoken/root
for (let i = 0; i < cTokens.length; i++) {
const ctoken = cTokens[i];
const root = rootsArray[i];

// for every user in that ctoken's data
const cTokenBalances = balances[ctoken.toLowerCase() as keyof typeof balances];
const cTokenProofs = proofs[ctoken.toLowerCase() as keyof typeof proofs];

for (const userBalanceData of Object.entries(cTokenBalances)) {
const userAddress = userBalanceData[0];
const userBalance = userBalanceData[1];
const proof = cTokenProofs[userAddress as keyof typeof cTokenProofs];

// check that the user can claim the correct amount
const claimable = await claimChecker.checkClaim(root, userAddress, userBalance, proof);
expect(claimable).to.be.true;
}
}

const rariMerkleRedeemer = contracts.rariMerkleRedeemer as RariMerkleRedeemer;
const merkleRedeemerDripper = contracts.merkleRedeemerDripper as MerkleRedeemerDripper;

await validatePCV(contracts);

console.log(rootsArray);
console.log(ratesArray);

// validate that all 20 ctokens exist & are set
for (let i = 0; i < cTokens.length; i++) {
expect(await rariMerkleRedeemer.merkleRoots(cTokens[i])).to.be.equal(rootsArray[i]);
expect(await rariMerkleRedeemer.cTokenExchangeRates(cTokens[i])).to.be.equal(ratesArray[i]);
expect(await rariMerkleRedeemer.merkleRoots(cTokens[i].toLowerCase())).to.be.equal(rootsArray[i].toLowerCase());
expect(await rariMerkleRedeemer.cTokenExchangeRates(cTokens[i].toLowerCase())).to.be.equal(
ratesArray[i].toLowerCase()
);
}

//console.log(`Sending ETH to both contracts...`);
Expand All @@ -133,7 +168,7 @@ const validate: ValidateUpgradeFunc = async (addresses, oldContracts, contracts,
// check initial balances of dripper & redeemer
// ensure that initial balance of the dripper is a multiple of drip amount
const fei = contracts.fei as Fei;
expect(await fei.balanceOf(rariMerkleRedeemer.address)).to.be.equal(rariMerkleRedeemerInitialBalance);
expect(await fei.balanceOf(rariMerkleRedeemer.address)).to.be.gte(rariMerkleRedeemerInitialBalance);
expect(await fei.balanceOf(merkleRedeemerDripper.address)).to.be.equal(merkleRedeemerDripperInitialBalance);
expect((await fei.balanceOf(merkleRedeemerDripper.address)).mod(dripAmount)).to.be.equal(0);

Expand Down
Loading

0 comments on commit 551bf0d

Please sign in to comment.