Desde Byzantium podemos implementar contratos de proxy actualizables mucho más fácilmente con el uso de instrucciones returndatacopy
de returndatasize
montaje. Esto significa que ya no tenemos que registrar tipos y tamaños de devolución como cuando usamos el EtherRouter .
La forma más confiable que conocemos para estructurar un contrato de proxy es como el Zeppelin Proxy , donde delegatecall
se realiza en ensamblaje. Sin embargo, también parece funcionar cuando se realiza delegatecall
como una llamada de Solidity de alto nivel donde la función de reserva del contrato de proxy se ve así:
function () public {
bool callSuccess = upgradableContractAddress.delegatecall(msg.data);
if (callSuccess) {
assembly {
returndatacopy(0x0, 0x0, returndatasize)
return(0x0, returndatasize)
}
} else {
revert();
}
}
Este enfoque (vea el proxy completo aquí ) es un poco más breve y requiere menos conocimiento de ensamblaje para comprenderlo. Mis pruebas mínimas para este enfoque parecen funcionar.
Entonces, ¿en qué situaciones no funcionará este enfoque de alto nivel?
Y si no hay ninguno, ¿cuál es la probabilidad de que el código de bytes compilado para la llamada del delegado de alto nivel cambie entre versiones de Solidity, rompiendo este enfoque para esas versiones?
Un problema es que copia sus datos en la memoria comenzando en la dirección 0
. Esto funcionará para tamaños de retorno de menos de 64 bytes, pero comenzará a sobrescribir otra memoria en ese punto.
En su lugar, deberías hacer algo más como
let m := mload(0x40)
returndatacopy(m, 0, returndatasize)
return(0, returndatasize)
Para mitigar el problema mencionado por @Tjaden Hess, haga lo que hace OpenZeppelin:
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
if (success) {
return returndata;
} else {
if (returndata.length > 0) {
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
Si la llamada no falla, devuelva los datos como lo haría normalmente en Solidity. De lo contrario, revierta a través de ensamblado para aumentar el motivo de reversión.
Consejo profesional: vea mi implementación de esto en PRBProxy .
skang404
okme
Pablo Razvan Berg