-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathestimation.py
218 lines (175 loc) · 8.43 KB
/
estimation.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
HEADER_STORAGE_OFFSET = 64
CODE_OFFSET = 128
VERKLE_NODE_WIDTH = 256
MAIN_STORAGE_OFFSET = 256 ** 31
WITNESS_BRANCH_COST = 1900
WITNESS_CHUNK_COST = 200
COLD_SLOAD_COST = 2100
COLD_ACCOUNT_ACCESS_COST = 2600
SUBTREE_EDIT_COST = 3000
CHUNK_EDIT_COST = 500
CHUNK_FILL_COST = 6200
# Pre-verkle (EIP-2929, EIP-2200 etc.) constants
G_CALLVALUE = 9000
SSTORE_SET_GAS = 20000
WARM_STORAGE_READ_COST = 100
def calculate_chunks_read_verkle_gas_cost(contract_chunks):
# NOTE: subtree 0 initialization cost will be tracked here
branches_access_events = {}
code_cost = 0
for [contract_chunk, _] in contract_chunks.items():
code_cost += WITNESS_CHUNK_COST
branch_id = contract_chunk // 256
if branch_id not in branches_access_events:
branches_access_events[branch_id] = True
code_cost += WITNESS_BRANCH_COST
return code_cost
def get_storage_slot_tree_keys(storage_key_hex):
storage_key = int(storage_key_hex, 16)
# special storage for slots 0..64
if storage_key < (CODE_OFFSET - HEADER_STORAGE_OFFSET):
pos = HEADER_STORAGE_OFFSET + storage_key
else:
pos = MAIN_STORAGE_OFFSET + storage_key
branch_id = pos // VERKLE_NODE_WIDTH
sub_id = pos % VERKLE_NODE_WIDTH
return [branch_id, sub_id]
# subtract current costs and apply new costs
def calculate_slots_verkle_difference(contract_slots):
# NOTE: subtree 0 is already initialized in "calculate_chunks_read_verkle_gas_cost"
accessed_subtrees = {0: True}
accessed_leaves = {0: {}}
edited_subtrees = {}
edited_leaves = {}
new_costs = 0
old_cost = 0
for slot_id in contract_slots:
slot = contract_slots[slot_id]
[branch_id, sub_id] = get_storage_slot_tree_keys(slot_id)
for opcode in slot:
old_cost += opcode['gas']
if opcode['opcode'] == 'SSTORE':
if branch_id in edited_subtrees and sub_id in edited_leaves[branch_id]:
new_costs += WARM_STORAGE_READ_COST # this is not explicitly specified by EIP-4762
if branch_id not in edited_subtrees:
new_costs += SUBTREE_EDIT_COST
edited_subtrees[branch_id] = True
edited_leaves[branch_id] = {}
if sub_id not in edited_leaves[branch_id]:
edited_leaves[branch_id][sub_id] = True
if opcode['gas'] == SSTORE_SET_GAS: # considering pre-verkle value 0 as equivalent to 'None'
print(f"Note: detected a 20000 gas SSTORE which may disproportionately affect Verkle gas costs")
new_costs += CHUNK_FILL_COST
else:
new_costs += CHUNK_EDIT_COST
# SLOAD and SSTORE opcodes with a given address and key process
# an access event of the form (address, tree_key, sub_key)
if opcode['opcode'] == 'SLOAD' or opcode['opcode'] == 'SSTORE':
if branch_id in accessed_subtrees and sub_id in accessed_leaves[branch_id]:
new_costs += WARM_STORAGE_READ_COST # this is not explicitly specified by EIP-4762
if branch_id not in accessed_subtrees:
new_costs += WITNESS_BRANCH_COST
accessed_subtrees[branch_id] = True
accessed_leaves[branch_id] = {}
if sub_id not in accessed_leaves[branch_id]:
accessed_leaves[branch_id][sub_id] = True
new_costs += WITNESS_CHUNK_COST
return new_costs - old_cost
# it appears that all the refund-based logic in EIP-2200 and EIP-2929 is removed in EIP-4762
def calculate_slots_read_verkle_removed_refunds(contract_slots):
refunds = 0
for slot_id in contract_slots:
for opcode in contract_slots[slot_id]:
refunds += opcode['refund']
return refunds
def calculate_call_opcode_verkle_savings(trace_data, address):
if address not in trace_data['count_call_with_value']:
return 0
return -1 * trace_data['count_call_with_value'][address] * G_CALLVALUE
def calculate_touching_opcode_cost_difference(trace_data):
old_cost = 0
new_cost = 0
difference = 0
for address in trace_data['touched']:
if address not in trace_data['chunks']:
# this address was only accessed by an 'ADDRESS TOUCHING' opcode
old_cost += COLD_ACCOUNT_ACCESS_COST
# NOTE: this is not exactly correct, some opcodes cause multiple chunk access events
new_cost += WITNESS_BRANCH_COST + WITNESS_CHUNK_COST
difference += new_cost - old_cost
return difference
def calculate_create2_opcode_cost_difference(trace_data, address):
if address not in trace_data['created_contracts']:
return 0
difference = 0
for contract in trace_data['created_contracts'][address]:
size_bytes = trace_data['code_sizes'][contract]
if not size_bytes > 0:
raise Exception(f"Invalid contract size {contract} {size_bytes}")
old_cost = size_bytes * 200
code_chunks_count = (size_bytes + 30) // 31
main_code_chunks_in_main_branch = CODE_OFFSET - HEADER_STORAGE_OFFSET
extra_branches_count = (code_chunks_count - main_code_chunks_in_main_branch + 255) // 256
# 1 extra branch for counting the cost of editing the "main" branch
# 5 "chunks" for VERSION_LEAF_KEY, NONCE_LEAF_KEY, BALANCE_LEAF_KEY, CODE_KECCAK_LEAF_KEY, CODE_SIZE_LEAF_KEY
new_cost = (
CHUNK_FILL_COST * (code_chunks_count + 5) +
SUBTREE_EDIT_COST * (extra_branches_count + 1)
)
difference += new_cost - old_cost
return difference
def get_name(address, names):
if address not in names or names[address] is None:
return address
else:
short_address = "(" + address[:8] + "..." + address[38:] + ")"
return names[address] + " " + short_address
def estimate_verkle_gas_cost_difference(trace_data, names):
results = {
'per_contract_result': {},
'total_gas_cost_difference': 0
}
# Dump number of unique slots used by each address
chunks = trace_data['chunks']
for addr in chunks:
max_chunk = max(chunks[addr].keys())
num_chunks = len(chunks[addr])
addr_code_cost = calculate_chunks_read_verkle_gas_cost(chunks[addr])
addr_storage_difference = 0
addr_storage_removed_refund = 0
if addr in trace_data['slots']:
addr_slots = trace_data['slots'][addr]
addr_storage_difference = calculate_slots_verkle_difference(addr_slots)
addr_storage_removed_refund = calculate_slots_read_verkle_removed_refunds(addr_slots)
call_opcode_with_value_diff = calculate_call_opcode_verkle_savings(trace_data, addr)
create2_opcode_cost_difference = calculate_create2_opcode_cost_difference(trace_data, addr)
code_size_chunks = (trace_data['code_sizes'][addr] + 30) // 31
contract_name = get_name(addr, names)
per_contract_diff = (
addr_code_cost +
addr_storage_difference +
create2_opcode_cost_difference +
addr_storage_removed_refund +
call_opcode_with_value_diff
)
per_contract_result = {
'contract_name': contract_name,
'max_chunk': max_chunk,
'num_chunks': num_chunks,
'create2_opcode_cost_difference': create2_opcode_cost_difference,
'addr_code_cost': addr_code_cost,
'addr_storage_difference': addr_storage_difference,
# EIP-2200 heavily relies on refunds which is superseded by EIP-4762
'addr_storage_removed_refund': addr_storage_removed_refund,
'call_opcode_with_value_diff': call_opcode_with_value_diff,
'code_size': trace_data['code_sizes'][addr],
'code_size_chunks': code_size_chunks,
'per_contract_diff': per_contract_diff
}
results['per_contract_result'][addr] = per_contract_result
results['total_gas_cost_difference'] += per_contract_diff
# touching an extra address is per-transaction and cannot be tracked per-contract
address_touching_opcode_cost_difference = calculate_touching_opcode_cost_difference(trace_data)
results['address_touching_opcode_cost_difference'] = address_touching_opcode_cost_difference
results['total_gas_cost_difference'] += address_touching_opcode_cost_difference
return results