top of page
  • Writer's pictureJeffrey Scholz

Nodelegatecall Explained

Updated: May 13

The nodelegatecall modifier prevents delegatecalls from being sent to a contract. We will first show the mechanism for how to accomplish this then later discuss the motivation for why one might do this.


Below, we’ve simplified the nodelegatecall modifier originally created by Uniswap V3’s noDelegateCall:


contract NoDelegateCallExample {
    address immutable private originalAddress;

    constructor() {
	    originalAddress = address(this);
    }

    modifier noDelegateCall() {
	    require(address(this) == originalAddress, "no delegate call");
	    _;
    }
}

The address(this) will change depending on the execution environment, but originalAddress will always be the deployed address of the code that uses nodelegatecall. So if another contract does a delegatecall to a function with the noDelegateCall modifier, then address(this) will not equal originalAddress and the transaction will revert. It is extremely critical that original address is an immutable variable, otherwise the contract issueing the delegatecall could strategically put the address of the contract using NoDelegateCall in that slot and bypass the require statement.


Testing nodelegatecall

Below we provide the code to test nodelegatecall.


contract NoDelegateCall {
    address immutable private originalAddress;

    constructor() {
        originalAddress = address(this);
    }

    modifier noDelegateCall() {
        require(address(this) == originalAddress,                			
			"no delegate call");
        _;
    }
}

contract A is NoDelegateCall {
    uint256 public x;

    function increment() noDelegateCall public {
        x++;
    }
}

contract B {
    uint256 public x; // this variable does not increment

    function tryDelegatecall(address a) external {
        (bool ok, ) = a.delegatecall(
            abi.encodeWithSignature("increment()")
		);// ignore ok
    }
}

Contract B does a delegatecall to A which is using the nodelegatecall modifier. Although the transaction to B.tryDelegatecall will not revert because the return value of the low level call is ignored, the storage variable x will not be incremented because the transaction inside the context of delegatecall reverts.


Motivation for nodelegatecall

Uniswap V2 is the most forked DeFi protocol in history. The Uniswap V2 protocol faced competition from projects copying the source code line-by-line and marketing the new product as an alternative to Uniswap V2 and sometimes incentivizing users by providing airdrops.


To prevent this from happening, the Uniswap team licensed Uniswap V3 under the Business Source License — anyone can copy the code but it cannot be used for commercial purposes until the license expired in April 2023.


However, if someone wanted to make a “copy” of Uniswap V3 they could simply create a clone proxy and point it to an instance of Uniswap V3 — then market that smart contract as an alternative to Uniswap V3. The nodelegatecall modifier prevents this from happening.



140 views0 comments

Recent Posts

See All

Comentários


bottom of page