polkamarkets-js 3.4.2 → 3.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/abis/AccessControlUpgradeable.json +1 -0
- package/abis/ERC165Upgradeable.json +1 -0
- package/abis/Hashes.json +1 -0
- package/abis/Math.json +1 -1
- package/abis/MerkleProof.json +1 -0
- package/abis/MerkleRewardsDistributor.json +1 -0
- package/abis/MerkleRewardsDistributorTest.json +1 -0
- package/abis/Panic.json +1 -0
- package/abis/SafeCast.json +1 -0
- package/abis/SignedMath.json +1 -0
- package/abis/Strings.json +1 -1
- package/contracts/MerkleRewardsDistributor.sol +194 -0
- package/package.json +1 -1
- package/script/copy-abis.js +57 -0
- package/scripts/AddAdminToLand.s.sol +29 -0
- package/scripts/ClaimMerkleRoot.s.sol +34 -0
- package/scripts/CreateLand.s.sol +28 -0
- package/scripts/CreateMarkets.s.sol +71 -0
- package/scripts/DeployContracts.s.sol +94 -0
- package/scripts/DeployMerkleRewardsDistributor.s.sol +27 -0
- package/scripts/DeployToken.s.sol +17 -0
- package/scripts/DeployUSDT.s.sol +24 -0
- package/scripts/MintTokens.s.sol +21 -0
- package/scripts/PublishMerkleRoot.s.sol +50 -0
- package/scripts/ResolveMarket.s.sol +23 -0
- package/scripts/TradeMarket.s.sol +28 -0
- package/scripts/UpdateMarket.s.sol +55 -0
- package/src/Application.js +28 -1
- package/src/interfaces/index.js +1 -0
- package/src/models/IContract.js +101 -1
- package/src/models/MerkleRewardsDistributorContract.js +107 -0
- package/src/models/PolkamarketsSmartAccount.js +23 -0
- package/src/models/index.js +3 -1
- package/test/MerkleRewardsDistributor.t.sol +316 -0
- /package/abis/{test.json → Test.json} +0 -0
|
@@ -6,6 +6,11 @@ const { createPublicClient, http } = require('viem');
|
|
|
6
6
|
const { signerToSimpleSmartAccount } = require('permissionless/accounts');
|
|
7
7
|
|
|
8
8
|
class PolkamarketsSmartAccount {
|
|
9
|
+
networkConfig = null;
|
|
10
|
+
provider = null;
|
|
11
|
+
isConnectedWallet = null;
|
|
12
|
+
thirdwebAccount = null;
|
|
13
|
+
smartAccount = null;
|
|
9
14
|
|
|
10
15
|
static PIMLICO_FACTORY_ADDRESS = '0x9406Cc6185a346906296840746125a0E44976454';
|
|
11
16
|
static THIRDWEB_FACTORY_ADDRESS = '0x85e23b94e7F5E9cC1fF78BCe78cfb15B81f0DF00';
|
|
@@ -65,12 +70,30 @@ class PolkamarketsSmartAccount {
|
|
|
65
70
|
return { isConnectedWallet: false, address: null, signer: null };
|
|
66
71
|
}
|
|
67
72
|
|
|
73
|
+
getThirdwebAccount() {
|
|
74
|
+
if (this.thirdwebAccount) {
|
|
75
|
+
return this.thirdwebAccount;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// For backward compatibility, check if provider is already a thirdweb account
|
|
79
|
+
if (this.provider && typeof this.provider.sendTransaction === "function") {
|
|
80
|
+
return this.provider;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
68
86
|
async getAddress() {
|
|
69
87
|
const { isConnectedWallet, address } = await this.providerIsConnectedWallet();
|
|
70
88
|
if (isConnectedWallet) {
|
|
71
89
|
return address;
|
|
72
90
|
}
|
|
73
91
|
|
|
92
|
+
// Check if we have a stored thirdweb account (for agw support)
|
|
93
|
+
if (this.thirdwebAccount && this.thirdwebAccount.address) {
|
|
94
|
+
return this.thirdwebAccount.address;
|
|
95
|
+
}
|
|
96
|
+
|
|
74
97
|
if (this.networkConfig.useThirdWeb && this.networkConfig.isZkSync) {
|
|
75
98
|
if (this.provider.address) {
|
|
76
99
|
return this.provider.address;
|
package/src/models/index.js
CHANGED
|
@@ -14,6 +14,7 @@ const FantasyERC20Contract = require('./FantasyERC20Contract');
|
|
|
14
14
|
const WETH9Contract = require('./WETH9Contract');
|
|
15
15
|
const ArbitrationContract = require('./ArbitrationContract');
|
|
16
16
|
const ArbitrationProxyContract = require('./ArbitrationProxyContract');
|
|
17
|
+
const MerkleRewardsDistributorContract = require('./MerkleRewardsDistributorContract');
|
|
17
18
|
|
|
18
19
|
module.exports = {
|
|
19
20
|
ERC20Contract,
|
|
@@ -31,5 +32,6 @@ module.exports = {
|
|
|
31
32
|
ArbitrationContract,
|
|
32
33
|
ArbitrationProxyContract,
|
|
33
34
|
PredictionMarketV3FactoryContract,
|
|
34
|
-
PredictionMarketV3ControllerContract
|
|
35
|
+
PredictionMarketV3ControllerContract,
|
|
36
|
+
MerkleRewardsDistributorContract
|
|
35
37
|
}
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.20;
|
|
3
|
+
|
|
4
|
+
import "forge-std/Test.sol";
|
|
5
|
+
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
|
|
6
|
+
import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
|
|
7
|
+
|
|
8
|
+
import {MerkleRewardsDistributor} from "../contracts/MerkleRewardsDistributor.sol";
|
|
9
|
+
import {ERC20MinterPauser} from "../contracts/ERC20MinterPauser.sol";
|
|
10
|
+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
11
|
+
|
|
12
|
+
contract MerkleRewardsDistributorTest is Test {
|
|
13
|
+
MerkleRewardsDistributor internal distributor;
|
|
14
|
+
ERC20MinterPauser internal token;
|
|
15
|
+
|
|
16
|
+
address internal deployer = address(this);
|
|
17
|
+
address internal alice;
|
|
18
|
+
address internal bob;
|
|
19
|
+
address internal carol;
|
|
20
|
+
|
|
21
|
+
function setUp() public {
|
|
22
|
+
alice = makeAddr("ALICE");
|
|
23
|
+
bob = makeAddr("BOB");
|
|
24
|
+
carol = makeAddr("CAROL");
|
|
25
|
+
|
|
26
|
+
address implementation = address(new MerkleRewardsDistributor());
|
|
27
|
+
bytes memory initData = abi.encodeCall(MerkleRewardsDistributor.initialize, (deployer));
|
|
28
|
+
ERC1967Proxy proxy = new ERC1967Proxy(implementation, initData);
|
|
29
|
+
distributor = MerkleRewardsDistributor(address(proxy));
|
|
30
|
+
|
|
31
|
+
token = new ERC20MinterPauser("Test Token", "TEST");
|
|
32
|
+
token.mint(address(distributor), 1_000_000 ether);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// local Merkle builder (keccak abi.encode)
|
|
36
|
+
function _leaf(uint256 index, address user, address tokenAddr, uint256 amount, string memory contestId)
|
|
37
|
+
internal pure returns (bytes32)
|
|
38
|
+
{
|
|
39
|
+
return keccak256(abi.encode(index, user, tokenAddr, amount, contestId));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function _buildRoot(bytes32[] memory leaves) internal pure returns (bytes32 root) {
|
|
43
|
+
if (leaves.length == 0) return bytes32(0);
|
|
44
|
+
while (leaves.length > 1) {
|
|
45
|
+
uint256 n = (leaves.length + 1) / 2;
|
|
46
|
+
bytes32[] memory next = new bytes32[](n);
|
|
47
|
+
for (uint256 i = 0; i < leaves.length; i += 2) {
|
|
48
|
+
bytes32 left = leaves[i];
|
|
49
|
+
bytes32 right = (i + 1 < leaves.length) ? leaves[i + 1] : left;
|
|
50
|
+
next[i/2] = _parent(left, right);
|
|
51
|
+
}
|
|
52
|
+
leaves = next;
|
|
53
|
+
}
|
|
54
|
+
return leaves[0];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function _parent(bytes32 a, bytes32 b) internal pure returns (bytes32) {
|
|
58
|
+
(bytes32 left, bytes32 right) = a <= b ? (a,b) : (b,a);
|
|
59
|
+
return keccak256(abi.encodePacked(left, right));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function _proof(bytes32[] memory leaves, uint256 index) internal pure returns (bytes32[] memory p) {
|
|
63
|
+
// Build layers copying logic from _buildRoot to also record siblings
|
|
64
|
+
bytes32[] memory current = leaves;
|
|
65
|
+
bytes32[] memory proofTmp = new bytes32[](64);
|
|
66
|
+
uint256 proofLen = 0;
|
|
67
|
+
uint256 idx = index;
|
|
68
|
+
while (current.length > 1) {
|
|
69
|
+
uint256 n = (current.length + 1) / 2;
|
|
70
|
+
bytes32[] memory next = new bytes32[](n);
|
|
71
|
+
for (uint256 i = 0; i < current.length; i += 2) {
|
|
72
|
+
bytes32 left = current[i];
|
|
73
|
+
bytes32 right = (i + 1 < current.length) ? current[i + 1] : left;
|
|
74
|
+
next[i/2] = _parent(left, right);
|
|
75
|
+
if (i == (idx ^ 1) - 1 || i == (idx & ~uint256(1))) {
|
|
76
|
+
// sibling for this level
|
|
77
|
+
bytes32 sib = (idx % 2 == 0) ? right : left;
|
|
78
|
+
if (i + 1 >= current.length && idx % 2 == 1) {
|
|
79
|
+
// when paired with itself, skip duplicate sibling
|
|
80
|
+
} else {
|
|
81
|
+
proofTmp[proofLen++] = sib;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
current = next;
|
|
86
|
+
idx >>= 1;
|
|
87
|
+
}
|
|
88
|
+
p = new bytes32[](proofLen);
|
|
89
|
+
for (uint256 i = 0; i < proofLen; i++) p[i] = proofTmp[i];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function test_PublishRootAndSingleClaim() public {
|
|
93
|
+
string memory cid = "daily:2025-09-30";
|
|
94
|
+
|
|
95
|
+
bytes32[] memory leaves = new bytes32[](2);
|
|
96
|
+
leaves[0] = _leaf(0, alice, address(token), 100 ether, cid);
|
|
97
|
+
leaves[1] = _leaf(1, bob, address(token), 200 ether, cid);
|
|
98
|
+
bytes32 root = _buildRoot(leaves);
|
|
99
|
+
|
|
100
|
+
distributor.publishRoot(cid, IERC20(address(token)), root);
|
|
101
|
+
assertEq(distributor.getRoot(cid, IERC20(address(token))), root);
|
|
102
|
+
|
|
103
|
+
bytes32[] memory proofAlice = _proof(leaves, 0);
|
|
104
|
+
assertFalse(distributor.isClaimed(cid, IERC20(address(token)), 0));
|
|
105
|
+
distributor.claim(cid, IERC20(address(token)), 0, alice, 100 ether, proofAlice);
|
|
106
|
+
assertTrue(distributor.isClaimed(cid, IERC20(address(token)), 0));
|
|
107
|
+
assertEq(token.balanceOf(alice), 100 ether);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function test_RevertOnInvalidProof() public {
|
|
111
|
+
string memory cid = "weekly:2025-W40";
|
|
112
|
+
|
|
113
|
+
bytes32[] memory leaves = new bytes32[](1);
|
|
114
|
+
leaves[0] = _leaf(0, alice, address(token), 50 ether, cid);
|
|
115
|
+
bytes32 root = _buildRoot(leaves);
|
|
116
|
+
distributor.publishRoot(cid, IERC20(address(token)), root);
|
|
117
|
+
|
|
118
|
+
bytes32[] memory wrongProof = new bytes32[](0);
|
|
119
|
+
vm.expectRevert(bytes("MerkleRewards: invalid proof"));
|
|
120
|
+
distributor.claim(cid, IERC20(address(token)), 0, alice, 51 ether, wrongProof);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function test_RevertOnDoubleClaim() public {
|
|
124
|
+
string memory cid = "monthly:2025-09";
|
|
125
|
+
|
|
126
|
+
bytes32[] memory leaves = new bytes32[](1);
|
|
127
|
+
leaves[0] = _leaf(0, alice, address(token), 10 ether, cid);
|
|
128
|
+
bytes32 root = _buildRoot(leaves);
|
|
129
|
+
distributor.publishRoot(cid, IERC20(address(token)), root);
|
|
130
|
+
|
|
131
|
+
bytes32[] memory proof = _proof(leaves, 0);
|
|
132
|
+
distributor.claim(cid, IERC20(address(token)), 0, alice, 10 ether, proof);
|
|
133
|
+
|
|
134
|
+
vm.expectRevert(bytes("MerkleRewards: already claimed"));
|
|
135
|
+
distributor.claim(cid, IERC20(address(token)), 0, alice, 10 ether, proof);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function test_ClaimMany() public {
|
|
139
|
+
string memory cid1 = "daily:2025-09-30";
|
|
140
|
+
string memory cid2 = "weekly:2025-W40";
|
|
141
|
+
|
|
142
|
+
bytes32[] memory leaves1 = new bytes32[](1);
|
|
143
|
+
leaves1[0] = _leaf(0, alice, address(token), 100 ether, cid1);
|
|
144
|
+
bytes32 root1 = _buildRoot(leaves1);
|
|
145
|
+
distributor.publishRoot(cid1, IERC20(address(token)), root1);
|
|
146
|
+
|
|
147
|
+
bytes32[] memory leaves2 = new bytes32[](2);
|
|
148
|
+
leaves2[0] = _leaf(0, alice, address(token), 5 ether, cid2);
|
|
149
|
+
leaves2[1] = _leaf(1, bob, address(token), 7 ether, cid2);
|
|
150
|
+
bytes32 root2 = _buildRoot(leaves2);
|
|
151
|
+
distributor.publishRoot(cid2, IERC20(address(token)), root2);
|
|
152
|
+
|
|
153
|
+
string[] memory cids = new string[](2);
|
|
154
|
+
cids[0] = cid1; cids[1] = cid2;
|
|
155
|
+
IERC20[] memory tokens = new IERC20[](2);
|
|
156
|
+
tokens[0] = IERC20(address(token)); tokens[1] = IERC20(address(token));
|
|
157
|
+
uint256[] memory idxs = new uint256[](2);
|
|
158
|
+
idxs[0] = 0; idxs[1] = 0;
|
|
159
|
+
address[] memory users = new address[](2);
|
|
160
|
+
users[0] = alice; users[1] = alice;
|
|
161
|
+
uint256[] memory amts = new uint256[](2);
|
|
162
|
+
amts[0] = 100 ether; amts[1] = 5 ether;
|
|
163
|
+
bytes32[][] memory proofs = new bytes32[][](2);
|
|
164
|
+
proofs[0] = _proof(leaves1, 0);
|
|
165
|
+
proofs[1] = _proof(leaves2, 0);
|
|
166
|
+
|
|
167
|
+
distributor.claimMany(cids, tokens, idxs, users, amts, proofs);
|
|
168
|
+
assertTrue(distributor.isClaimed(cid1, IERC20(address(token)), 0));
|
|
169
|
+
assertTrue(distributor.isClaimed(cid2, IERC20(address(token)), 0));
|
|
170
|
+
assertEq(token.balanceOf(alice), 105 ether);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function test_AdminWithdraw() public {
|
|
174
|
+
uint256 balBefore = token.balanceOf(address(distributor));
|
|
175
|
+
distributor.withdraw(IERC20(address(token)), 1 ether);
|
|
176
|
+
assertEq(token.balanceOf(address(distributor)), balBefore - 1 ether);
|
|
177
|
+
assertEq(token.balanceOf(deployer), 1 ether);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function test_OnlyOwnerGuards() public {
|
|
181
|
+
string memory cid = "daily:2025-10-01";
|
|
182
|
+
bytes32 dummyRoot = bytes32(uint256(1));
|
|
183
|
+
|
|
184
|
+
vm.prank(alice);
|
|
185
|
+
vm.expectRevert(bytes("MerkleRewards: must have admin role"));
|
|
186
|
+
distributor.publishRoot(cid, IERC20(address(token)), dummyRoot);
|
|
187
|
+
|
|
188
|
+
vm.prank(alice);
|
|
189
|
+
vm.expectRevert();
|
|
190
|
+
distributor.withdraw(IERC20(address(token)), 1);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function test_AdminRoleCanPublishRoot() public {
|
|
194
|
+
// grant admin to alice and publish
|
|
195
|
+
distributor.addAdmin(alice);
|
|
196
|
+
assertTrue(distributor.isAdmin(alice));
|
|
197
|
+
|
|
198
|
+
string memory cid = "daily:2025-10-05";
|
|
199
|
+
bytes32[] memory leaves = new bytes32[](1);
|
|
200
|
+
leaves[0] = _leaf(0, alice, address(token), 1 ether, cid);
|
|
201
|
+
bytes32 root = _buildRoot(leaves);
|
|
202
|
+
|
|
203
|
+
vm.prank(alice);
|
|
204
|
+
distributor.publishRoot(cid, IERC20(address(token)), root);
|
|
205
|
+
assertEq(distributor.getRoot(cid, IERC20(address(token))), root);
|
|
206
|
+
|
|
207
|
+
// remove admin and ensure access revoked
|
|
208
|
+
distributor.removeAdmin(alice);
|
|
209
|
+
assertFalse(distributor.isAdmin(alice));
|
|
210
|
+
vm.prank(alice);
|
|
211
|
+
vm.expectRevert(bytes("MerkleRewards: must have admin role"));
|
|
212
|
+
distributor.publishRoot(cid, IERC20(address(token)), root);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function test_RootNotSet_RevertOnClaim() public {
|
|
216
|
+
string memory cid = "weekly:2025-W41";
|
|
217
|
+
bytes32[] memory proof = new bytes32[](0);
|
|
218
|
+
vm.expectRevert(bytes("MerkleRewards: root not set"));
|
|
219
|
+
distributor.claim(cid, IERC20(address(token)), 0, alice, 1, proof);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function test_RootUpdateInvalidatesOldProof() public {
|
|
223
|
+
string memory cid = "monthly:2025-10";
|
|
224
|
+
|
|
225
|
+
// first root
|
|
226
|
+
bytes32[] memory leaves1 = new bytes32[](1);
|
|
227
|
+
leaves1[0] = _leaf(0, alice, address(token), 10 ether, cid);
|
|
228
|
+
bytes32 root1 = _buildRoot(leaves1);
|
|
229
|
+
distributor.publishRoot(cid, IERC20(address(token)), root1);
|
|
230
|
+
bytes32[] memory proof1 = _proof(leaves1, 0);
|
|
231
|
+
|
|
232
|
+
// update root with different allocation
|
|
233
|
+
bytes32[] memory leaves2 = new bytes32[](1);
|
|
234
|
+
leaves2[0] = _leaf(0, alice, address(token), 20 ether, cid);
|
|
235
|
+
bytes32 root2 = _buildRoot(leaves2);
|
|
236
|
+
distributor.publishRoot(cid, IERC20(address(token)), root2);
|
|
237
|
+
|
|
238
|
+
// old proof must fail now
|
|
239
|
+
vm.expectRevert(bytes("MerkleRewards: invalid proof"));
|
|
240
|
+
distributor.claim(cid, IERC20(address(token)), 0, alice, 10 ether, proof1);
|
|
241
|
+
|
|
242
|
+
// new proof succeeds
|
|
243
|
+
bytes32[] memory proof2 = _proof(leaves2, 0);
|
|
244
|
+
distributor.claim(cid, IERC20(address(token)), 0, alice, 20 ether, proof2);
|
|
245
|
+
assertEq(token.balanceOf(alice), 20 ether);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function test_ClaimWithWrongTokenOrContest() public {
|
|
249
|
+
string memory cid = "daily:2025-10-02";
|
|
250
|
+
bytes32[] memory leaves = new bytes32[](1);
|
|
251
|
+
leaves[0] = _leaf(0, alice, address(token), 5 ether, cid);
|
|
252
|
+
distributor.publishRoot(cid, IERC20(address(token)), _buildRoot(leaves));
|
|
253
|
+
bytes32[] memory proof = _proof(leaves, 0);
|
|
254
|
+
|
|
255
|
+
// wrong token -> root not set for that key
|
|
256
|
+
ERC20MinterPauser other = new ERC20MinterPauser("Other", "OTH");
|
|
257
|
+
vm.expectRevert(bytes("MerkleRewards: root not set"));
|
|
258
|
+
distributor.claim(cid, IERC20(address(other)), 0, alice, 5 ether, proof);
|
|
259
|
+
|
|
260
|
+
// wrong amount -> invalid proof
|
|
261
|
+
vm.expectRevert(bytes("MerkleRewards: invalid proof"));
|
|
262
|
+
distributor.claim(cid, IERC20(address(token)), 0, alice, 6 ether, proof);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function test_IsClaimedReflectsState() public {
|
|
266
|
+
string memory cid = "daily:2025-10-03";
|
|
267
|
+
bytes32[] memory leaves = new bytes32[](1);
|
|
268
|
+
leaves[0] = _leaf(0, alice, address(token), 1 ether, cid);
|
|
269
|
+
distributor.publishRoot(cid, IERC20(address(token)), _buildRoot(leaves));
|
|
270
|
+
assertFalse(distributor.isClaimed(cid, IERC20(address(token)), 0));
|
|
271
|
+
distributor.claim(cid, IERC20(address(token)), 0, alice, 1 ether, _proof(leaves, 0));
|
|
272
|
+
assertTrue(distributor.isClaimed(cid, IERC20(address(token)), 0));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function test_ClaimMany_LengthMismatchReverts() public {
|
|
276
|
+
string[] memory cid = new string[](1);
|
|
277
|
+
cid[0] = "daily:2025-10-04";
|
|
278
|
+
IERC20[] memory toks = new IERC20[](1);
|
|
279
|
+
toks[0] = IERC20(address(token));
|
|
280
|
+
uint256[] memory idx = new uint256[](1);
|
|
281
|
+
idx[0] = 0;
|
|
282
|
+
address[] memory users = new address[](1);
|
|
283
|
+
users[0] = alice;
|
|
284
|
+
// amounts empty to force mismatch
|
|
285
|
+
uint256[] memory amts = new uint256[](0);
|
|
286
|
+
bytes32[][] memory proofs = new bytes32[][](1);
|
|
287
|
+
proofs[0] = new bytes32[](0);
|
|
288
|
+
|
|
289
|
+
vm.expectRevert(bytes("MerkleRewards: arrays length mismatch"));
|
|
290
|
+
distributor.claimMany(cid, toks, idx, users, amts, proofs);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function test_ClaimMany_DuplicateIndexReverts() public {
|
|
294
|
+
string memory cid = "weekly:2025-W42";
|
|
295
|
+
bytes32[] memory leaves = new bytes32[](1);
|
|
296
|
+
leaves[0] = _leaf(0, alice, address(token), 3 ether, cid);
|
|
297
|
+
distributor.publishRoot(cid, IERC20(address(token)), _buildRoot(leaves));
|
|
298
|
+
|
|
299
|
+
string[] memory cids = new string[](2);
|
|
300
|
+
cids[0] = cid; cids[1] = cid;
|
|
301
|
+
IERC20[] memory toks = new IERC20[](2);
|
|
302
|
+
toks[0] = IERC20(address(token)); toks[1] = IERC20(address(token));
|
|
303
|
+
uint256[] memory idxs = new uint256[](2);
|
|
304
|
+
idxs[0] = 0; idxs[1] = 0; // duplicate same index
|
|
305
|
+
address[] memory users = new address[](2);
|
|
306
|
+
users[0] = alice; users[1] = alice;
|
|
307
|
+
uint256[] memory amts = new uint256[](2);
|
|
308
|
+
amts[0] = 3 ether; amts[1] = 3 ether;
|
|
309
|
+
bytes32[][] memory proofs = new bytes32[][](2);
|
|
310
|
+
proofs[0] = _proof(leaves, 0);
|
|
311
|
+
proofs[1] = _proof(leaves, 0);
|
|
312
|
+
|
|
313
|
+
vm.expectRevert(bytes("MerkleRewards: already claimed"));
|
|
314
|
+
distributor.claimMany(cids, toks, idxs, users, amts, proofs);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
File without changes
|