Übergeben Sie eine Funktion als Parameter in Solidity

Können Sie eine Funktion als Parameter in Solidity übergeben?

Meine Vermutung: Es gibt das Konzept von addressin Solidity, aber sie repräsentieren contracts. Verträge können Fallback-Funktionen haben, aber ich glaube nicht, dass Sie ihnen Parameter geben können. Denken Sie daran, die Funktion als Parameter per Adresse zu übergeben, wie Sie es in C tun würden.

Gibt es einen legitimen Weg, Funktionen als Parameter zu übergeben, oder gibt es einen Hacky-Weg, wenn nicht?

Wenn ja, wie? Und wenn nicht, warum nicht?

Solidität ist zu kostspielig für funktionale Programmierung
Ich verstehe, dass viele funktionale Programmierkonzepte wie funktionale Komposition zu teuer sein könnten, aber ich bin neugierig, ob das Übergeben von Funktionen zu teuer ist. Das Übergeben eines Zeigers an eine Funktion sollte ziemlich billig sein.
Beide sind unterschiedliche Funktionen als Parameter und funktionale Programmierung, oder?
Ja sie sind anders. Das Übergeben von Funktionen als Parameter ist nur eine hilfreiche Technik, die von funktionalen Programmiersprachen verwendet wird. Beispielsweise können Sie in C eine Funktion als Parameter übergeben, aber Sie würden C nicht wirklich als funktionale Programmiersprache bezeichnen.
@NikhilM: "Solidity ist zu kostspielig für funktionale Programmierung" Nur neugierig, können Sie mehr erklären, warum es zu kostspielig ist?
@dbryson einfache Tatsache, dass es mehr Codezeilen benötigen würde und os den Bytecode ausführt.
Nicht so - gute Compiler für funktionale Sprachen sind nicht weniger effizient als die von deklarativen Sprachen.

Antworten (5)

Funktionen (auch bekannt als Methoden) werden von der ABI spezifiziert und haben eine Methoden-ID, die die ersten 4 Bytes von sha3 (Keccak-256) der Signatur der Methode sind.

Hier ist ein Beispiel für den Aufruf someFunctionvon contract:contract.call(bytes4(sha3("someFunction()")))

Hier ist eine getestete Funktion mit Übergabe von a methodIdals Parameter:

contract C1 {
    uint public _n;  // public just for easy inspection in Solidity Browser

    function foo(uint n) returns(uint) {
        _n = n;
        return _n;
    }

    function invoke(bytes4 methodId, uint n) returns(bool) {
        return this.call(methodId, n);
    }
}

Testen Sie es im Solidity Browser , indem Sie "0x2fbebd38", 9als Parameter to verwenden invoke, und sehen Sie dann, dass es _ngleich ist 9.

Anmerkungen:

  • 0x2fbebd38ist das Ergebnis von bytes4(sha3("foo(uint256)"))(vergessen Sie nicht die Notwendigkeit, kanonische Typen zu verwenden, in diesem Fall uint256gemäß ABI. )

  • Rückgabewerte von call und callcode sind boolesch, entweder der Aufruf war erfolgreich oder der Aufruf ist fehlgeschlagen. Es ist nicht möglich, einen Wert aus dem Aufruf zurückzugeben, da dies erfordern würde, dass der Vertrag den Rückgabetyp vorher kennt.

Natürlich ist das eine externe Funktion - es gibt auch interne, und einen Zeiger auf eine zu übergeben, wäre eine ganz andere Sache ...
Es scheint, dass Sie sehen können, wie diese Technik hier verwendet wird: github.com/nexusdev/dappsys/blob/…
Diese Methode hat zwei Nachteile: 1) kostet viel mehr Gas, 2) wird als Anruf von "extern" behandelt und ist daher nicht verfügbar, wenn die Zielfunktion als "intern" markiert ist.

Um die Antwort von Nick Johnson zu ergänzen, können Sie mit Funktionstypen in neueren Versionen von Solidity jetzt Funktionszeiger beschreiben:

http://solidity.readthedocs.io/en/latest/types.html#function-types

