Недавно я заново изучал solidity, консолидировал детали и писал “Solidity Minimalist Primer” для начинающих (программисты могут найти другое руководство), обновляемое 1-3 раза в неделю.
Весь код и учебные пособия находятся с открытым исходным кодом на GitHub: github.com/AmazingAng/WTFSolidity
В Лекции 17: Отправка ETH мы говорили об использовании call для отправки ETH, и в этой лекции мы покажем вам, как использовать его для вызова контрактов.
call — это низкоуровневая функция-член типа адрес, которая используется для взаимодействия с другими контрактами. Возвращает (bool, data), что соответствует успешному выполнению вызова и возвращаемому значению целевой функции соответственно.
call является официально рекомендуемым методом отправки ETH путем запуска функций fallback или receive по solidity. Не рекомендуется вызывать другой контракт с вызовом, потому что, вызывая функцию небезопасного контракта, вы отдаете ей инициативу. Рекомендуемым методом по-прежнему является вызов функции после объявления переменной контракта, см. Лекция 19: Вызов других контрактов. Если мы не знаем исходный код или ABI контракта другой стороны, мы не можем генерировать переменные контракта, но мы все равно можем вызывать функции контракта другой стороны через call.
Правила использования call следующие:

Двоичная кодировка использует функцию структурированного кодирования abi.encodeWithSignature для получения:

Сигнатура функции: “Имя функции (тип параметра, разделенного запятыми)”. Например, abi.encodeWithSignature(“f(uint256,address)”, _x, _addr).
Кроме того, при вызове контракта можно указать количество ETH и газа, которое будет отправлено транзакцией:

Это кажется немного сложным, поэтому давайте возьмем пример приложения для звонков.
Давайте начнем с написания простого целевого контракта OtherContract и его развертывания, код в основном такой же, как и в Лекции 19, за исключением добавления резервной функции.

Этот контракт содержит переменную состояния x, журнал событий, который запускается при получении ETH, и три функции:
getBalance(): Возвращает баланс ETH контракта. setX(): внешняя оплачиваемая функция, которая может установить значение x и отправить ETH на контракт. getX(): Считывает значение x.
Давайте напишем контракт Call для вызова целевой функции контракта. Прежде всего, напишем определение события Response и выведем успешное выполнение и данные, возвращенные вызовом, чтобы мы могли наблюдать возвращаемое значение.

Мы определяем функцию callSetX для вызова setX() целевого контракта, переносим количество ETH в msg.value и выпускаем событие Response для вывода успеха и данных:

Затем мы вызываем callSetX, чтобы изменить переменную состояния _x на 5, а параметрами являются адрес OtherContract и 5, потому что целевая функция setX() не возвращает значение, поэтому выходные данные события Response равны 0x, то есть пусты.
Вызовем функцию getX(), которая вернет значение целевого контракта _x типа uint256. Мы можем использовать abi.decode для декодирования возвращаемого значения вызова, данных и считывания значения.

Из выходных данных события Response мы видим, что данные 0x0000000000000000000000000000000000000000000000000000000000000005. После abi.decode окончательное возвращаемое значение равно 5.
Если функция, которую мы вводим в вызов, не существует в целевом контракте, то будет запущена резервная функция целевого контракта.

В приведенном выше примере мы вызвали несуществующую функцию foo. Вызов по-прежнему может быть успешно выполнен и вернуть success, но на самом деле это резервная функция вызываемого целевого контракта.
В этом докладе мы показали, как использовать вызов низкоуровневой функции для вызова других контрактов. call не является рекомендуемым методом вызова контракта, так как он небезопасен. Но полезно позволить нам вызывать целевой контракт, не зная исходного кода и ABI.