Uso de una llamada de delegado de alto nivel en contratos actualizables desde Bizancio

Desde Byzantium podemos implementar contratos de proxy actualizables mucho más fácilmente con el uso de instrucciones returndatacopyde returndatasizemontaje. 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 delegatecallse realiza en ensamblaje. Sin embargo, también parece funcionar cuando se realiza delegatecallcomo 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?

Es posible que desee contribuir a la discusión que se lleva a cabo en github.com/ethereum/EIPs/pull/897
gracias por este gran código de ejemplo. No tengo una respuesta a su pregunta, pero me preguntaba si podría ayudarme. Me gustaría usar este método, pero solo me importa una función específica. Al usar la llamada delegada, ¿especificaría la función explícitamente o se incluirá en msg.data? si lo hago explícitamente se vería así? gist.github.com/okwme/a86e32a843bc15453002c5a0229da021
Parece que Anton Bukov agregó soporte para delegados de alto nivel en Address.sol de OpenZeppelin .

Respuestas (2)

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)
¿Por qué el código de @willjgriff deja de funcionar para tamaños de devolución superiores a 64 bytes?
¿Es porque anularía el puntero de memoria libre?
Sí, y seguir escribiendo destruiría cualquier otra memoria que se haya asignado. Consulte docs.soliditylang.org/en/v0.8.7/internals/layout_in_memory.html

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 .