Wie konstruiere ich einen Aufruf zu einem anderen Vertrag mit Inline-Assembly?

Ich möchte einen Vertrag aufrufen und die Rückgabewerte per EVM-Montage in Solidity manuell bearbeiten. Dies sollte beispielsweise einfach zwei Zahlen zusammenzählen.

contract Test1 {
    function add(int a, int b) returns(int){  //Simply add the two arguments and return
        return a+b;
    }
    function() returns (int){  //If the function signature doesn't check out, return -1
        return -1;
    }
}

contract Test2 {
    Test1 test1;

    function Test2(){  //Constructor function
        test1 = new Test1();  //Create new "Test1" function
    }

    function test(int a, int b) constant returns (int c){
        address addr = address(test1);  //Place the test1 address on the stack
         bytes4 sig = bytes4(sha3("add(int256,int256)")); //Function signature

        assembly {
            let x := mload(0x40)   //Find empty storage location using "free memory pointer"
            mstore(x,sig) //Place signature at begining of empty storage 
            mstore(add(x,0x04),a) //Place first argument directly next to signature
            mstore(add(x,0x24),b) //Place second argument next to first, padded to 32 bytes

            call(5000, addr, 0, //Issue call, providing 5k gas and 0 value to "addr"
            x, 0x44, add(x,0x80), 0x20) //Inputs start at location "x" and are 68 bytes long, outputs start 128 bytes after x, and are 32 bytes long
            c := mload(add(x,0x80)) //Assign output value to c
            mstore(0x40,add(x,0x100)) // Set storage pointer to empty space
        }
    }

    function test2(int a, int b) constant returns(int c){ //Make sure the Test1 function works properly
        return test1.add(a,b); // (It does)
    }
}

Das Problem ist, dass dies einen Out-of-Gas-Fehler zurückgibt, der von der call...Leitung stammt.

Wie kann ich dieses Problem beheben?

Kein Kommentar zu Ihrem Problem, sondern nur neugierig, wofür das Einfügen von Assembly in Solidity dient und warum nicht in Solidity codiert wird, außer für die Herausforderung des Verstandes?
Hier gibt es einige Gründe , und in diesem Fall liegt es daran, dass ich ein Byte-Array mit dynamischer Größe aus einem anderen Vertrag abrufen muss, was nicht automatisch erfolgen kann, da die EVM zuvor Speicher für die Rückgabe von Daten zuweisen muss
Danke, sehr interessant, auch wenn es für mich unerreichbar ist, solch einen Low-Level-Code zu schreiben 😥
Sie sollten es wirklich versuchen, es ist wirklich nützlich und selbst einfache Dinge wie das Kopieren von Speicher in neue Variablen machen den Umgang mit Arrays und Strings viel einfacher
Vielleicht werde ich eines Tages, wer weiß, sehr zuversichtlich sein...

Antworten (1)

Der Fehler war auf ein nicht behandeltes Element auf dem Stapel zurückzuführen, das vom callOpcode hinterlassen wurde. Der funktionierende und optimierte relevante Code ist hier:

    assembly {
        let x := mload(0x40)   //Find empty storage location using "free memory pointer"
        mstore(x,sig) //Place signature at begining of empty storage 
        mstore(add(x,0x04),a) //Place first argument directly next to signature
        mstore(add(x,0x24),b) //Place second argument next to first, padded to 32 bytes

        let success := call(      //This is the critical change (Pop the top stack value)
                            5000, //5k gas
                            addr, //To addr
                            0,    //No value
                            x,    /Inputs are stored at location x
                            0x44, //Inputs are 68 bytes long
                            x,    //Store output over input (saves space)
                            0x20) //Outputs are 32 bytes long

        c := mload(x) //Assign output value to c
        mstore(0x40,add(x,0x44)) // Set storage pointer to empty space
    }

Danke an @chriseth für den Hinweis auf meinen Fehler

BEARBEITEN:

Wie @Ilan betonte, ist das Finale mstorenicht unbedingt erforderlich, da es uns egal ist, diesen Speicher zugewiesen zu halten. Wenn die zurückgegebenen Daten ein Heap-Objekt wie ein Array sind, müssen Sie sicherstellen, dass der Speicher zugewiesen bleibt.

Der 5. Parameter callwird in Bytes gemessen, nicht in Bits. SE lässt mich nicht zur Korrektur bearbeiten, da die Änderung nicht "groß genug" ist, obwohl sie massive Auswirkungen auf die Genauigkeit der Antwort hat. :/
Guter Fang, bearbeitet
@TjadenHess Frage: Beim Setzen des Speicherzeigers auf leeren Speicherplatz. da die Eingabe nicht mehr benötigt wird. kannst du x + 0x20 so verwenden:mstore(0x40,add(x,0x20));
Eigentlich müssen Sie es überhaupt nicht setzen, da Sie den Wert bereits auf den Stack geladen haben
@TjadenHess Nachdem ich mehr mit dem Code gespielt habe, gibt es hier 3 Probleme. 1. Das Setzen der Ausgabe über die Eingabe funktioniert bei einigen Funktionen nicht, bei denen ein Ausgabewert möglicherweise eingestellt wird, bevor die Eingabe zum letzten Mal verwendet wird. 2. Der freie Speicherzeiger sollte gesetzt werden, bevor eine andere Funktion aufgerufen wird, die ihn ebenfalls verwenden könnte. 3. nachdem der Funktionsaufruf abgeschlossen wurde. Der freie Speicherzeiger sollte auf x zurückgesetzt werden, das ist die Startposition, an der er sich befand, bevor der Funktionsaufruf stattfand.
1) Stimmt, aber das wäre ein ziemlich ungewöhnliches Muster. Das spart Benzin und ist in der Praxis recht verbreitet. 2) Der Speicher ist anruflokal, nicht vertragslokal, sodass dies niemals passieren kann. 3) Richtig, wie oben angesprochen
@TjadenHess kannst du etwas mehr Einblick in den Fehler geben? Ich beschäftige mich mit der Inline-Montage. successWas genau passiert, wenn Sie den Anrufausgang nicht zuweisen? Wird die folgende Ausgabe geladen, cum den booleschen Erfolg als Eingabeargument (zusammen mit x) und den Fehler auszugeben? Danke vielmals.
Was genau passiert, hängt von der Compiler-Version ab. Alles nach ~4.0 wird einfach nicht mit diesem Fehler kompiliert. Grundsätzlich entsprechen Assembly-Variablen Positionen auf dem Stapel. Wenn wir die Variable verwenden, generiert der Compiler eine swap nOperation, in der angegeben nist, wie weit unten im Stapel sich die Variable befindet. Wenn wir in sol ~3.5 ein zusätzliches Element auf dem Stack belassen, sind die Adressen um 1 versetzt, und daher ist das Argument für die nächste Funktion das, was 1 oben xauf dem Stack war, was eine ungültige Speicheradresse ergibt.