Skip to content

Commit 90423f1

Browse files
committed
Implement EIP-1283
1 parent 9713c29 commit 90423f1

File tree

8 files changed

+298
-22
lines changed

8 files changed

+298
-22
lines changed

eth/db/account.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -239,11 +239,11 @@ def has_root(self, state_root: bytes) -> bool:
239239
#
240240
# Storage
241241
#
242-
def get_storage(self, address, slot):
242+
def get_storage(self, address, slot, from_journal=True):
243243
validate_canonical_address(address, title="Storage Address")
244244
validate_uint256(slot, title="Storage Slot")
245245

246-
account = self._get_account(address)
246+
account = self._get_account(address, from_journal)
247247
storage = HashTrie(HexaryTrie(self._journaldb, account.storage_root))
248248

249249
slot_as_key = pad32(int_to_big_endian(slot))
@@ -375,8 +375,8 @@ def account_is_empty(self, address):
375375
#
376376
# Internal
377377
#
378-
def _get_account(self, address):
379-
rlp_account = self._journaltrie.get(address, b'')
378+
def _get_account(self, address, from_journal=True):
379+
rlp_account = (self._journaltrie if from_journal else self._trie_cache).get(address, b'')
380380
if rlp_account:
381381
account = rlp.decode(rlp_account, sedes=Account)
382382
else:

eth/vm/computation.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ def __init__(self,
130130

131131
self._memory = Memory()
132132
self._stack = Stack()
133-
self._gas_meter = GasMeter(message.gas)
133+
self._gas_meter = self.get_gas_meter()
134134

135135
self.children = []
136136
self.accounts_to_delete = {}
@@ -406,6 +406,9 @@ def add_log_entry(self, account: Address, topics: List[int], data: bytes) -> Non
406406
#
407407
# Getters
408408
#
409+
def get_gas_meter(self) -> GasMeter:
410+
return GasMeter(self.msg.gas)
411+
409412
def get_accounts_for_deletion(self) -> Tuple[Tuple[bytes, bytes], ...]:
410413
if self.is_error:
411414
return tuple()

eth/vm/forks/constantinople/computation.py

+10
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
from eth.vm.forks.byzantium.computation import (
99
ByzantiumComputation
1010
)
11+
from eth.vm.gas_meter import (
12+
allow_negative_refund_strategy,
13+
GasMeter,
14+
)
1115

1216
from .opcodes import CONSTANTINOPLE_OPCODES
1317

@@ -27,3 +31,9 @@ class ConstantinopleComputation(ByzantiumComputation):
2731
# Override
2832
opcodes = CONSTANTINOPLE_OPCODES
2933
_precompiles = CONSTANTINOPLE_PRECOMPILES
34+
35+
def get_gas_meter(self) -> GasMeter:
36+
return GasMeter(
37+
self.msg.gas,
38+
allow_negative_refund_strategy
39+
)

eth/vm/forks/constantinople/constants.py

+6
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,11 @@
22

33

44
GAS_EXTCODEHASH_EIP1052 = 400
5+
GAS_SSTORE_EIP1283_NOOP = 200
6+
GAS_SSTORE_EIP1283_INIT = 20000
7+
GAS_SSTORE_EIP1283_CLEAN = 5000
8+
GAS_SSTORE_EIP1283_CLEAR_REFUND = 15000
9+
GAS_SSTORE_EIP1283_RESET_CLEAR_REFUND = 19800
10+
GAS_SSTORE_EIP1283_RESET_REFUND = 4800
511

612
EIP1234_BLOCK_REWARD = 2 * denoms.ether

eth/vm/forks/constantinople/opcodes.py

+8
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
from eth.vm.forks.constantinople.constants import (
1717
GAS_EXTCODEHASH_EIP1052
1818
)
19+
from eth.vm.forks.constantinople.storage import (
20+
sstore_eip1283,
21+
)
1922
from eth.vm.logic import (
2023
arithmetic,
2124
context,
@@ -52,6 +55,11 @@
5255
mnemonic=mnemonics.CREATE2,
5356
gas_cost=constants.GAS_CREATE,
5457
)(),
58+
opcode_values.SSTORE: as_opcode(
59+
logic_fn=sstore_eip1283,
60+
mnemonic=mnemonics.SSTORE,
61+
gas_cost=constants.GAS_NULL,
62+
),
5563
}
5664

