Neben dem public
Modifikator führt Ethereum den external
einen ein. Beide können außerhalb des Vertrags und innerhalb (das spätere durch this.f()-Muster) aufgerufen werden. Darüber hinaus laut den Dokumenten :
Externe Funktionen sind manchmal effizienter, wenn sie große Datenarrays empfangen.
aber es gibt keine näheren infos was das eigentlich sometimes
bedeutet und ob dieser effizienzgewinn auch für interne gespräche stattfindet.
Was sind die Best Practices für die Verwendung von external
vs- public
Keywords? Gibt es Muster oder Empfehlungen?
Ein einfaches Beispiel, das diesen Effekt demonstriert, sieht so aus:
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;
}
}
Wenn wir jede Funktion aufrufen, können wir sehen, dass die public
Funktion 496 Gas verbraucht, während die external
Funktion nur 261 verbraucht.
Der Unterschied besteht darin, dass Solidity in öffentlichen Funktionen Array-Argumente sofort in den Speicher kopiert, während externe Funktionen direkt aus Aufrufdaten lesen können. Die Speicherzuweisung ist teuer, während das Lesen von Anrufdaten billig ist.
Der Grund, warum public
Funktionen alle Argumente in den Speicher schreiben müssen, liegt darin, dass öffentliche Funktionen intern aufgerufen werden können, was eigentlich ein völlig anderer Prozess als externe Aufrufe ist. Interne Aufrufe werden über Sprünge im Code ausgeführt, und Array-Argumente werden intern durch Zeiger auf den Speicher übergeben. Wenn also der Compiler den Code für eine interne Funktion generiert, erwartet diese Funktion, dass sich ihre Argumente im Speicher befinden.
Für externe Funktionen muss der Compiler keine internen Aufrufe zulassen und erlaubt es daher, Argumente direkt aus calldata zu lesen, wodurch der Kopierschritt eingespart wird.
Als Best Practices sollten Sie verwenden, external
wenn Sie davon ausgehen, dass die Funktion immer nur extern aufgerufen wird, und verwenden, public
wenn Sie die Funktion intern aufrufen müssen. Es macht fast nie Sinn, das this.f()
Muster zu verwenden, da dies die CALL
Ausführung eines Real erfordert, was teuer ist. Außerdem wäre die Übergabe von Arrays über diese Methode weitaus teurer als die interne Übergabe.
Sie werden im Wesentlichen immer dann Leistungsvorteile sehen, external
wenn Sie eine Funktion nur extern aufrufen und viele Aufrufdaten (z. B. große Arrays) übergeben.
Beispiele zur Unterscheidung:
öffentlich - alle können darauf zugreifen
extern - Kann nicht intern, sondern nur extern aufgerufen werden
intern - nur dieser Vertrag und davon abgeleitete Verträge können zugreifen
privat - kann nur von diesem Vertrag aus aufgerufen werden
Tjadens Antwort ist großartig, aber ich denke, dass sie ein Update für die neuesten Versionen von Solidity verdient. Sein Code-Snippet lässt sich nicht mehr kompilieren. Sie erhalten jetzt diesen Fehler:
Datenposition muss
memory
odercalldata
für Parameter in Funktion sein, aber es wurde keine angegeben.
Das liegt an der neuen Anforderung, bei der Verwendung von Referenztypen wie Arrays explizit zu sein . Auch memory
und calldata
sind jetzt unabhängig von ihrer Sichtbarkeit in allen Funktionen erlaubt.
Eine Umschreibung würde in etwa so aussehen:
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;
}
}
Als Randnotiz können Sie calldata
Variablen in dekodieren, memory
aber nicht umgekehrt.
Umstrukturierung der obigen Antwort zur Verdeutlichung:
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;
}
}
Wenn Sie wissen, dass die Funktion nur extern aufgerufen wird, verwenden Sie sieexternal
Es macht fast nie Sinn, das this.f()-Muster zu verwenden, da dies die Ausführung eines echten CALL erfordert, der teuer ist.
Ich habe gerade das Ergebnis mit dem neuesten Compiler überprüft. Es scheint, dass die Reduzierung der Gaskosten durch Speicher vs. Anrufdaten verursacht wird, dh ob eine Funktion extern oder öffentlich ist, spielt keine Rolle. Entscheidend ist, ob das Eingabearray Speicher oder Aufrufdaten ist.
// 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;
}
}
Das public
ist gleichbedeutend mit external
plus internal
.
Mit anderen Worten, sowohl public
als auch external
können von außerhalb Ihres Vertrags aufgerufen werden (wie MetaMask), aber von diesen beiden public
können nur von anderen Funktionen innerhalb Ihres Vertrags aufgerufen werden.
Da public
mehr Zugriff als gewährt external
wird, ist die allgemein bewährte Methode, zu bevorzugen external
. Dann können Sie einen Wechsel zu erwägen, public
wenn Sie die Auswirkungen auf Sicherheit und Design vollständig verstehen.
Beachten Sie auch, dass external
dies vertragsextern bedeutet, nicht das Netzwerk. Sowohl external
als public
auch Funktionen können von einem anderen Vertrag innerhalb derselben Transaktion aufgerufen werden. Aus dem Dokument:
Externe Funktionen sind Teil der Vertragsschnittstelle, können also aus anderen Verträgen und über Transaktionen aufgerufen werden.
Verhindert also external
nicht ablaufinvariante Aufrufe der Funktion. Dafür kann man den ReentrancyGuard von OpenZeppelin verwenden , aber das kostet Benzin.
Ich habe es mit solidity 0.8.14 versucht
pragma solidity 0.8.14;
contract Test {
function test(uint[2] calldata a) external pure returns (uint){
return a[1]*2;
}
}
Wie Sie sehen können, ist der verwendete Sichtbarkeitsbezeichner der Funktion extern
pragma solidity 0.8.14;
contract Test {
function test(uint[2] calldata a) public pure returns (uint){
return a[1]*2;
}
}
in diesem Fall ist der verwendete Funktionssichtbarkeitsbezeichner öffentlich
In beiden Fällen beträgt das verbrauchte Gas 22116 (im Remix), sodass beim Gasverbrauch kein Unterschied zwischen extern und öffentlich besteht
Ein weiterer Test:
pragma solidity 0.8.14;
contract Test1 {
uint a;
function test() external{
a = 1;
}
}
In diesem Fall habe ich eine Transaktion getestet, die den Vertragsstatus des Vertrags ändert, und die Ausführungskosten betragen 43300
pragma solidity 0.8.14;
contract Test2 {
uint a;
function test() public{
a = 1;
}
}
Wenn ich dieselbe Funktion ausführe, aber von extern zu öffentlich wechsele, habe ich dasselbe Ergebnis erhalten: Die Ausführungskosten betragen 43300
Also beim Spritverbrauch sehe ich keinen Unterschied
Für die neuesten Compiler ist eine externe Funktion also eine öffentliche Funktion, die ihre Argumente dazu zwingt, sich in Calldata zu befinden, während eine öffentliche Funktion eine Funktion ist, die von außen sichtbar ist und es ihren Argumenten ermöglicht, sich sowohl im Speicher als auch in Calldata zu befinden.
Jakob Wojciechowski
Wilhelm Entriken
external
?Justin
Anton Pegov
ulu
Tjaden Hess
Ende
function name() public view returns (string)
der ERC-20 Token Standard als extern deklariert werden, um etwas Gas zu sparen? Weil es so aussieht, als würde die Funktionname()
nicht intern aufgerufen.Henrique Barcelona
external
overpublic
verblasst also.Chan-Ho Suh
calldata
Verwendung für jede Variable oder jeden Parameter, sogar ininternal
Funktionen.Dominik
external
und -internal
Funktion zu haben, alspublic
wenn Sie dann beide aufrufen müssten? 2) Wie der Kommentator oben sagte, ist dies jetzt irrelevant, da0.6.9
? 3) Verbessert das Hinzufügenview
weiterer Leistung?Maxareo
Mukus
Samlaf
Kamel
CALL
,DELEGATECALL
, etc. und ist teurer. Was Konstruktoren angeht - sie sind beides nicht, das Anwenden von Sichtbarkeit auf Konstruktoren dehnt das Konzept irgendwie aus und passt nicht perfekt , weshalb es in 0.7.0 aus der Sprache entfernt wurde.Kamel
memory
vscalldata
, die nicht mehr an die Sichtbarkeit gebunden ist. Auf dem neuesten Compilerexternal
undpublic
geben Ihnen genau den gleichen Bytecode, wenn dies das einzige ist, was sich unterscheidet, wie in meiner Antwort in "Reduziert die Verwendung von extern über öffentlich in einer Bibliothek die Gaskosten?" .