El modificador noDelegateCall evita que se envíe un delegatecall a un contrato. Primero mostraremos el mecanismo de cómo lograr esto y luego discutiremos la motivación de por qué alguien podría hacer esto.
A continuación, hemos simplificado el modificador noDelegateCall creado originalmente por el noDelegateCall de Uniswap V3:
contract NoDelegateCallExample {
address immutable private originalAddress;
constructor() {
originalAddress = address(this);
}
modifier noDelegateCall() {
require(address(this) == originalAddress, "no delegate call");
_;
}
}
El address(this) cambiará dependiendo del entorno de ejecución, pero originalAddress siempre será la dirección desplegada del código que usa noDelegateCall. Por lo tanto, si otro contrato hace un delegatecall a una función con el modificador noDelegateCall, entonces address(this) no será igual a originalAddress y la transacción se revertirá. Es extremadamente crítico que la dirección original sea una variable immutable, de lo contrario, el contrato que emite el delegatecall podría colocar estratégicamente la dirección del contrato que usa noDelegateCall en ese espacio y eludir la declaración require.
Probando noDelegateCall
A continuación proporcionamos el código para probar 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
}
}
El contrato B hace un delegatecall a A, que está usando el modificador noDelegateCall. Aunque la transacción a B.tryDelegatecall no se revertirá porque se ignora el valor de retorno de la llamada de bajo nivel, la variable de almacenamiento x no se incrementará porque la transacción dentro del contexto del delegatecall se revierte.
Motivación para noDelegateCall
Uniswap V2 es el protocolo DeFi con más forks en la historia. El protocolo Uniswap V2 se enfrentó a la competencia de proyectos que copiaban el código fuente línea por línea y comercializaban el nuevo producto como una alternativa a Uniswap V2, a veces incentivando a los usuarios mediante airdrops.
Para evitar que esto sucediera, el equipo de Uniswap licenció Uniswap V3 bajo la Business Source License: cualquiera puede copiar el código, pero no puede usarse para fines comerciales hasta que la licencia expiró en abril de 2023.
Sin embargo, si alguien quisiera hacer una “copia” de Uniswap V3, simplemente podría crear un clone proxy y apuntarlo a una instancia de Uniswap V3, para luego comercializar ese smart contract como una alternativa a Uniswap V3. El modificador noDelegateCall evita que esto suceda.
Publicado originalmente el 11 de mayo