Pasar una función como parámetro en Solidity

¿Puedes pasar una función como parámetro en Solidity?

Mi conjetura: existe el concepto de addressen Solidity, pero representan contracts. Los contratos pueden tener funciones de respaldo, pero no creo que pueda darles parámetros. Pensando en pasar la función como parámetro por dirección como lo harías en C.

¿Existe una forma legítima de pasar funciones como parámetros o, de no ser así, existe una forma pirateada?

Si lo hay, ¿cómo? Y si no lo hay, ¿por qué no?

La solidez es demasiado costosa para la programación funcional
Entiendo que muchos conceptos de programación funcional, como la composición funcional, pueden ser demasiado costosos, pero tengo curiosidad específicamente si pasar funciones es demasiado costoso. Pasar un puntero a una función debería ser bastante barato.
Ambas son funciones diferentes como parámetro y programación funcional, ¿verdad?
Sí, son diferentes. Pasar funciones como un parámetro es solo una técnica útil que utilizan los lenguajes de programación funcional. Por ejemplo, puede pasar una función como un parámetro en C, pero realmente no llamaría a C un lenguaje de programación funcional.
@NikhilM: "La solidez es demasiado costosa para la programación funcional" Solo por curiosidad, ¿puede explicar más por qué es demasiado costosa?
@dbryson simple hecho de que necesitaría más líneas de código y os dos el código de bytes.
No es así: los buenos compiladores de lenguajes funcionales no son menos eficientes que los de los lenguajes declarativos.

Respuestas (5)

Las funciones (también conocidas como métodos) están especificadas por ABI y tienen un ID de método, que son los primeros 4 bytes del sha3 (Keccak-256) de la firma del método.

Aquí hay un ejemplo de invocación someFunctionen contract:contract.call(bytes4(sha3("someFunction()")))

Aquí hay una función probada con pasar a methodIdcomo parámetro:

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

Pruébelo en Solidity Browser usando "0x2fbebd38", 9como parámetros para invoke, luego vea que _nes igual a 9.

Notas:

  • 0x2fbebd38es el resultado de bytes4(sha3("foo(uint256)"))(no olvide la necesidad de usar tipos canónicos, en este caso uint256, según la ABI ) .

  • Los valores de retorno de call y callcode son booleanos, ya sea que la llamada se haya realizado correctamente o que haya fallado. No es posible devolver un valor de call ya que eso requeriría que el contrato conozca el tipo de devolución de antemano.

Por supuesto, esa es una función externa; también existen funciones internas, y pasar un puntero a una sería un asunto completamente diferente...
Parece que puede ver que se usa esta técnica aquí: github.com/nexusdev/dappsys/blob/…
Este método tiene dos inconvenientes: 1) cuesta mucho más gasolina, 2) se trata como una llamada "externa" y, por lo tanto, no es accesible si la función de destino está marcada como "interna"

Para agregar a la respuesta de Nick Johnson, los tipos de función en versiones recientes de solidity le permiten describir punteros de función ahora:

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

Los tipos de función son los tipos de funciones. Las variables del tipo de función se pueden asignar desde funciones y los parámetros de función del tipo de función se pueden usar para pasar funciones y devolver funciones desde llamadas a funciones. Los tipos de funciones vienen en dos sabores: funciones internas y externas

¿Cuál es el abi en ese caso?
¿Qué costes aportan los tipos de funciones al consumo de gas?

La respuesta de eth se aplica a las llamadas a funciones externas (entre contratos, o mediante el uso de la interfaz externa para llamar a su propio contrato); aquí intentaré responder a las llamadas de funciones internas.

Actualmente, Solidity no proporciona sintaxis para describir el tipo de un puntero de función, por lo que no puede tomarlos como argumentos o valores de retorno. Sin embargo, las funciones son de primera clase y se pueden asignar a variables usando var; aquí está el ejemplo del manual de usuario de Solidity :

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;
  }
}
Gracias por la respuesta útil :) Pensé que podría haber otros ángulos y no internalicé esa parte de los documentos para recordarlo.
No estoy seguro de cuándo cambió eso, pero ya no es correcto, ya que parece que al menos desde 0.4.16 solidity proporciona sintaxis para describir el tipo de funciones, por lo que puede pasarlas como argumentos ( solidity.readthedocs.io/en /v0.4.24/types.html ), vea la respuesta de Michael

Para cualquiera que venga de Google y busque un ejemplo rápido de cómo pasar una función como parámetro, consulte el siguiente extracto del código (fuente 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...
}

Como se señaló en otras respuestas, hay distintas respuestas a su pregunta dependiendo de si está hablando de llamar a la función "externamente" o llamarla "internamente".

Dentro del mismo contrato, las versiones recientes de Solidity incluyen funciones de lenguaje para pasar un "puntero de función" (no es realmente un puntero en sí, pero se comporta como tal). En otras palabras, existen variables de tipo "función". Esto es lo que quiero decir con pasar "internamente" una función y llamarla.

Para llamadas "externas", es decir, llamadas de función a una dirección en cadena, usaría el miembro de tipo de dirección de Solidity "llamada()" y/o Yul (ensamblaje), etc. para emular una llamada de contrato, ya que Solidity compilaría dicha llamada de función cuando se expresa como "someAddr.someFunction()"... y esto equivale a codificar la firma de la función como un selector (los selectores son los primeros 4 bytes del hash de la firma de la función) más los argumentos que desea pasar, y ejecutar el bytecodes EVM necesarios (ensamblado) usando esos datos.

En otras palabras, para funciones "externas", el "puntero de función" es la tupla (dirección, selector).