5765
CONSTANTINOPLE_OPCODES = merge(
+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
from eth.constants import (
2+
UINT256
3+
)
4+
from eth.vm.forks.constantinople import (
5+
constants
6+
)
7+
8+
from eth.utils.hexadecimal import (
9+
encode_hex,
10+
)
11+
12+
13+
def sstore_eip1283(computation):
14+
slot, value = computation.stack_pop(num_items=2, type_hint=UINT256)
15+
16+
current_value = computation.state.account_db.get_storage(
17+
address=computation.msg.storage_address,
18+
slot=slot,
19+
)
20+
21+
original_value = computation.state.account_db.get_storage(
22+
address=computation.msg.storage_address,
23+
slot=slot,
24+
from_journal=False
25+
)
26+
27+
gas_refund = 0
28+
29+
if current_value == value:
30+
gas_cost = constants.GAS_SSTORE_EIP1283_NOOP
31+
else:
32+
if original_value == current_value:
33+
if original_value == 0:
34+
gas_cost = constants.GAS_SSTORE_EIP1283_INIT
35+
else:
36+
gas_cost = constants.GAS_SSTORE_EIP1283_CLEAN
37+
38+
if value == 0:
39+
gas_refund += constants.GAS_SSTORE_EIP1283_CLEAR_REFUND
40+
else:
41+
gas_cost = constants.GAS_SSTORE_EIP1283_NOOP
42+
43+
if original_value != 0:
44+
if current_value == 0:
45+
gas_refund -= constants.GAS_SSTORE_EIP1283_CLEAR_REFUND
46+
if value == 0:
47+
gas_refund += constants.GAS_SSTORE_EIP1283_CLEAR_REFUND
48+
49+
if original_value == value:
50+
if original_value == 0:
51+
gas_refund += constants.GAS_SSTORE_EIP1283_RESET_CLEAR_REFUND
52+
else:
53+
gas_refund += constants.GAS_SSTORE_EIP1283_RESET_REFUND
54+
55+
computation.consume_gas(
56+
gas_cost,
57+
reason="SSTORE: {0}[{1}] -> {2} (current: {3} / original: {4})".format(
58+
encode_hex(computation.msg.storage_address),
59+
slot,
60+
value,
61+
current_value,
62+
original_value,
63+
)
64+
)
65+
66+
if gas_refund:
67+
computation.refund_gas(gas_refund)
68+
69+
computation.state.account_db.set_storage(
70+
address=computation.msg.storage_address,
71+
slot=slot,
72+
value=value,
73+
)

eth/vm/gas_meter.py

+22-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import logging
22
from typing import (
3-
cast
3+
Callable,
4+
cast,
45
)
56
from eth_utils import (
67
ValidationError,
@@ -16,17 +17,35 @@
1617
)
1718

1819

20+
def default_refund_strategy(gas_refunded_total: int, amount: int) -> int:
21+
if amount < 0:
22+
raise ValidationError("Gas refund amount must be positive")
23+
24+
return gas_refunded_total + amount
25+
26+
27+
def allow_negative_refund_strategy(gas_refunded_total: int, amount: int) -> int:
28+
return gas_refunded_total + amount
29+
30+
31+
RefundStrategy = Callable[[int, int], int]
32+
33+
1934
class GasMeter(object):
35+
2036
start_gas = None # type: int
2137

2238
gas_refunded = None # type: int
2339
gas_remaining = None # type: int
2440

2541
logger = cast(TraceLogger, logging.getLogger('eth.gas.GasMeter'))
2642

27-
def __init__(self, start_gas: int) -> None:
43+
def __init__(self,
44+
start_gas: int,
45+
refund_strategy: RefundStrategy = default_refund_strategy) -> None:
2846
validate_uint256(start_gas, title="Start Gas")
2947

48+
self.refund_strategy = refund_strategy
3049
self.start_gas = start_gas
3150

3251
self.gas_remaining = self.start_gas
@@ -70,10 +89,7 @@ def return_gas(self, amount: int) -> None:
7089
)
7190

7291
def refund_gas(self, amount: int) -> None:
73-
if amount < 0:
74-
raise ValidationError("Gas refund amount must be positive")
75-
76-
self.gas_refunded += amount
92+
self.gas_refunded = self.refund_strategy(self.gas_refunded, amount)
7793

7894
self.logger.trace(
7995
'GAS REFUND: %s + %s -> %s',

0 commit comments

Comments
 (0)