Skip to content

Commit 4c6ce2d

Browse files
Orland0xOrlando
and
Orlando
authored
feat: merkle whitelist (#241)
* feat: merkle whitelist voting strategy * chore: formatting * chore: updated remappings * chore: merkle whitelist test * chore: fixed foundry lib call issue --------- Co-authored-by: Orlando <[email protected]>
1 parent df37a70 commit 4c6ce2d

File tree

5 files changed

+200
-0
lines changed

5 files changed

+200
-0
lines changed

.gitmodules

+3
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,6 @@
2525
path = lib/openzeppelin-contracts-upgradeable
2626
url = https://github.com/openzeppelin/openzeppelin-contracts-upgradeable
2727
branch = v4.8.0
28+
[submodule "lib/murky"]
29+
path = lib/murky
30+
url = https://github.com/dmfxyz/murky

lib/murky

Submodule murky added at 40de6e8

remappings.txt

+1
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ forge-gas-snapshot/=lib/forge-gas-snapshot/src
55
@prb/test/=lib/prb-test/src/
66
@zodiac/=lib/zodiac/contracts/
77
@gnosis.pm/safe-contracts=lib/safe-contracts
8+
@murky/=lib/murky/src
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.18;
4+
5+
import { MerkleProof } from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
6+
import { IVotingStrategy } from "../interfaces/IVotingStrategy.sol";
7+
8+
/// @title Whitelist Voting Strategy
9+
/// @notice Allows a variable voting power whitelist that is stored in a merkle tree to be used for voting power.
10+
contract MerkleWhitelistVotingStrategy is IVotingStrategy {
11+
/// @notice Error thrown when the proof submitted is invalid.
12+
error InvalidProof();
13+
14+
/// @notice Error thrown when the proof submitted does not correspond to the `voter` address.
15+
error InvalidMember();
16+
17+
/// @dev The data for each member of the whitelist.
18+
struct Member {
19+
// The address of the member.
20+
address addr;
21+
// The voting power of the member.
22+
uint96 vp;
23+
}
24+
25+
/// @notice Returns the voting power of an address.
26+
/// @param voter The address to get the voting power of.
27+
/// @param params Parameter array containing the root the merkle tree containing the whitelist.
28+
/// @param userParams Parameter array containing the desired member of the whitelist and its associated merkle proof.
29+
/// @return votingPower The voting power of the address if it exists in the whitelist, otherwise reverts.
30+
function getVotingPower(
31+
uint32 /* blockNumber */,
32+
address voter,
33+
bytes calldata params,
34+
bytes calldata userParams
35+
) external pure override returns (uint256 votingPower) {
36+
bytes32 root = abi.decode(params, (bytes32));
37+
(bytes32[] memory proof, Member memory member) = abi.decode(userParams, (bytes32[], Member));
38+
39+
if (member.addr != voter) revert InvalidMember();
40+
if (MerkleProof.verify(proof, root, keccak256(abi.encode(member))) != true) revert InvalidProof();
41+
42+
return member.vp;
43+
}
44+
}
+151
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.18;
3+
4+
import { Test } from "forge-std/Test.sol";
5+
import { Merkle } from "@murky/Merkle.sol";
6+
import { MerkleWhitelistVotingStrategy } from "../src/voting-strategies/MerkleWhitelistVotingStrategy.sol";
7+
8+
contract MerkleWhitelistVotingStrategyTest is Test {
9+
error InvalidProof();
10+
error InvalidMember();
11+
12+
MerkleWhitelistVotingStrategy public merkleWhitelistVotingStrategy;
13+
Merkle public merkleLib;
14+
15+
function setUp() public {
16+
merkleWhitelistVotingStrategy = new MerkleWhitelistVotingStrategy();
17+
merkleLib = new Merkle();
18+
}
19+
20+
function testMerkleWhitelistVotingPower() public {
21+
MerkleWhitelistVotingStrategy.Member[] memory members = new MerkleWhitelistVotingStrategy.Member[](4);
22+
members[0] = MerkleWhitelistVotingStrategy.Member(address(3), 33);
23+
members[1] = MerkleWhitelistVotingStrategy.Member(address(1), 11);
24+
members[2] = MerkleWhitelistVotingStrategy.Member(address(5), 55);
25+
members[3] = MerkleWhitelistVotingStrategy.Member(address(5), 77);
26+
27+
bytes32[] memory leaves = new bytes32[](4);
28+
leaves[0] = keccak256(abi.encode(members[0]));
29+
leaves[1] = keccak256(abi.encode(members[1]));
30+
leaves[2] = keccak256(abi.encode(members[2]));
31+
leaves[3] = keccak256(abi.encode(members[3]));
32+
33+
bytes32 root = merkleLib.getRoot(leaves);
34+
35+
assertEq(
36+
merkleWhitelistVotingStrategy.getVotingPower(
37+
0,
38+
members[0].addr,
39+
abi.encode(root),
40+
abi.encode(merkleLib.getProof(leaves, 0), members[0])
41+
),
42+
members[0].vp
43+
);
44+
assertEq(
45+
merkleWhitelistVotingStrategy.getVotingPower(
46+
0,
47+
members[1].addr,
48+
abi.encode(root),
49+
abi.encode(merkleLib.getProof(leaves, 1), members[1])
50+
),
51+
members[1].vp
52+
);
53+
assertEq(
54+
merkleWhitelistVotingStrategy.getVotingPower(
55+
0,
56+
members[2].addr,
57+
abi.encode(root),
58+
abi.encode(merkleLib.getProof(leaves, 2), members[2])
59+
),
60+
members[2].vp
61+
);
62+
assertEq(
63+
merkleWhitelistVotingStrategy.getVotingPower(
64+
0,
65+
members[3].addr,
66+
abi.encode(root),
67+
abi.encode(merkleLib.getProof(leaves, 3), members[3])
68+
),
69+
members[3].vp
70+
);
71+
}
72+
73+
function testMerkleWhitelistInvalidProof() public {
74+
MerkleWhitelistVotingStrategy.Member[] memory members = new MerkleWhitelistVotingStrategy.Member[](4);
75+
members[0] = MerkleWhitelistVotingStrategy.Member(address(3), 33);
76+
members[1] = MerkleWhitelistVotingStrategy.Member(address(1), 11);
77+
members[2] = MerkleWhitelistVotingStrategy.Member(address(5), 55);
78+
members[3] = MerkleWhitelistVotingStrategy.Member(address(5), 77);
79+
80+
bytes32[] memory leaves = new bytes32[](4);
81+
leaves[0] = keccak256(abi.encode(members[0]));
82+
leaves[1] = keccak256(abi.encode(members[1]));
83+
leaves[2] = keccak256(abi.encode(members[2]));
84+
leaves[3] = keccak256(abi.encode(members[3]));
85+
86+
bytes32 root = merkleLib.getRoot(leaves);
87+
88+
// Proof is empty
89+
vm.expectRevert(InvalidProof.selector);
90+
merkleWhitelistVotingStrategy.getVotingPower(
91+
0,
92+
members[0].addr,
93+
abi.encode(root),
94+
abi.encode(new bytes32[](0), members[0])
95+
);
96+
}
97+
98+
function testMerkleWhitelistInvalidMember() public {
99+
MerkleWhitelistVotingStrategy.Member[] memory members = new MerkleWhitelistVotingStrategy.Member[](4);
100+
members[0] = MerkleWhitelistVotingStrategy.Member(address(3), 33);
101+
members[1] = MerkleWhitelistVotingStrategy.Member(address(1), 11);
102+
members[2] = MerkleWhitelistVotingStrategy.Member(address(5), 55);
103+
members[3] = MerkleWhitelistVotingStrategy.Member(address(5), 77);
104+
105+
bytes32[] memory leaves = new bytes32[](4);
106+
leaves[0] = keccak256(abi.encode(members[0]));
107+
leaves[1] = keccak256(abi.encode(members[1]));
108+
leaves[2] = keccak256(abi.encode(members[2]));
109+
leaves[3] = keccak256(abi.encode(members[3]));
110+
111+
bytes32 root = merkleLib.getRoot(leaves);
112+
113+
bytes32[] memory proof = merkleLib.getProof(leaves, 2);
114+
115+
// Proof is for a different member than the voter address
116+
vm.expectRevert(InvalidMember.selector);
117+
merkleWhitelistVotingStrategy.getVotingPower(
118+
0,
119+
members[1].addr,
120+
abi.encode(root),
121+
abi.encode(proof, members[2])
122+
);
123+
}
124+
125+
function testLargeMerkleWhitelist() public {
126+
uint256 numMembers = 100;
127+
MerkleWhitelistVotingStrategy.Member[] memory members = new MerkleWhitelistVotingStrategy.Member[](numMembers);
128+
for (uint256 i = 0; i < numMembers; i++) {
129+
members[i] = MerkleWhitelistVotingStrategy.Member(address(uint160(i)), uint96(i));
130+
}
131+
132+
bytes32[] memory leaves = new bytes32[](numMembers);
133+
for (uint256 i = 0; i < numMembers; i++) {
134+
leaves[i] = keccak256(abi.encode(members[i]));
135+
}
136+
137+
bytes32 root = merkleLib.getRoot(leaves);
138+
139+
for (uint256 i = 0; i < numMembers; i++) {
140+
assertEq(
141+
merkleWhitelistVotingStrategy.getVotingPower(
142+
0,
143+
members[i].addr,
144+
abi.encode(root),
145+
abi.encode(merkleLib.getProof(leaves, i), members[i])
146+
),
147+
members[i].vp
148+
);
149+
}
150+
}
151+
}

0 commit comments

Comments
 (0)