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