Skip to content

Commit 37ed35e

Browse files
committed
Implement EIP-1283
1 parent e68e7c3 commit 37ed35e

File tree

8 files changed

+312
-29
lines changed

8 files changed

+312
-29
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).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_NOOP_EIP1283 = 200
6+
GAS_SSTORE_INIT_EIP1283 = 20000
7+
GAS_SSTORE_CLEAN_EIP1283 = 5000
8+
GAS_SSTORE_CLEAR_REFUND_EIP1283 = 15000
9+
GAS_SSTORE_RESET_CLEAR_REFUND_EIP1283 = 19800
10+
GAS_SSTORE_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_NOOP_EIP1283
31+
else:
32+
if original_value == current_value:
33+
if original_value == 0:
34+
gas_cost = constants.GAS_SSTORE_INIT_EIP1283
35+
else:
36+
gas_cost = constants.GAS_SSTORE_CLEAN_EIP1283
37+
38+
if value == 0:
39+
gas_refund += constants.GAS_SSTORE_CLEAR_REFUND_EIP1283
40+
else:
41+
gas_cost = constants.GAS_SSTORE_NOOP_EIP1283
42+
43+
if original_value != 0:
44+
if current_value == 0:
45+
gas_refund -= constants.GAS_SSTORE_CLEAR_REFUND_EIP1283
46+
if value == 0:
47+
gas_refund += constants.GAS_SSTORE_CLEAR_REFUND_EIP1283
48+
49+
if original_value == value:
50+
if original_value == 0:
51+
gas_refund += constants.GAS_SSTORE_RESET_CLEAR_REFUND_EIP1283
52+
else:
53+
gas_refund += constants.GAS_SSTORE_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

+36-13
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,49 @@
1617
)
1718

1819

20+
def default_refund_strategy(meter: "GasMeter", amount: int) -> None:
21+
if amount < 0:
22+
raise ValidationError("Gas refund amount must be positive")
23+
24+
meter.gas_refunded += amount
25+
26+
meter.logger.trace(
27+
'GAS REFUND: %s + %s -> %s',
28+
meter.gas_refunded - amount,
29+
amount,
30+
meter.gas_refunded,
31+
)
32+
33+
34+
def allow_negative_refund_strategy(meter: "GasMeter", amount: int) -> None:
35+
meter.gas_refunded += amount
36+
37+
meter.logger.trace(
38+
'GAS REFUND: %s + %s -> %s',
39+
meter.gas_refunded - amount,
40+
amount,
41+
meter.gas_refunded,
42+
)
43+
44+
45+
RefundStrategy = Callable[["GasMeter", int], None]
46+
47+
1948
class GasMeter(object):
49+
2050
start_gas = None # type: int
2151

2252
gas_refunded = None # type: int
2353
gas_remaining = None # type: int
2454

2555
logger = cast(TraceLogger, logging.getLogger('eth.gas.GasMeter'))
2656

27-
def __init__(self, start_gas: int) -> None:
57+
def __init__(self,
58+
start_gas: int,
59+
refund_strategy: RefundStrategy = default_refund_strategy) -> None:
2860
validate_uint256(start_gas, title="Start Gas")
2961

62+
self.refund_strategy = refund_strategy
3063
self.start_gas = start_gas
3164

3265
self.gas_remaining = self.start_gas
@@ -70,14 +103,4 @@ def return_gas(self, amount: int) -> None:
70103
)
71104

72105
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
77-
78-
self.logger.trace(
79-
'GAS REFUND: %s + %s -> %s',
80-
self.gas_refunded - amount,
81-
amount,
82-
self.gas_refunded,
83-
)
106+
return self.refund_strategy(self, amount)

0 commit comments

Comments
 (0)