Skip to content
This repository was archived by the owner on May 8, 2023. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions ethernaut/recovery/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
node_modules
.env
coverage
coverage.json
typechain
typechain-types

# Hardhat files
cache
artifacts

yarn.lock
52 changes: 52 additions & 0 deletions ethernaut/recovery/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Recovery

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.

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Recovery {
event TokenCreated(address tokenAddress);

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

contract SimpleToken {
string public name;
mapping(address => uint) public balances;

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

// 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;
balances[_to] = _amount;
}

// clean up after ourselves
function destroy(address payable _to) public {
selfdestruct(_to);
}
}
```

Find solution in `SOLUTION.md`

[Inspired by](https://ethernaut.openzeppelin.com/level/0xb4B157C7c4b0921065Dded675dFe10759EecaA6D)
10 changes: 10 additions & 0 deletions ethernaut/recovery/SOLUTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Recovery solution

> 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.

1. Use [getContractAddress](https://docs.ethers.org/v5/api/utils/address/#utils-getContractAddress) to calculate an address of the first deployed Simple token assuming nonce to equal 1.

2. Use Simple token's `destroy` function to transfer its RBTC to any address

See how it works in `test/Recovery.test.ts`
44 changes: 44 additions & 0 deletions ethernaut/recovery/contracts/Recovery.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Recovery {
event TokenCreated(address tokenAddress);

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

contract SimpleToken {
string public name;
mapping(address => uint) public balances;

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

// 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;
balances[_to] = _amount;
}

// clean up after ourselves
function destroy(address payable _to) public {
selfdestruct(_to);
}
}
8 changes: 8 additions & 0 deletions ethernaut/recovery/hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";

const config: HardhatUserConfig = {
solidity: "0.8.18",
};

export default config;
27 changes: 27 additions & 0 deletions ethernaut/recovery/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "recovery",
"version": "1.0.0",
"license": "MIT",
"devDependencies": {
"@ethersproject/abi": "^5.4.7",
"@ethersproject/providers": "^5.4.7",
"@nomicfoundation/hardhat-chai-matchers": "^1.0.0",
"@nomicfoundation/hardhat-network-helpers": "^1.0.0",
"@nomicfoundation/hardhat-toolbox": "^2.0.0",
"@nomiclabs/hardhat-ethers": "^2.0.0",
"@nomiclabs/hardhat-etherscan": "^3.0.0",
"@typechain/ethers-v5": "^10.1.0",
"@typechain/hardhat": "^6.1.2",
"@types/chai": "^4.2.0",
"@types/mocha": ">=9.1.0",
"@types/node": ">=12.0.0",
"chai": "^4.2.0",
"ethers": "^5.4.7",
"hardhat": "^2.13.0",
"hardhat-gas-reporter": "^1.0.8",
"solidity-coverage": "^0.8.0",
"ts-node": ">=8.0.0",
"typechain": "^8.1.0",
"typescript": ">=4.5.0"
}
}
67 changes: 67 additions & 0 deletions ethernaut/recovery/test/Recovery.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { expect } from 'chai';
import { ethers } from 'hardhat';
import { Recovery, SimpleToken } from '../typechain-types';

describe('Recovery', () => {
let recovery: Recovery;

async function deploy() {
const RecoveryFactory = await ethers.getContractFactory('Recovery');
recovery = await RecoveryFactory.deploy();
}

before(deploy);

describe('first Simple token was generated by the creator', () => {
let simpleTokenAddr: string;

it('creator should generate new token', async () => {
const tokenName = 'MEOW';
const tx = await recovery.generateToken(tokenName, 0);
await expect(tx).to.emit(recovery, 'TokenCreated');
// get created token address from event
const filter = recovery.filters.TokenCreated(null);
const [event] = await recovery.queryFilter(filter);
simpleTokenAddr = event.args.tokenAddress;
});

it('creator should send 0.001 RBTC to generated token', async () => {
const [creator] = await ethers.getSigners();
const value = ethers.utils.parseEther('0.001');
const tx = creator.sendTransaction({
to: simpleTokenAddr,
value,
});
await expect(() => tx).to.changeEtherBalance(simpleTokenAddr, value);
});
});

describe('since then simple token address was lost', () => {
let simpleToken: SimpleToken;
let expectedBalance = ethers.utils.parseEther('0.001');

it('should recover Simple token address', async () => {
// token factory nonce before the first token creation
const nonce = 1;
// address of the first token created by the token factory:
const tokenAddress = ethers.utils.getContractAddress({
from: recovery.address,
nonce,
});
simpleToken = await ethers.getContractAt('SimpleToken', tokenAddress);
// make sure the address balance is 0.001 RBTC
expect(await ethers.provider.getBalance(simpleToken.address)).to.equal(
expectedBalance,
);
});

it('should recover the 0.001 ether from the lost contract address', async () => {
const [, hacker] = await ethers.getSigners();
const tx = simpleToken.destroy(hacker.address);
await expect(() => tx).to.changeEtherBalances(
[hacker, simpleToken],
[expectedBalance, expectedBalance.mul(-1)],
);
});
});
});
11 changes: 11 additions & 0 deletions ethernaut/recovery/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"compilerOptions": {
"target": "es2020",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"resolveJsonModule": true
}
}