25

Can you pass a function as a parameter in Solidity?

My guess: There is the concept of address in Solidity, but they represent contracts. Contracts can have fallback functions, but I don't think you can give them parameters. Thinking about passing the function as a parameter by address like you would do in C.

Is there a legit way to pass functions as parameters, or if not, is there a hacky way?

If there is, how? And if there isn't, why not?

eth
  • 85,679
  • 53
  • 285
  • 406
Karl Floersch
  • 1,691
  • 3
  • 19
  • 24
  • Solidity is too costly for functional programming – niksmac Apr 25 '16 at 17:53
  • I understand that a lot of functional programming concepts like functional composition might be too expensive, but I'm curious specifically if passing functions is too expensive. Passing a pointer to a function should be pretty cheap. – Karl Floersch Apr 25 '16 at 17:57
  • Both are different function as a parameter and functional programming right? – niksmac Apr 25 '16 at 17:59
  • Yeah they are different. Passing functions as a parameter is just a helpful technique that functional programming languages utilize. For instance, you can pass a function as a parameter in C, but you wouldn't really call C a functional programming language. – Karl Floersch Apr 25 '16 at 18:03
  • 2
    @NikhilM : " Solidity is too costly for functional programming " Just curious can you explain more why it's too costly? – dbryson Apr 25 '16 at 18:19
  • @dbryson simple fact that it would need more lines of code and os dos the byte code. – niksmac Apr 26 '16 at 08:50
  • Not so - good functional language compilers are no less efficient than those of declarative languages. – Nick Johnson Apr 26 '16 at 14:08

5 Answers5

18

Functions (aka Methods) are specified by the ABI, and have a Method ID, which is the first 4 bytes of the sha3 (Keccak-256) of the method's signature.

Here's an example of invoking someFunction on contract: contract.call(bytes4(sha3("someFunction()")))

Here is a tested function with passing a methodId as a 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);
    }
}

Test it in Solidity Browser by using "0x2fbebd38", 9 as the parameters to invoke, then see that _n equals 9.

Notes:

  • 0x2fbebd38 is the result of bytes4(sha3("foo(uint256)")) (don't forget the need to use canonical types, in this case uint256, per the ABI.)

  • Return values from call and callcode are boolean, either the call succeeded or the call failed. It is not possible to return a value from call since that would require the contract to know the return type beforehand.

eth
  • 85,679
  • 53
  • 285
  • 406
  • 1
    Of course, that's an external function - internal ones exist too, and passing a pointer to one would be a different matter entirely... – Nick Johnson Apr 26 '16 at 14:09
  • It seems you can see this technique being used here: https://github.com/nexusdev/dappsys/blob/6f4381954d1ed130a577a42023b28a79aa415d05/contracts/auth/basic_authority_test.sol#L39 – Karl Floersch Apr 28 '16 at 01:26
  • 3
    This method has two drabacks: 1) costs way more gas, 2) is treated as call from "external" and hence non-accessible if target function is marked "internal" – SCBuergel May 01 '17 at 16:15
10

To add on to Nick Johnson's answer, function types in recent versions of solidity allow you to describe function pointers now:

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

Function types are the types of functions. Variables of function type can be assigned from functions and function parameters of function type can be used to pass functions to and return functions from function calls. Function types come in two flavours - internal and external functions

Michael
  • 101
  • 1
  • 3
6

eth's answer applies to external function calls (between contracts, or by using the external interface to call your own contract); here I'll attempt to answer for internal function calls.

Solidity currently provides no syntax for describing the type of a function pointer, so you can't take them as arguments or return values. However, functions are first-class and can be assigned to variables using var; here's the example from the Solidity user manual:

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;
  }
}
Nick Johnson
  • 8,144
  • 1
  • 28
  • 35
  • 1
    Thanks for helpful answer :) I thought there might be other angles and I didn't internalize that part of the docs to remember it. – eth Apr 26 '16 at 18:43
  • Not sure when that changed, but this is not correct anymore, as it seems at least as early as 0.4.16 solidity does provide syntax for describing the type of functions, so you can pass them as arguments (https://solidity.readthedocs.io/en/v0.4.24/types.html), see Michael's answer – matthias_buehlmann Aug 22 '18 at 12:45
2

For anyone coming from google looking for a quick example of how to pass a function as a parameter, check out the following code excerpt (source 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 uintself.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 &lt; self.length; i++) {
        r = f(r, self[i]);
    }
}

// more methods...

}

devinm
  • 135
  • 4
0

As noted in other answers, there are distinct answers to your question depending on whether you are talking about calling the function "externally" or calling it "internally."

Within the same contract, recent Solidity versions include language features to pass around a "function pointer" (it's not really a pointer per-se but it behaves like one). In other words, there exist variables of type "function." This is what I mean by "internally" passing around a function and calling it.

For "external" calls, i.e., function calls to an on-chain address, you would use Solidity address type member "call()" and/or Yul (assembly) etc to emulate a contract call as Solidity would compile such a function call when expressed as "someAddr.someFunction()" ... and this amounts to encoding the function signature as a selector (selectors are the first 4 bytes of the hash of the function signature) plus the arguments you want to pass, and executing the necessary EVM bytecodes (assembly) using those data.

In other words, for "external" functions, the "function pointer" is the tuple (address,selector).