Aparte del public
modificador, Ethereum introduce el external
uno. Ambos pueden ser llamados fuera del contrato y dentro (el último por el patrón this.f()). Además, según los documentos :
Las funciones externas a veces son más eficientes cuando reciben grandes conjuntos de datos.
pero no hay más información sobre qué significa realmente sometimes
y si esta ganancia de eficiencia se mantiene también para llamadas internas.
¿Cuáles son las mejores prácticas para usar la palabra clave external
vs public
? ¿Hay algún patrón o recomendación?
Un ejemplo simple que demuestra este efecto se ve así:
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;
}
}
Al llamar a cada función, podemos ver que la public
función usa 496 gas, mientras que la external
función usa solo 261.
La diferencia se debe a que en las funciones públicas, Solidity copia inmediatamente los argumentos de la matriz en la memoria, mientras que las funciones externas pueden leer directamente desde los datos de llamada. La asignación de memoria es cara, mientras que la lectura de datos de llamadas es barata.
La razón por la que public
las funciones necesitan escribir todos los argumentos en la memoria es que las funciones públicas pueden llamarse internamente, lo que en realidad es un proceso completamente diferente al de las llamadas externas. Las llamadas internas se ejecutan a través de saltos en el código y los argumentos de la matriz se pasan internamente mediante punteros a la memoria. Por lo tanto, cuando el compilador genera el código para una función interna, esa función espera que sus argumentos estén ubicados en la memoria.
Para funciones externas, el compilador no necesita permitir llamadas internas, por lo que permite que los argumentos se lean directamente desde calldata, ahorrando el paso de copia.
En cuanto a las mejores prácticas, debe usar external
si espera que la función solo se llame externamente, y use public
si necesita llamar a la función internamente. Casi nunca tiene sentido usar el this.f()
patrón, ya que requiere un real CALL
para ejecutarse, lo cual es costoso. Además, pasar arreglos a través de este método sería mucho más costoso que pasarlos internamente.
Esencialmente, verá beneficios de rendimiento external
cada vez que solo llame a una función externamente y pase una gran cantidad de datos de llamada (por ejemplo, matrices grandes).
Ejemplos para diferenciar:
público - todos pueden acceder
externo: no se puede acceder internamente, solo externamente
interno: solo este contrato y los contratos derivados de él pueden acceder
privado: solo se puede acceder desde este contrato
La respuesta de Tjaden es excelente, pero creo que merece una actualización para las últimas versiones de Solidity. Su fragmento de código ya no se compila. Recibe este error ahora:
La ubicación de los datos debe ser
memory
ocalldata
para el parámetro en función, pero no se proporcionó ninguno.
Esto se debe al nuevo requisito de ser explícito al usar tipos de referencia , como matrices. También memory
y calldata
ahora están permitidos en todas las funciones independientemente de su visibilidad.
Una reescritura sería algo como esto:
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;
}
}
Como nota al margen, puede decodificar calldata
variables memory
pero no al revés.
Reestructurando la respuesta anterior para mayor claridad:
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;
}
}
Si sabe que la función solo se llamará externamente, useexternal
Casi nunca tiene sentido usar el patrón this.f(), ya que requiere que se ejecute una LLAMADA real, lo cual es costoso.
Acabo de comprobar el resultado con el último compilador. Parece que la reducción del costo del gas se debe a la memoria frente a los datos de llamada, es decir, no importa si una función es externa o pública. Lo que importa es si la matriz de entrada es memoria o datos de llamada.
// 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;
}
}
El public
es equivalente a external
más internal
.
En otras palabras, ambos public
y external
pueden llamarse desde fuera de su contrato (como MetaMask), pero estos dos solo public
pueden llamarse desde otras funciones dentro de su contrato.
Debido a que public
otorga más acceso que external
, la mejor práctica general es preferir external
. Entonces puede considerar cambiar a public
si comprende completamente las implicaciones de seguridad y diseño.
También tenga en cuenta que external
significa externo al contrato, no a la red. Tanto external
las public
funciones como pueden llamarse desde otro contrato dentro de la misma transacción. Del documento:
Las funciones externas son parte de la interfaz del contrato, lo que significa que se pueden llamar desde otros contratos y mediante transacciones.
Por lo tanto external
, no evita las llamadas reentrantes a la función. Para eso, uno puede usar ReentrancyGuard de OpenZeppelin , pero eso cuesta gasolina.
He probado con 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;
}
}
como puede ver, el especificador de visibilidad de función utilizado es externo
pragma solidity 0.8.14;
contract Test {
function test(uint[2] calldata a) public pure returns (uint){
return a[1]*2;
}
}
en este caso, el especificador de visibilidad de función utilizado es público
para ambos casos el gas consumido es 22116 (en remix) por lo que no hay diferencia entre externo y publico en cuanto al consumo de gas
Otra prueba:
pragma solidity 0.8.14;
contract Test1 {
uint a;
function test() external{
a = 1;
}
}
En este caso he probado una transacción que cambia el estado del contrato del contrato y el costo de ejecución es 43300
pragma solidity 0.8.14;
contract Test2 {
uint a;
function test() public{
a = 1;
}
}
ejecutando la misma función pero cambiando de externo a público obtuve el mismo resultado: el costo de ejecución es igual a 43300
Entonces, en términos de consumo de gas, no vi ninguna diferencia.
Entonces, para los compiladores más recientes, una función externa es una función pública que obliga a que sus argumentos residan en calldata, mientras que una función pública es una función que es visible desde el exterior y permite que sus argumentos residan tanto en la memoria como en calldata.
Jakub Wojciechowski
Guillermo Entriken
external
?justin
anton pegov
ulú
tjaden hess
Ender
function name() public view returns (string)
declararse ERC-20 Token Standard como externo para ahorrar algo de gasolina? Porque parece que la funciónname()
no se llama internamente.Henrique Barcelos
external
overpublic
se desvanece.Chan Ho Suh
calldata
ser utilizado para cualquier variable o parámetro, incluso eninternal
funciones.Domingo
external
yinternal
en lugar depublic
tener que llamar a ambos entonces? 2) Como dijo el comentarista anterior, ¿es esto ahora irrelevante desde0.6.9
? 3) ¿Agregarview
más mejora el rendimiento?Maxareo
Mukus
Samlaf
camello
CALL
,DELEGATECALL
, etc. y es más caro. En cuanto a los constructores, no lo son, aplicar visibilidad a un constructor es un poco estirar el concepto y no encaja perfectamente , razón por la cual se eliminó del lenguaje en 0.7.0.camello
memory
vscalldata
, que ya no está ligada a la visibilidad. En el último compiladorexternal
ypublic
le dará el mismo código de bytes exacto si eso es lo único que difiere , como se muestra en mi respuesta en "¿Usar externo sobre público en una biblioteca reduce los costos de gas?" .