DEV Community

Cover image for Preventing unwanted delegate call
MOYED
MOYED

Posted on

Preventing unwanted delegate call

Sources

DelegateCall: Calling Another Contract Function in Solidity

Difference between CALL, CALLCODE and DELEGATECALL

Solidity call() and delegateCall()

How to prevent the code of my contract being used in a CALLCODE?


Overview

When delegatecall used in caller contract to target contract,

  • target contract executes its logic
  • context(storage) is on caller contract

Overview

As EVM saves filed variable to storage in a slot order, if we use delegatecall , we should set the order of variables in same order.

Variable orders matter


Preventing unwanted Delegate call

This method is from Uniswap v3’s NoDelegateCall.sol.

The core is to compare between the original address of contract(originalAdress) and new address that executes delegatecall (newAdress).

1. How it works

pragma solidity >0.8.0;

contract Receiver {
    string greeting = "Hello";
    address private immutable originalAdress;
    event Greeting(string greeting, address originalAdress, address newAddress);

    constructor() {
        // Immutables are computed in the init code of the contract, and then inlined into the deployed bytecode.
        // In other words, this variable won't change when it's checked at runtime.
        originalAdress = address(this);
    }  
    function greet() external  {
        address newAddress = address(this);
        emit Greeting(greeting, originalAdress, newAddress);
    }
}

contract Sender {
    string greeting = "Hi";

    function delegatedGreeting(address _contract) external {
        (bool success,) = _contract.delegatecall(
            abi.encodeWithSignature("greet()")
        );
    }

    function callGreeting(address _contract) external {
        (bool success,) = _contract.call(
            abi.encodeWithSignature("greet()")
        );
    }
}
Enter fullscreen mode Exit fullscreen mode
  • Sender contract address : 0xaE036c65C649172b43ef7156b009c6221B596B8b
  • Reciever contract address : 0xcD6a42782d230D7c13A74ddec5dD140e55499Df9

origianalAddress

This variable is initialized in constructor of Reciever contract and fixed to Reciever contract address whether Sender executes call or delegatecall. It cannot be modified because of immutable keyword.

newAddress

This variable is assigned to address(this) inside the function greet . As address(this) refers the contract which the function is executing, it’s value varies when it’s triggered by call and delegatecall .

  • call

    Using call function, the contract that is executed is Reciever . So, newAddress is same to orginalAddress .

  • delegatecall

    But, using delegatecall, the contract that is actually executed is Sender . So, newAddress stores the value of Sender contract address, which is different to originalAddress.

2. 50% Success(revert issue)

pragma solidity >0.8.0;

contract Receiver {
    string greeting = "Hello";
    address private immutable original;
    event Greeting(string greeting, address original, address addressThis);

    constructor() {
        // Immutables are computed in the init code of the contract, and then inlined into the deployed bytecode.
        // In other words, this variable won't change when it's checked at runtime.
        original = address(this);
    }

    /// @dev Private method is used instead of inlining into modifier because modifiers are copied into each method,
    ///     and the use of immutable means the address bytes are copied in every place the modifier is used.
    function checkNotDelegateCall() private view {
        require(address(this) == original);
    }

    /// @notice Prevents delegatecall into the modified method
    modifier noDelegateCall() {
        checkNotDelegateCall();
        _;
    }

    function greet() external noDelegateCall {
        emit Greeting(greeting, original, address(this));
    }
}

contract Sender {
    string greeting = "Hi";

    function delegatedGreeting(address _contract) external {
        (bool success,) = _contract.delegatecall(
            abi.encodeWithSignature("greet()")
        );
    }

    function callGreeting(address _contract) external {
        (bool success,) = _contract.call(
            abi.encodeWithSignature("greet()")
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

Empty event

I implemented noDelegateCall modifier to check the difference between original variable and address(this) inside function greet . The problem was that although the emitted event was empty, delegatecall wasn’t reverted. So I tried to make it revert...

3. Why doesn’t delegatedcall revert?

solidity delegatecall prevention doesn't works

When low-level delegatecall reverts, it doesn’t automatically revert main transaction. Instead It returns false as the first return value.

So, to make delegatecall revert, we should check if the first return value success is true.

function delegatedGreeting(address _contract) external {
    (bool success,) = _contract.delegatecall(
        abi.encodeWithSignature("greet()")
    );
    require(success == true, "delegatecall failed");
}
Enter fullscreen mode Exit fullscreen mode

4. 100% Success

pragma solidity >0.8.0;

contract Receiver {
    string greeting = "Hello";
    address private immutable original;
    event Greeting(string greeting, address original, address addressThis);

    constructor() {
        // Immutables are computed in the init code of the contract, and then inlined into the deployed bytecode.
        // In other words, this variable won't change when it's checked at runtime.
        original = address(this);
    }

    /// @dev Private method is used instead of inlining into modifier because modifiers are copied into each method,
    ///     and the use of immutable means the address bytes are copied in every place the modifier is used.
    function checkNotDelegateCall() private view {
        require(address(this) == original);
    }

    /// @notice Prevents delegatecall into the modified method
    modifier noDelegateCall() {
        checkNotDelegateCall();
        _;
    }

    function greet() external noDelegateCall {
        emit Greeting(greeting, original, address(this));
    }
}

contract Sender {
    string greeting = "Hi";

    function delegatedGreeting(address _contract) external {
        (bool success,) = _contract.delegatecall(
            abi.encodeWithSignature("greet()")
        );
        require(success == true, "Delegation failed!");
    }

    function callGreeting(address _contract) external {
        (bool success,) = _contract.call(
            abi.encodeWithSignature("greet()")
        );
        require(success == true, "Call failed!");
    }
}
Enter fullscreen mode Exit fullscreen mode

Discussion (0)