Optionaler Multi-Parameter-Aufruf an externen Vertrag

Ich möchte ERC223 aus geschäftlichen Gründen zur Hälfte implementieren. Dies bedeutet, dass Sie andere Verträge benachrichtigen können, wenn sie Token erhalten, aber nicht zurückkehren, wenn der Empfänger ein Vertrag ist, der die tokenFallback-Funktion nicht implementiert. Die folgenden Situationen sollten also funktionieren:

  1. Benutzer überträgt Token an eine Adresse. Kein Aufruf von tokenFallback (da es sich nur um eine Adresse handelt), die Übertragung ist erfolgreich.
  2. Der Benutzer überträgt Token an einen Vertrag, der tokenFallback nicht implementiert. Kein Anruf, Übertragung erfolgreich.
  3. Der Benutzer überträgt das Token an einen Vertrag, der tokenFallback implementiert. Funktion wird aufgerufen, Übertragung gelingt.

Hier meine bisherigen Erkenntnisse:

  • Ich kann anrufen, indem ich tue, TokenRecipient(address).tokenFallback(msg.sender, value, 0)und das funktioniert für 1 und 3, aber nicht für 2, weil ich keine Möglichkeit finde, zu überprüfen, ob der Vertrag mit der ERC223-Schnittstelle konform ist.
  • Ich kann aufrufen, indem ich to.call(<parameters>), und dies funktioniert, wenn ich den bytes-Parameter weglasse. Aber das Problem ist, dass der ERC223-Aufruf diesen Bytes-Parameter enthält, und selbst ihre Referenzimplementierung scheint nicht so zu funktionieren: https://github.com/Dexaran/ERC223-token-standard/issues/51 Das Verhalten, das ich ' Ich sehe, dass, sobald es überhaupt einen bytesParameter gibt, call()nur falsch zurückgegeben wird und nichts unternommen wird.
  • Ich könnte theoretisch Inline-Assembly verwenden, um den Aufruf auszuführen, aber ich kann keine Dokumentation darüber finden, wie ich den bytesParameter richtig codieren soll. Ich weiß, dass es möglich ist, weil die erste Option hier vertragsübergreifend funktioniert, ich muss nur wissen, wie man den Montageaufruf tatsächlich durchführt.

Kann jemand eine Anleitung geben, wie man hier am besten vorgeht? Gibt es eine Lösung für call()oder eine Möglichkeit, den normalen Aufruf daran zu hindern, zurückgesetzt zu werden, wenn die Funktion nicht vorhanden ist? Oder kann mir alternativ jemand auf einige Dokumente verweisen, wie ich den Aufruf von der Inline-Assemblierung vorbereiten kann?

EDIT: Folgendes habe ich für die Montage versucht. Scheint nicht zu funktionieren, da die Funktion nicht aufgerufen wird, wenn sie existiert:

function _internalNoThrowTokenFallback(address contractRecipient, address from, uint value, bytes data)
    internal
{
    // We need to do this in assembly because:
    //   1. Calling the function as normal would result in a revert if the destination contract doesn't
    //      implement tokenFallback. We want this case to be ignored.
    //   2. We could use contractRecipient.call() but that doesn't work with bytes parameters: 
    //      https://github.com/ethereum/solidity/issues/2884
    //   3. So we need to do the call the same way 
    assembly {
        // Get some free memory to copy our params over to.
        let free_ptr := mload(0x40)

        // Tee up the function selector in the memory we've gotten
        mstore(free_ptr, 0xc0ee0b8a)

        // Load our calldata across to the new call, except for
        //  - The function selector (4 bytes)
        //  - The first address parameter (64 bytes when padded)
        //
        // Thus making a total of 68 bytes we want to ignore, and we want to copy the
        // data to our free_ptr after our function selector
        calldatacopy(add(free_ptr, 4), 68, sub(calldatasize, 68))

        // Ok, call the function without allowing the other contract to alter our state
        let result := staticcall(
            30000,                  // Give the other contract 30k gas to work with
            contractRecipient,      // The other contract's address
            free_ptr,               // We've prepared the inputs at free_ptr.
            sub(calldatasize, 64),  // They're the same length as our inputs were minus the address parameter.
            0,                      // There's no return from the function
            0
        )

        // Don't check the result or revert if the call failed. We want to proceed regardless.
    }
}

Antworten (1)

Nachdem ich mit dem Kernteam an diesem Problem gearbeitet habe, konnte ich dieses Problem lösen. Ich teile die Lösung für alle, die in Zukunft darauf stoßen.

Kontext

  • call()hat einen Fehler, bei dem Funktionsparameter beschädigt werden, indem sie nicht korrekt codiert werden, wenn es sich um dynamische Typen handelt, z bytes.
  • In solidity v0.5 ist die Verwendung der Version call()dieser kodierten Parameter nicht mehr erlaubt und wird nicht kompiliert. call()nimmt nur ein einziges bytesArgument, das der vorcodierte Funktionsaufruf mit Selektor und allen Argumenten gemäß der Abi-Spezifikation ist.
  • abi.encodeWithSignature()wurde zu diesem Zweck eingeführt, der den Fehler nicht hat call()und Funktionsparameter, auch dynamische, korrekt codiert.

Lösung

Wenn Sie diese Funktion also optional für einen anderen Vertrag aufrufen möchten:

function tokenFallback(address from, uint amount, bytes data) public {
    // Do stuff here
}

Sie haben diese Möglichkeiten:


Normaler Aufruf: Caste den empfangenden Vertrag in einen Typ mit dieser Funktionssignatur im abi und rufe die Funktion normal auf, zB

CallReceiver receiver = CallReceiver(to);
receiver.tokenFallback(msg.sender, value, data);

tokenFallbackWenn der Empfänger in diesem Fall die gesamte Transaktion nicht implementiert , wird sie zurückgesetzt, was sie nicht sehr optional macht.


Optionaler Aufruf: call() , zB

to.call(abi.encodeWithSignature(
    "tokenFallback(address,uint256,bytes)", 
    msg.sender,
    value,
    data
));

Dies gibt trueoder zurück false, je nachdem, ob der Aufruf erfolgreich war oder nicht, wodurch Sie Fehler ignorieren und mit der Ausführung fortfahren können. Beachten Sie, dass, obwohl die Funktion mit einem uintParameter deklariert ist, dies uint256bei der Berechnung des Funktionsselektors der Fall ist. Der kanonische Typ wird immer für Parameter verwendet, und es gibt kein Leerzeichen zwischen den Typen in der Funktionssignatur. Mehr Infos in der abi spec .


Assembly Call: Sie könnten dafür Inline-Assembly verwenden, aber da ich ohne dies eine Lösung finden konnte, habe ich mich entschieden, diesen Weg nicht zu gehen. Das erste Problem mit der obigen Assembly besteht darin, dass sie die statischen Parameter verschiebt und den dynamischen Zeiger nicht auf den bytesParameter aktualisiert, aber ich bin mir nicht sicher, ob dies das einzige Problem ist, da ich mich nicht darum gekümmert habe, dies seither call()durchzuarbeiten abi.encodeWithSignature()funktioniert .