Nodelegatecall Explained

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

Originally Published May 11

20 Common Solidity Beginner Mistakes

20 Common Solidity Beginner Mistakes Our intent is not to be patronizing towards developers early in their journey with this article. Having reviewed code from numerous Solidity developers, we’ve seen some mistakes occur more frequently and we list those here. By no means is this an exhaustive list of mistakes a Solidity developer can make. […]

Smart Contract Foundry Upgrades with the OpenZeppelin Plugin

Smart Contract Foundry Upgrades with the OpenZeppelin Plugin Upgrading a smart contract is a multistep and error-prone process, so to minimize the chances of human error, it is desirable to use a tool that automates the procedure as much as possible. Therefore, the OpenZeppelin Upgrade Plugin streamlines deploying, upgrading and managing smart contracts built with Foundry or […]

UUPS: Universal Upgradeable Proxy Standard (ERC-1822)

UUPS: Universal Upgradeable Proxy Standard (ERC-1822) The UUPS pattern is a proxy pattern where the upgrade function resides in the implementation contract, but changes the implementation address stored in the proxy contract via a delegatecall from the proxy. The high level mechanism is shown in the animation below: Similar to the Transparent Upgradeable Proxy, the […]

Try Catch and all the ways Solidity can revert

Try Catch and all the ways Solidity can revert This article describes all the kinds of errors that can happen when a smart contract is called, and how the Solidity Try / Catch block responds (or fails to respond) to each of them. To understand how Try / Catch works in Solidity, we must understand […]