Funktionstypen sind die Typen von Funktionen. Variablen des Funktionstyps können von Funktionen zugewiesen werden, und Funktionsparameter des Funktionstyps können verwendet werden, um Funktionen an Funktionsaufrufe zu übergeben und Funktionen von Funktionsaufrufen zurückzugeben. Funktionstypen gibt es in zwei Varianten – interne und externe Funktionen

Was ist in diesem Fall das Abi?
Welche Kosten verursachen Funktionsarten beim Gasverbrauch?

Die Antwort von eth gilt für externe Funktionsaufrufe (zwischen Verträgen oder durch Verwendung der externen Schnittstelle zum Aufrufen Ihres eigenen Vertrags); hier werde ich versuchen, für interne Funktionsaufrufe zu antworten.

Solidity bietet derzeit keine Syntax zum Beschreiben des Typs eines Funktionszeigers, sodass Sie sie nicht als Argumente oder Rückgabewerte verwenden können. Funktionen sind jedoch erstklassig und können mit var; ​​Variablen zugewiesen werden. Hier ist das Beispiel aus dem Solidity-Benutzerhandbuch :

contract FunctionSelector {
  function select(bool useB, uint x) returns (uint z) {
    var f = a;
    if (useB) f = b;
    return f(x);
  }
  function a(uint x) returns (uint z) {
    return x * x;
  }
  function b(uint x) returns (uint z) {
    return 2 * x;
  }
}
Danke für die hilfreiche Antwort :) Ich dachte, es könnte andere Blickwinkel geben, und ich habe diesen Teil der Dokumentation nicht verinnerlicht, um mich daran zu erinnern.
Ich bin mir nicht sicher, wann sich das geändert hat, aber das ist nicht mehr korrekt, da es scheint, dass solidity zumindest so früh wie 0.4.16 eine Syntax zum Beschreiben des Funktionstyps bereitstellt, sodass Sie sie als Argumente übergeben können ( solidity.readthedocs.io/en /v0.4.24/types.html ), siehe Michaels Antwort

Für alle, die von Google kommen und nach einem schnellen Beispiel suchen, wie eine Funktion als Parameter übergeben wird, sehen Sie sich den folgenden Codeauszug an (Quelle https://docs.soliditylang.org/en/latest/types.html#function-types )

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

library ArrayUtils {
    // internal functions can be used in internal library functions because
    // they will be part of the same code context
    function map(uint[] memory self, function (uint) pure returns (uint) f)
        internal
        pure
        returns (uint[] memory r)
    {
        r = new uint[](self.length);
        for (uint i = 0; i < self.length; i++) {
            r[i] = f(self[i]);
        }
    }

    function reduce(
        uint[] memory self,
        function (uint, uint) pure returns (uint) f
    )
        internal
        pure
        returns (uint r)
    {
        r = self[0];
        for (uint i = 1; i < self.length; i++) {
            r = f(r, self[i]);
        }
    }

    // more methods...
}

Wie in anderen Antworten erwähnt, gibt es unterschiedliche Antworten auf Ihre Frage, je nachdem, ob Sie die Funktion "extern" oder "intern" aufrufen.

Innerhalb desselben Vertrags enthalten neuere Solidity-Versionen Sprachfunktionen, um einen "Funktionszeiger" herumzureichen (es ist nicht wirklich ein Zeiger an sich, aber er verhält sich wie einer). Mit anderen Worten, es gibt Variablen vom Typ "Funktion". Das ist, was ich meine, wenn ich eine Funktion "intern" umgebe und aufrufe.

Für „externe“ Aufrufe, dh Funktionsaufrufe an eine On-Chain-Adresse, würden Sie Solidity-Adresstypmember „call()“ und/oder Yul (Assembly) usw. verwenden, um einen Vertragsaufruf zu emulieren, da Solidity einen solchen Funktionsaufruf kompilieren würde wenn es als "someAddr.someFunction()" ausgedrückt wird ... und dies läuft darauf hinaus, die Funktionssignatur als Selektor zu codieren (Selektoren sind die ersten 4 Bytes des Hashs der Funktionssignatur) plus die Argumente, die Sie übergeben möchten, und die auszuführen erforderlichen EVM-Bytecodes (Assembly) unter Verwendung dieser Daten.

Mit anderen Worten, für "externe" Funktionen ist der "Funktionszeiger" das Tupel (Adresse, Selektor).