Skip to content

Commit eb888e2

Browse files
committed
update allowlist
Signed-off-by: Adam Wolf <wolfynft@gmail.com>
1 parent 94a4bd0 commit eb888e2

File tree

3 files changed

+184
-34
lines changed

3 files changed

+184
-34
lines changed

contracts/nft/erc1155m/clones/ERC1155MagicDropCloneable.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ contract ERC1155MagicDropCloneable is ERC1155MagicDropMetadataCloneable {
197197
revert AllowlistStageNotActive();
198198
}
199199

200-
if (!MerkleProofLib.verify(proof, stage.merkleRoot, keccak256(abi.encodePacked(to)))) {
200+
if (!MerkleProofLib.verify(proof, stage.merkleRoot, keccak256(bytes.concat(keccak256(abi.encode(to)))))) {
201201
revert InvalidProof();
202202
}
203203

test/erc1155m/clones/ERC1155MagicDropCloneable.t.sol

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,13 @@ contract ERC1155MagicDropCloneableTest is Test {
3535
SetupConfig internal config;
3636

3737
function setUp() public {
38+
// Prepare an array of addresses for testing allowlist
39+
address[] memory addresses = new address[](1);
40+
addresses[0] = allowedAddr;
41+
// Deploy the new MerkleTestHelper with multiple addresses
42+
merkleHelper = new MerkleTestHelper(addresses);
43+
3844
token = ERC1155MagicDropCloneable(LibClone.deployERC1967(address(new ERC1155MagicDropCloneable())));
39-
merkleHelper = new MerkleTestHelper(allowedAddr);
4045

4146
// Initialize token
4247
token.initialize("TestToken", "TT", owner);
@@ -167,19 +172,20 @@ contract ERC1155MagicDropCloneableTest is Test {
167172
// Move time to allowlist
168173
vm.warp(allowlistStart + 1);
169174

170-
vm.deal(merkleHelper.getAllowedAddress(), 1 ether);
171-
vm.prank(merkleHelper.getAllowedAddress());
172-
token.mintAllowlist{value: 0.005 ether}(
173-
merkleHelper.getAllowedAddress(), tokenId, 1, merkleHelper.getProofFor(merkleHelper.getAllowedAddress()), ""
174-
);
175+
vm.deal(allowedAddr, 1 ether);
176+
vm.prank(allowedAddr);
175177

176-
assertEq(token.balanceOf(merkleHelper.getAllowedAddress(), tokenId), 1);
178+
// Generate a proof for the allowedAddr from our new MerkleTestHelper
179+
bytes32[] memory proof = merkleHelper.getProofFor(allowedAddr);
180+
181+
token.mintAllowlist{value: 0.005 ether}(allowedAddr, tokenId, 1, proof, "");
182+
183+
assertEq(token.balanceOf(allowedAddr, tokenId), 1);
177184
}
178185

179186
function testMintAllowlistInvalidProofReverts() public {
180187
vm.warp(allowlistStart + 1);
181188

182-
address allowedAddr = merkleHelper.getAllowedAddress();
183189
bytes32[] memory proof = merkleHelper.getProofFor(allowedAddr);
184190

185191
vm.deal(allowedAddr, 1 ether);
@@ -193,7 +199,6 @@ contract ERC1155MagicDropCloneableTest is Test {
193199
// Before allowlist start
194200
vm.warp(allowlistStart - 10);
195201

196-
address allowedAddr = merkleHelper.getAllowedAddress();
197202
bytes32[] memory proof = merkleHelper.getProofFor(allowedAddr);
198203
vm.deal(allowedAddr, 1 ether);
199204
vm.prank(allowedAddr);
@@ -205,7 +210,6 @@ contract ERC1155MagicDropCloneableTest is Test {
205210
function testMintAllowlistNotEnoughValueReverts() public {
206211
vm.warp(allowlistStart + 1);
207212

208-
address allowedAddr = merkleHelper.getAllowedAddress();
209213
bytes32[] memory proof = merkleHelper.getProofFor(allowedAddr);
210214
vm.deal(allowedAddr, 0.001 ether);
211215
vm.prank(allowedAddr);
@@ -217,7 +221,6 @@ contract ERC1155MagicDropCloneableTest is Test {
217221
function testMintAllowlistWalletLimitExceededReverts() public {
218222
vm.warp(allowlistStart + 1);
219223

220-
address allowedAddr = merkleHelper.getAllowedAddress();
221224
bytes32[] memory proof = merkleHelper.getProofFor(allowedAddr);
222225
vm.deal(allowedAddr, 1 ether);
223226

@@ -238,7 +241,6 @@ contract ERC1155MagicDropCloneableTest is Test {
238241
// unlimited wallet limit for the purpose of this test
239242
token.setWalletLimit(tokenId, 0);
240243

241-
address allowedAddr = merkleHelper.getAllowedAddress();
242244
vm.deal(allowedAddr, 11 ether);
243245
vm.prank(allowedAddr);
244246

@@ -517,10 +519,10 @@ contract ERC1155MagicDropCloneableTest is Test {
517519
uint256 initialProtocolBalance = token.PROTOCOL_FEE_RECIPIENT().balance;
518520
uint256 initialPayoutBalance = payoutRecipient.balance;
519521

520-
vm.deal(merkleHelper.getAllowedAddress(), 1 ether);
521-
vm.prank(merkleHelper.getAllowedAddress());
522+
vm.deal(allowedAddr, 1 ether);
523+
vm.prank(allowedAddr);
522524
token.mintAllowlist{value: 0.005 ether}(
523-
merkleHelper.getAllowedAddress(), tokenId, 1, merkleHelper.getProofFor(merkleHelper.getAllowedAddress()), ""
525+
allowedAddr, tokenId, 1, merkleHelper.getProofFor(allowedAddr), ""
524526
);
525527

526528
uint256 expectedProtocolFee = (0.005 ether * token.PROTOCOL_FEE_BPS()) / token.BPS_DENOMINATOR();

test/helpers/MerkleTestHelper.sol

Lines changed: 166 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,185 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity ^0.8.22;
33

4-
// Dummy merkle proof generation utilities for testing
4+
/**
5+
* @title MerkleTestHelper
6+
* @dev This contract builds a Merkle tree from a list of addresses, stores the root,
7+
* and provides a function to retrieve a Merkle proof for a given address.
8+
*
9+
* NOTE: Generating Merkle trees on-chain is gas-expensive, so this is typically
10+
* done only in testing scenarios or for very short lists.
11+
*/
512
contract MerkleTestHelper {
6-
// This is a placeholder helper. In a real test, you'd generate a real merkle tree offline.
7-
// Here we hardcode a single allowlisted address and its proof.
8-
bytes32[] internal _proof;
13+
address[] internal _allowedAddrs;
914
bytes32 internal _root;
10-
address internal _allowedAddr;
1115

12-
constructor(address allowedAddr) {
13-
_allowedAddr = allowedAddr;
14-
// For simplicity, root = keccak256(abi.encodePacked(_allowedAddr))
15-
// Proof is empty since this is a single-leaf tree.
16-
_root = keccak256(abi.encodePacked(_allowedAddr));
16+
/**
17+
* @dev Constructor that takes in an array of addresses, builds a Merkle tree, and stores the root.
18+
*/
19+
constructor(address[] memory allowedAddresses) {
20+
// Copy addresses to storage
21+
for (uint256 i = 0; i < allowedAddresses.length; i++) {
22+
_allowedAddrs.push(allowedAddresses[i]);
23+
}
24+
25+
// Build leaves from the addresses
26+
bytes32[] memory leaves = _buildLeaves(_allowedAddrs);
27+
28+
// Compute merkle root
29+
_root = _computeMerkleRoot(leaves);
1730
}
1831

32+
/**
33+
* @notice Returns the Merkle root of the addresses list.
34+
*/
1935
function getRoot() external view returns (bytes32) {
2036
return _root;
2137
}
2238

39+
/**
40+
* @notice Returns the Merkle proof for a given address.
41+
* @dev If the address is not found or is not part of the _allowedAddrs array,
42+
* this will return an empty array.
43+
*/
2344
function getProofFor(address addr) external view returns (bytes32[] memory) {
24-
if (addr == _allowedAddr) {
25-
// Single-leaf tree: no proof necessary except empty array
45+
// Find the index of the address in our stored list
46+
(bool isInList, uint256 index) = _findAddressIndex(addr);
47+
if (!isInList) {
48+
// Return empty proof if address doesn't exist in the allowed list
2649
return new bytes32[](0);
27-
} else {
28-
// No valid proof
29-
bytes32[] memory emptyProof;
30-
return emptyProof;
3150
}
51+
52+
// Build leaves in memory
53+
bytes32[] memory leaves = _buildLeaves(_allowedAddrs);
54+
55+
// Build the proof for the leaf at the found index
56+
return _buildProof(leaves, index);
57+
}
58+
59+
/**
60+
* @dev Creates an array of leaves by double hashing each address:
61+
* keccak256(bytes.concat(keccak256(abi.encodePacked(address))))
62+
*/
63+
function _buildLeaves(address[] memory addrs) internal pure returns (bytes32[] memory) {
64+
bytes32[] memory leaves = new bytes32[](addrs.length);
65+
for (uint256 i = 0; i < addrs.length; i++) {
66+
leaves[i] = keccak256(bytes.concat(keccak256(abi.encode(addrs[i]))));
67+
}
68+
return leaves;
69+
}
70+
71+
/**
72+
* @dev Computes the Merkle root from an array of leaves.
73+
* Pairs each leaf, hashing them together until only one root remains.
74+
* If there is an odd number of leaves at a given level, the last leaf is "promoted" (copied up).
75+
*/
76+
function _computeMerkleRoot(bytes32[] memory leaves) internal pure returns (bytes32) {
77+
require(leaves.length > 0, "No leaves to build a merkle root");
78+
79+
uint256 n = leaves.length;
80+
while (n > 1) {
81+
for (uint256 i = 0; i < n / 2; i++) {
82+
// Sort the pair before hashing
83+
(bytes32 left, bytes32 right) = leaves[2 * i] < leaves[2 * i + 1]
84+
? (leaves[2 * i], leaves[2 * i + 1])
85+
: (leaves[2 * i + 1], leaves[2 * i]);
86+
leaves[i] = keccak256(abi.encodePacked(left, right));
87+
}
88+
// If odd, promote last leaf
89+
if (n % 2 == 1) {
90+
leaves[n / 2] = leaves[n - 1];
91+
n = (n / 2) + 1;
92+
} else {
93+
n = n / 2;
94+
}
95+
}
96+
97+
// The first element is now the root
98+
return leaves[0];
99+
}
100+
101+
/**
102+
* @dev Builds a Merkle proof for the leaf at the given index.
103+
* We recompute the pairing tree on the fly, capturing the "sibling" each time.
104+
*/
105+
function _buildProof(bytes32[] memory leaves, uint256 targetIndex) internal pure returns (bytes32[] memory) {
106+
bytes32[] memory proof = new bytes32[](_proofLength(leaves.length));
107+
uint256 proofPos = 0;
108+
uint256 n = leaves.length;
109+
uint256 index = targetIndex;
110+
111+
while (n > 1) {
112+
bool isIndexEven = (index % 2) == 0;
113+
uint256 pairIndex = isIndexEven ? index + 1 : index - 1;
114+
115+
if (pairIndex < n) {
116+
// Add the sibling to the proof without sorting
117+
proof[proofPos] = leaves[pairIndex];
118+
proofPos++;
119+
}
120+
121+
// Move up to the next level
122+
for (uint256 i = 0; i < n / 2; i++) {
123+
// Sort pairs when building the next level
124+
(bytes32 left, bytes32 right) = leaves[2 * i] < leaves[2 * i + 1]
125+
? (leaves[2 * i], leaves[2 * i + 1])
126+
: (leaves[2 * i + 1], leaves[2 * i]);
127+
leaves[i] = keccak256(abi.encodePacked(left, right));
128+
}
129+
130+
// Handle odd number of leaves
131+
if (n % 2 == 1) {
132+
leaves[n / 2] = leaves[n - 1];
133+
n = (n / 2) + 1;
134+
} else {
135+
n = n / 2;
136+
}
137+
138+
index = index / 2;
139+
}
140+
141+
// Trim unused proof elements
142+
uint256 trimSize = 0;
143+
for (uint256 i = proof.length; i > 0; i--) {
144+
if (proof[i - 1] != 0) {
145+
break;
146+
}
147+
trimSize++;
148+
}
149+
150+
bytes32[] memory trimmedProof = new bytes32[](proof.length - trimSize);
151+
for (uint256 i = 0; i < trimmedProof.length; i++) {
152+
trimmedProof[i] = proof[i];
153+
}
154+
155+
return trimmedProof;
32156
}
33157

34-
function getAllowedAddress() external view returns (address) {
35-
return _allowedAddr;
158+
/**
159+
* @dev Helper to find the index of a given address in the _allowedAddrs array.
160+
*/
161+
function _findAddressIndex(address addr) internal view returns (bool, uint256) {
162+
for (uint256 i = 0; i < _allowedAddrs.length; i++) {
163+
if (_allowedAddrs[i] == addr) {
164+
return (true, i);
165+
}
166+
}
167+
return (false, 0);
168+
}
169+
170+
/**
171+
* @dev Computes an upper bound for the proof length (worst-case).
172+
* For n leaves, the maximum proof length is ~log2(n).
173+
* Here we just do a simple upper bound for clarity.
174+
*/
175+
function _proofLength(uint256 n) internal pure returns (uint256) {
176+
// If n=1, no proof. Otherwise, each tree level can contribute 1 node in the proof path.
177+
// A simplistic approach: log2(n) <= 256 bits for typical usage, but we do this in-line:
178+
uint256 count = 0;
179+
while (n > 1) {
180+
n = (n + 1) / 2; // integer division round up
181+
count++;
182+
}
183+
return count;
36184
}
37185
}

0 commit comments

Comments
 (0)