Real DeFi CTF :D
How to Submit a Solution:
Use the below submission form to submit the solution: https://quillaudits.typeform.com/QuillCTF
Objective of CTF
Admin has gifted you 5e18 Btokens on your birthday. Using A,B,C,D,E token pairs on swap contracts, increase your BTokens. (See Foundry SetUp)
Note: You can create POCs using Foundry/Hardhat. Without proper POC, your submissions will not be accepted.
Contract Code:
pragma solidity ^0.8;
interface ISwapV2Router02 {
function factory() external pure returns (address);
function WETH() external pure returns (address);
function addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) external returns (uint amountA, uint amountB, uint liquidity);
function addLiquidityETH(
address token,
uint amountTokenDesired,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
)
external
payable
returns (uint amountToken, uint amountETH, uint liquidity);
function removeLiquidity(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) external returns (uint amountA, uint amountB);
function removeLiquidityETH(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external returns (uint amountToken, uint amountETH);
function removeLiquidityWithPermit(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline,
bool approveMax,
uint8 v,
bytes32 r,
bytes32 s
) external returns (uint amountA, uint amountB);
function removeLiquidityETHWithPermit(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline,
bool approveMax,
uint8 v,
bytes32 r,
bytes32 s
) external returns (uint amountToken, uint amountETH);
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external returns (uint[] memory amounts);
function swapTokensForExactTokens(
uint amountOut,
uint amountInMax,
address[] calldata path,
address to,
uint deadline
) external returns (uint[] memory amounts);
function swapExactETHForTokens(
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external payable returns (uint[] memory amounts);
function swapTokensForExactETH(
uint amountOut,
uint amountInMax,
address[] calldata path,
address to,
uint deadline
) external returns (uint[] memory amounts);
function swapExactTokensForETH(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external returns (uint[] memory amounts);
function swapETHForExactTokens(
uint amountOut,
address[] calldata path,
address to,
uint deadline
) external payable returns (uint[] memory amounts);
function quote(
uint amountA,
uint reserveA,
uint reserveB
) external pure returns (uint amountB);
function getAmountOut(
uint amountIn,
uint reserveIn,
uint reserveOut
) external pure returns (uint amountOut);
function getAmountIn(
uint amountOut,
uint reserveIn,
uint reserveOut
) external pure returns (uint amountIn);
function getAmountsOut(
uint amountIn,
address[] calldata path
) external view returns (uint[] memory amounts);
function getAmountsIn(
uint amountOut,
address[] calldata path
) external view returns (uint[] memory amounts);
}
Foundry setUp:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.7;
import "forge-std/Test.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {ISwapV2Router02} from "v2-periphery/interfaces/ISwapV2Router02.sol";
contract Token is ERC20 {
constructor(
string memory name,
string memory symbol,
uint initialMint
) ERC20(name, symbol) {
_mint(msg.sender, initialMint);
}
}
contract Arbitrage is Test {
address[] tokens;
Token Atoken;
Token Btoken;
Token Ctoken;
Token Dtoken;
Token Etoken;
Token Ftoken;
address owner = makeAddr("owner");
address arbitrageMan = makeAddr("arbitrageMan");
ISwapV2Router02 router =
ISwapV2Router02(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
function addL(address first, address second, uint aF, uint aS) internal {
router.addLiquidity(
address(first),
address(second),
aF,
aS,
aF,
aS,
owner,
block.timestamp
);
}
function setUp() public {
vm.createSelectFork("https://eth-mainnet.g.alchemy.com/v2/...");
vm.startPrank(owner);
Atoken = new Token("Atoken", "ATK", 100 ether);
tokens.push(address(Atoken));
Btoken = new Token("Btoken", "BTK", 100 ether);
tokens.push(address(Btoken));
Ctoken = new Token("Ctoken", "CTK", 100 ether);
tokens.push(address(Ctoken));
Dtoken = new Token("Dtoken", "DTK", 100 ether);
tokens.push(address(Dtoken));
Etoken = new Token("Etoken", "ETK", 100 ether);
tokens.push(address(Etoken));
Atoken.approve(address(router), 100 ether);
Btoken.approve(address(router), 100 ether);
Ctoken.approve(address(router), 100 ether);
Dtoken.approve(address(router), 100 ether);
Etoken.approve(address(router), 100 ether);
addL(address(Atoken), address(Btoken), 17 ether, 10 ether);
addL(address(Atoken), address(Ctoken), 11 ether, 7 ether);
addL(address(Atoken), address(Dtoken), 15 ether, 9 ether);
addL(address(Atoken), address(Etoken), 21 ether, 5 ether);
addL(address(Btoken), address(Ctoken), 36 ether, 4 ether);
addL(address(Btoken), address(Dtoken), 13 ether, 6 ether);
addL(address(Btoken), address(Etoken), 25 ether, 3 ether);
addL(address(Ctoken), address(Dtoken), 30 ether, 12 ether);
addL(address(Ctoken), address(Etoken), 10 ether, 8 ether);
addL(address(Dtoken), address(Etoken), 60 ether, 25 ether);
Btoken.transfer(arbitrageMan, 5 ether);
vm.stopPrank();
}
function testHack() public {
vm.startPrank(arbitrageMan);
uint tokensBefore = Btoken.balanceOf(arbitrageMan);
Btoken.approve(address(router), 5 ether);
// solution
uint tokensAfter = Btoken.balanceOf(arbitrageMan);
assertGt(tokensAfter, tokensBefore);
}
}