Помимо public
модификатора Ethereum вводит еще external
один. Оба могут быть вызваны вне контракта и внутри (более поздний по шаблону this.f()). Более того, согласно документам :
Внешние функции иногда более эффективны, когда они получают большие массивы данных.
но нет дополнительной информации о том, что на самом деле sometimes
означает и сохраняется ли это повышение эффективности также для внутренних вызовов.
Каковы наилучшие методы использования ключевого слова external
vs public
? Есть ли схемы или рекомендации?
Простой пример, демонстрирующий этот эффект, выглядит так:
pragma solidity^0.4.12;
contract Test {
function test(uint[20] a) public returns (uint){
return a[10]*2;
}
function test2(uint[20] a) external returns (uint){
return a[10]*2;
}
}
Вызывая каждую функцию, мы видим, что public
функция использует 496 газа, а external
функция использует только 261.
Разница в том, что в публичных функциях Solidity сразу копирует аргументы массива в память, а внешние функции могут читать напрямую из calldata. Выделение памяти обходится дорого, тогда как чтение из calldata обходится дешево.
Причина, по которой public
функции должны записывать все аргументы в память, заключается в том, что общедоступные функции могут вызываться внутри, что на самом деле является совершенно другим процессом, чем внешние вызовы. Внутренние вызовы выполняются через переходы в коде, а аргументы массива передаются внутри с помощью указателей на память. Таким образом, когда компилятор генерирует код для внутренней функции, эта функция ожидает, что ее аргументы будут расположены в памяти.
Для внешних функций компилятору не нужно разрешать внутренние вызовы, поэтому он позволяет считывать аргументы непосредственно из данных вызова, экономя шаг копирования.
Что касается лучших практик, вы должны использовать external
, если вы ожидаете, что функция будет когда-либо вызываться только извне, и использовать, public
если вам нужно вызвать функцию внутри. Практически никогда не имеет смысла использовать this.f()
шаблон, так как для этого требуется выполнение реального CALL
, что дорого. Кроме того, передача массивов с помощью этого метода будет намного дороже, чем передача их внутри.
По сути, вы увидите преимущества в производительности external
каждый раз, когда вы вызываете функцию только извне и передаете много данных вызова (например, большие массивы).
Примеры для дифференциации:
общедоступный - все могут получить доступ
external - не может быть доступен внутри, только снаружи
внутренний - только этот контракт и контракты, вытекающие из него, могут получить доступ
частный - доступен только из этого контракта
Ответ Тьядена великолепен, но я думаю, что он заслуживает обновления для последних версий Solidity. Его фрагмент кода больше не компилируется. Вы получаете эту ошибку сейчас:
Местоположение данных должно быть
memory
илиcalldata
для параметра в функции, но ничего не было указано.
Это связано с новым требованием быть явным при использовании ссылочных типов , таких как массивы. Также memory
и calldata
теперь разрешены во всех функциях независимо от их видимости.
Переписывание будет выглядеть примерно так:
pragma solidity >=0.8.13;
contract ExternalPublicTest {
function foo(uint[20] memory a) public pure returns (uint){
return a[10]*2;
}
function bar(uint[20] calldata a) external pure returns (uint){
return a[10]*2;
}
}
В качестве примечания, вы можете декодировать calldata
переменные в , memory
но не наоборот.
Реструктуризация ответа выше для ясности:
pragma solidity^0.4.12;
contract Test {
/*
Cost: 496 Gas
This can be called internally or externally
Since internal calls expects function arguements to be allocated to memory, solidity immediately
copies array arguments to memory (This is what cost the additional gas.)
*/
function test(uint[20] a) public returns (uint) {
return a[10] * 2;
}
/*
Cost: Gas 261
Doesnt allow internal calls, read directly from CALLDATA saving on the copying step(memory allocation).
*/
function test(uint[20] a) external returns (uint) {
return a[10] * 2;
}
/*
Executed via JUMPs in code, array arguments are passed internally by pointers to memmory
Function expects argument to be located in memory.
*/
function test(uint[20] a) internal returns (uint) {
return a[10] * 2;
}
}
Если вы знаете, что функция будет вызываться только извне, используйтеexternal
Почти никогда не имеет смысла использовать шаблон this.f(), так как для этого требуется выполнение реального CALL, что дорого.
Только что проверил результат с последним компилятором. Кажется, что снижение стоимости газа вызвано памятью против calldata, т.е. является ли функция внешней или общедоступной, не имеет значения. Важно то, является ли входной массив памятью или данными вызова.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.4;
contract ExternalPublicTest {
function test(uint[20] memory a) public pure returns (uint){
return a[10]*2;
}
function test2(uint[20] calldata a) public pure returns (uint){
return a[10]*2;
}
}
Эквивалентно плюсу . public
_external
internal
Другими словами, оба public
и external
могут быть вызваны вне вашего контракта (например, MetaMask), но только эти два public
могут быть вызваны из других функций внутри вашего контракта.
Поскольку public
предоставляет больше доступа, чем external
, рекомендуется предпочесть external
. Затем вы можете подумать о переходе на public
него, если полностью понимаете последствия для безопасности и дизайна.
Также обратите внимание, что это external
означает внешний по отношению к контракту, а не к сети. Обе функции external
и public
могут вызываться из другого контракта в рамках одной и той же транзакции. Из документа:
Внешние функции являются частью интерфейса контракта, а значит, их можно вызывать из других контрактов и через транзакции.
Так external
что не предотвращает повторные вызовы функции. Для этого можно использовать ReentrancyGuard от OpenZeppelin , но это стоит газа.
Я пробовал с Solidity 0.8.14.
pragma solidity 0.8.14;
contract Test {
function test(uint[2] calldata a) external pure returns (uint){
return a[1]*2;
}
}
как вы можете видеть, используемый спецификатор видимости функции является внешним
pragma solidity 0.8.14;
contract Test {
function test(uint[2] calldata a) public pure returns (uint){
return a[1]*2;
}
}
в этом случае используемый спецификатор видимости функции является общедоступным
в обоих случаях потребляемый газ равен 22116 (в ремиксе), поэтому нет разницы между внешним и общественным потреблением газа.
Другой тест:
pragma solidity 0.8.14;
contract Test1 {
uint a;
function test() external{
a = 1;
}
}
В этом случае я протестировал транзакцию, которая меняет состояние контракта, а стоимость выполнения составляет 43300.
pragma solidity 0.8.14;
contract Test2 {
uint a;
function test() public{
a = 1;
}
}
выполнив ту же функцию, но изменив ее с внешней на публичную, я получил тот же результат: стоимость выполнения равна 43300
Так что по расходу газа я разницы не увидел
Таким образом, для последних компиляторов внешняя функция — это общедоступная функция, которая заставляет свои аргументы находиться в данных вызова, в то время как общедоступная функция — это функция, видимая извне и позволяющая своим аргументам находиться как в памяти, так и в данных вызова.
Якуб Войцеховский
Уильям Энтрикен
external
?Джастин
Антон Пегов
улу
Тьяден Хесс
Эндер
function name() public view returns (string)
стандарт токенов ERC-20 быть объявлен внешним для экономии газа? Потому что кажется, что функцияname()
не вызывается внутри.Энрике Барселуш
external
надpublic
исчезает.Чан-Хо Со
calldata
использовать любую переменную или параметр, даже вinternal
функциях.Доминик
external
иinternal
, чемpublic
если бы вам нужно было вызывать оба? 2) Как сказал комментатор выше, теперь это не имеет значения, так как0.6.9
? 3) Улучшает ли добавлениеview
производительность?Максарео
Мукус
самлаф
верблюд
CALL
, и т. дDELEGATECALL
., и стоит дороже. Что касается конструкторов - их нет, применение видимости к конструкторам несколько расширяет концепцию и не совсем подходит , поэтому она была удалена из языка в 0.7.0.верблюд
memory
vscalldata
, которое больше не связано с видимостью. В последнем компилятореexternal
иpublic
даст вам тот же точный байт-код, если это единственное, что отличается, как показано в моем ответе: «Снижает ли использование в библиотеке внешний над общедоступным какие-либо затраты на газ?» .