Передать функцию в качестве параметра в Solidity

Можете ли вы передать функцию в качестве параметра в Solidity?

Мое предположение:address в Solidity есть понятие s, но они представляют contracts. Контракты могут иметь резервные функции, но я не думаю, что вы можете задавать им параметры. Думая о передаче функции в качестве параметра по адресу, как вы делали бы это в C.

Есть ли законный способ передавать функции в качестве параметров, а если нет, то есть ли хакерский способ?

Если есть то как? А если нет, то почему?

Solidity слишком затратна для функционального программирования
Я понимаю, что многие концепции функционального программирования, такие как функциональная композиция, могут быть слишком дорогими, но мне особенно любопытно, не слишком ли дорога передача функций. Передача указателя на функцию должна быть довольно дешевой.
Оба являются разными функциями в качестве параметра и функционального программирования, верно?
Да они разные. Передача функций в качестве параметра — это просто полезная техника, которую используют языки функционального программирования. Например, вы можете передать функцию в качестве параметра в C, но на самом деле вы бы не назвали C функциональным языком программирования.
@NikhilM: «Solidity слишком дорог для функционального программирования». Просто любопытно, можете ли вы объяснить, почему это слишком дорого?
@dbryson простой факт, что потребуется больше строк кода, а os dos - байтовый код.
Это не так — хорошие компиляторы функциональных языков не менее эффективны, чем компиляторы декларативных языков.

Ответы (5)

Функции (также известные как методы) определяются ABI и имеют идентификатор метода, который представляет собой первые 4 байта sha3 (Keccak-256) сигнатуры метода.

Вот пример вызова someFunctionon contract:contract.call(bytes4(sha3("someFunction()")))

Вот проверенная функция с передачей a methodIdв качестве параметра:

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

Протестируйте его в браузере Solidity , используя "0x2fbebd38", 9в качестве параметров invoke, затем убедитесь, что он _nравен 9.

Заметки:

  • 0x2fbebd38является результатом bytes4(sha3("foo(uint256)"))(не забывайте о необходимости использовать канонические типы, в данном случае uint256, в соответствии с ABI. )

  • Возвращаемые значения от call и callcode являются логическими, либо вызов выполнен успешно, либо вызов не выполнен. Невозможно вернуть значение из вызова, поскольку для этого контракту необходимо заранее знать тип возвращаемого значения.

Конечно, это внешняя функция - внутренние тоже существуют, и передача указателя на одну из них - это совсем другое дело...
Кажется, вы можете увидеть, как эта техника используется здесь: github.com/nexusdev/dappsys/blob/…
Этот метод имеет два недостатка: 1) стоит намного больше газа, 2) рассматривается как вызов из «внешнего» и, следовательно, недоступен, если целевая функция помечена как «внутренняя».

Чтобы добавить к ответу Ника Джонсона, типы функций в последних версиях Solidity теперь позволяют описывать указатели функций:

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

Типы функций — это типы функций. Переменные функционального типа могут быть назначены из функций, а функциональные параметры функционального типа могут использоваться для передачи функций и возврата функций из вызовов функций. Типы функций бывают двух видов — внутренние и внешние функции.

Что такое аби в таком случае?
какие затраты приносят типы функций при использовании газа?

ответ eth относится к вызовам внешних функций (между контрактами или с помощью внешнего интерфейса для вызова вашего собственного контракта); здесь я попытаюсь ответить на вызовы внутренних функций.

В настоящее время Solidity не предоставляет синтаксиса для описания типа указателя функции, поэтому вы не можете использовать их в качестве аргументов или возвращаемых значений. Однако функции являются первоклассными и могут быть присвоены переменным с помощью var; вот пример из руководства пользователя 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;
  }
}
Спасибо за полезный ответ :) Я думал, что могут быть и другие точки зрения, и я не усвоил эту часть документов, чтобы запомнить ее.
Не уверен, когда это изменилось, но это уже не правильно, так как, по крайней мере, уже в 0.4.16 Solidity предоставляет синтаксис для описания типа функций, поэтому вы можете передавать их в качестве аргументов ( solidity.readthedocs.io/en /v0.4.24/types.html ), см. ответ Майкла

Для тех, кто приходит из Google и ищет быстрый пример того, как передать функцию в качестве параметра, ознакомьтесь со следующей выдержкой кода (источник 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...
}

Как отмечалось в других ответах, на ваш вопрос есть разные ответы в зависимости от того, говорите ли вы о вызове функции «извне» или о вызове ее «внутри».

В том же контракте последние версии Solidity включают языковые функции для передачи «указателя функции» (на самом деле это не указатель, но он ведет себя как таковой). Другими словами, существуют переменные типа «функция». Это то, что я имею в виду под "внутренней" передачей функции и ее вызовом.

Для «внешних» вызовов, то есть вызовов функций на адрес в цепочке, вы должны использовать член типа адреса Solidity «call()» и/или Yul (сборка) и т. д. для эмуляции вызова контракта, поскольку Solidity скомпилирует такой вызов функции. при выражении как "someAddr.someFunction()" ... и это означает кодирование сигнатуры функции в качестве селектора (селекторы - это первые 4 байта хэша сигнатуры функции) плюс аргументы, которые вы хотите передать, и выполнение необходимые байт-коды EVM (сборка) с использованием этих данных.

Другими словами, для «внешних» функций «указатель на функцию» — это кортеж (адрес, селектор).