Verwenden eines hochrangigen Delegiertenaufrufs in aktualisierbaren Verträgen seit Byzanz

Seit Byzanz können wir aktualisierbare Proxy-Verträge viel einfacher mit der Verwendungs- returndatacopyund returndatasizeBauanleitung implementieren. Das bedeutet, dass wir Rückgabetypen und -größen nicht mehr registrieren müssen, wie bei der Verwendung des EtherRouter .

Der zuverlässigste Weg, den wir kennen, um einen Proxy-Vertrag zu strukturieren, ist wie der Zeppelin-Proxy , bei dem delegatecallder in Assembly hergestellt wird. Es scheint jedoch auch zu funktionieren, wenn Sie den delegatecallSolidity-Aufruf auf hoher Ebene ausführen, bei dem die Fallback-Funktion des Proxy-Vertrags stattdessen so aussieht:

function () public {

    bool callSuccess = upgradableContractAddress.delegatecall(msg.data);

    if (callSuccess) {
        assembly {
            returndatacopy(0x0, 0x0, returndatasize)
            return(0x0, returndatasize)
        }
    } else {
        revert();
    }
}

Dieser Ansatz (siehe den gesamten Proxy hier ) ist etwas prägnanter und erfordert weniger Kenntnisse der Assemblierung, um ihn zu verstehen. Meine minimalen Tests für diesen Ansatz scheinen zu funktionieren.

In welchen Situationen funktioniert dieser Ansatz auf hoher Ebene nicht?

Und wenn es keine gibt, wie wahrscheinlich ist es, dass sich der kompilierte Bytecode für den Delegiertenaufruf auf hoher Ebene zwischen den Versionen von Solidity ändert und dieser Ansatz für diese Versionen bricht?

Vielleicht möchten Sie zu der Diskussion beitragen, die auf github.com/ethereum/EIPs/pull/897 stattfindet
vielen Dank für diesen tollen Beispielcode. Ich habe keine Antwort auf Ihre Frage, aber Sie fragen sich, ob Sie mir helfen können? Ich würde diese Methode gerne verwenden, aber ich interessiere mich nur für eine bestimmte Funktion. Würde ich bei der Verwendung von Delegatecall die Funktion explizit angeben oder wird sie in msg.data enthalten sein? Wenn ich es explizit mache, würde es so aussehen? gist.github.com/okwme/a86e32a843bc15453002c5a0229da021
Sieht so aus, als ob Anton Bukov Unterstützung für Delegiertenrufe auf hoher Ebene in Address.sol von OpenZeppelin hinzugefügt hat .

Antworten (2)

Ein Problem besteht darin, dass Sie Ihre Daten ab Adresse in den Speicher kopieren 0. Dies funktioniert für Rückgabegrößen von weniger als 64 Byte, beginnt jedoch an diesem Punkt, anderen Speicher zu überschreiben.

Stattdessen sollten Sie eher etwas tun

let m := mload(0x40)
returndatacopy(m, 0, returndatasize)
return(0, returndatasize)
Warum funktioniert der Code von @ willjgriff nicht mehr für Rückgabegrößen von mehr als 64 Byte?
Liegt das daran, dass es den freien Speicherzeiger überschreiben würde?
Ja, und weiteres Schreiben würde jeden anderen zugewiesenen Speicher überlasten. Siehe docs.soliditylang.org/en/v0.8.7/internals/layout_in_memory.html

Um das von @Tjaden Hess erwähnte Problem zu beheben, tun Sie, was OpenZeppelin tut:

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);
        }
    }

Wenn der Aufruf nicht fehlschlägt, geben Sie die Daten zurück, wie Sie es normalerweise in Solidity tun würden. Andernfalls kehren Sie über die Baugruppe zurück, um den Rückgängiggrund aufzublasen.

Pro-Tipp: Siehe meine Implementierung davon in PRBProxy .