Ich wurde von einer anderen Frage inspiriert , mehr über Stapeltiefenangriffe zu erfahren.
Wenn ich versuche, attack(1023)
Remix für diesen Vertrag auszuführen:
pragma solidity ^0.4.14;
contract Attacker {
uint x;
function attack(uint y) {
if (y > x) {
this.delegatecall(bytes4(sha3('attack(uint256)')), --y);
}
else {
throw ; // doesn't matter, we never get here
}
}
}
Mit 3M-Gas geht mir das Benzin 228 Anrufe tief aus. Gegen Ende scheinen die DELEGATECALL-Anweisungen etwa 1,5.000 Gas zu verbrauchen. Aber beim ersten DELEGATECALL sehe ich 94.000. Hier sind die Schritte, die ich verwende, um diese Nummer zu generieren:
attack(1023)
Aktualisieren. Ich lasse die Antwort unten stehen, da sie einige interessante Punkte enthält, aber ich glaube, ich bin dem endlich auf den Grund gegangen.
Der Schlüsselpunkt ist auf der Seite Feinheiten vergraben :
Ein CALL oder CREATE kann höchstens 63/64 des zum Zeitpunkt des CALL verbleibenden Gases verbrauchen; wenn ein CALL nach mehr als diesem vorgeschriebenen Maximum verlangt, dann wird der innere Ruf nur das vorgeschriebene maximale Gas haben, unabhängig davon, wie viel Gas verlangt wurde.
Dies wird im YP ziemlich klar erklärt , also weiß ich nicht, wie ich es verpasst habe :-)
Ihr CALL verbraucht also nicht so viel Gas, sondern Sie sind nicht in der Lage, das gesamte Gas, das Sie möchten, an den nächsten CALL in der Rekursion weiterzugeben. Wenn Ihr anfängliches Gas bei etwa 6,4 Mio. liegt, wird Ihr erster CALLed-Vertrag etwa 100.000 Gas weniger erhalten, als Sie dachten. Ich denke, das ist, was Sie sehen. Das überschüssige Gas, das nicht an den CALL weitergegeben wird, bleibt bei Vertragsbeendigung ungenutzt, es wird nicht verbraucht.
Also - kein Remix-Glitch (sorry, Remix!). Wenn überhaupt, sieht es so aus, als ob testrpc dies nicht erzwingt.
Ich denke, dies ist ein Berichtsproblem mit Remix. Ich habe eine geringfügige Variation Ihres Vertrags gegen testrpc (dh nicht Remix - in meiner eigenen Umgebung) ausgeführt und sehe nicht dasselbe.
pragma solidity ^0.4.13;
contract Attacker {
uint256 x;
function attack(uint256 y) payable returns(uint256) {
if (y > 0) {
this.delegatecall(bytes4(sha3('attack(uint256)')), --y);
}
else {
// Save the remaining gas in storage so that we can access it later
x = msg.gas;
return;
}
}
}
Das Start-y ist dann die Anzahl der delegatecall
Verwendungen. Die verbleibende Gasmenge bleibt im Speicher, auf den mit zugegriffen werden kann eth_getStorageAt
.
Ergebnisse (y ist die Anzahl der Delegiertenanrufe, der Wert ist das verbleibende Gas, diff ist die Differenz des Gases zwischen diesem und dem vorherigen).
In jedem Fall beträgt die Differenz 1112 Gas, einschließlich der Differenz zwischen null Delegiertenanrufen und einem Delegiertenanruf (mit Ausnahme von zusätzlichen 64 Gasen, die meiner Meinung nach auf das zusätzliche Nullbyte in den anfänglichen Anrufdaten zurückzuführen sind, wenn y = 0; dies wird mit 4 Gas berechnet statt 68 für ein Nicht-Null-Byte).
Das ist im Wesentlichen das, was ich erwarten würde, weshalb ich denke, dass Remix hier etwas wackelig sein muss.
Bearbeiten: Nur um die Remix-Schwachheit zu bestätigen. Mit demselben Programm: von der internen Gasmessung ( msg.gas
eingelagert) berichtet Remix, dass jedes zusätzliche Mal rund um den Delegiertenruf weitere etwa 45000 Gas verbraucht wird; Die von Remix auf der Registerkarte „Vertrag“ gemeldeten Vertragsausführungskosten steigen jedoch nur um 1339 Gas pro Delegiertenanruf.
Beachten Sie, dass das Gas vor dem sstore
von berechnet wird x
, sodass wir die (großen) Kosten dafür nicht sehen. Aber das macht nichts, da es sich um eine einmalige Sache handelt.
Schnitzer
Benjaminion
Schnitzer
L(n) = n - floor(n/64)
. Dieses Problem wurde stattdessen auf testrpc geöffnet: github.com/ethereumjs/testrpc/issues/367 @benjaminion Können Sie mir Ihre testrpc-Version/-Umgebung mitteilen, die ich der Problembeschreibung hinzufügen soll?Benjaminion