This blog is intended to uncover the level 2 fallout, showcasing how a developer typo can create a huge impact. The typo impacted the constructor function and converted the accessibility of the function to the public. The challenge focuses on a solidity constructor and is easier than the fallback
challenge.
Solidity Functions
Like any other programming language, Solidity functions are self-contained modules of code that accomplish a specific task.
Visibility
Internal functions can only be called inside the current contract.
External functions can be called from outside but can’t be called from inside. (functions from the same contract, functions from inherited contracts)
Private functions can only be called from inside the contract; even the inherited contracts can’t call them.
Public functions can be called from anywhere.
Solidity Constructors
Constructor is a particular function declared using the constructor
keyword. It is an optional function used to initialize the state variables of a contract.
Main Characteristics
- Only one constructor for a contract
- Constructor code is executed once when a contract is created
- A constructor can be either public or internal.
- If no constructor is defined, a default constructor is present in the contract.
Challenge
The challenge code is vulnerable due to a typo in the constructor.
/// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import 'openzeppelin-contracts-06/math/SafeMath.sol';
contract Fallout {
using SafeMath for uint256;
mapping (address => uint) allocations;
address payable public owner;
/* constructor */
function Fal1out() public payable {
owner = msg.sender;
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 {
msg.sender.transfer(address(this).balance);
}
function allocatorBalance(address allocator) public view returns (uint) {
return allocations[allocator];
}
}
Solving the challenge
The challenge’s objective is to become the contract owner, which can be achieved by calling the function fal1out
.
Before 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.
Before 0.4.22
, functions with the same name as the contract were considered a constructor. But Solidity changed this syntax, and constructor
keyword was made mandatory from version 0.4.22
(Release Notes). Hence the above code didn’t create a constructor as the developer commented. It was a publicly accessible function.
/* constructor */
function Fal1out() public payable {
owner = msg.sender;
allocations[owner] = msg.value;
}
Becoming the owner
The function Fal1out
is public, and the owner is changed when it is called. Solve the challenge by calling the function and collect all funds from the contract by calling collectAllocations
function.
contract.Fal1out();
await contract.collectAllocations();
It’s a challenge, and obviously, it will be vulnerable. But in a real-time scenario, an excellent example of a similar bug is the famous Rubixi hack that dates back to 2016. It was initially called DynamicPyramid, but the team changed the contract name before deployment to Rubixi. The constructor’s name wasn’t changed, allowing any user to become the creator.