This repository has been archived by the owner on Aug 21, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 107
/
gas_usage.rs
208 lines (184 loc) · 9.73 KB
/
gas_usage.rs
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
use std::collections::HashMap;
use starknet_api::transaction::Fee;
use super::fee_utils::{calculate_tx_l1_gas_usage, get_fee_by_l1_gas_usage};
use crate::abi::constants;
use crate::block_context::BlockContext;
use crate::execution::call_info::{CallInfo, MessageL1CostInfo};
use crate::fee::eth_gas_constants;
use crate::fee::os_resources::OS_RESOURCES;
use crate::state::cached_state::StateChangesCount;
use crate::transaction::account_transaction::AccountTransaction;
use crate::transaction::objects::{
HasRelatedFeeType, ResourcesMapping, TransactionExecutionResult, TransactionPreValidationResult,
};
#[cfg(test)]
#[path = "gas_usage_test.rs"]
pub mod test;
/// Returns an estimation of the L1 gas amount that will be used (by Starknet's update state and
/// the verifier) following the addition of a transaction with the given parameters to a batch;
/// e.g., a message from L2 to L1 is followed by a storage write operation in Starknet L1 contract
/// which requires gas.
pub fn calculate_tx_gas_usage<'a>(
call_infos: impl Iterator<Item = &'a CallInfo>,
state_changes_count: StateChangesCount,
l1_handler_payload_size: Option<usize>,
) -> TransactionExecutionResult<usize> {
let message_l1_cost_info = MessageL1CostInfo::new(call_infos, l1_handler_payload_size)?;
let n_l2_to_l1_messages = message_l1_cost_info.l2_to_l1_payloads_length.len();
let n_l1_to_l2_messages = usize::from(l1_handler_payload_size.is_some());
let starknet_gas_usage =
// Starknet's updateState gets the message segment as an argument.
message_l1_cost_info.message_segment_length * eth_gas_constants::GAS_PER_MEMORY_WORD
// Starknet's updateState increases a (storage) counter for each L2-to-L1 message.
+ n_l2_to_l1_messages * eth_gas_constants::GAS_PER_ZERO_TO_NONZERO_STORAGE_SET
// Starknet's updateState decreases a (storage) counter for each L1-to-L2 consumed message.
// (Note that we will probably get a refund of 15,000 gas for each consumed message but we
// ignore it since refunded gas cannot be used for the current transaction execution).
+ n_l1_to_l2_messages * eth_gas_constants::GAS_PER_COUNTER_DECREASE
+ get_consumed_message_to_l2_emissions_cost(l1_handler_payload_size)
+ get_log_message_to_l1_emissions_cost(&message_l1_cost_info.l2_to_l1_payloads_length);
// Calculate the effect of the transaction on the output data availability segment.
let residual_onchain_data_cost = get_onchain_data_cost(state_changes_count);
let sharp_gas_usage = message_l1_cost_info.message_segment_length
* eth_gas_constants::SHARP_GAS_PER_MEMORY_WORD
+ residual_onchain_data_cost;
Ok(starknet_gas_usage + sharp_gas_usage)
}
/// Returns the number of felts added to the output data availability segment as a result of adding
/// a transaction to a batch. Note that constant cells - such as the one that holds the number of
/// modified contracts - are not counted.
fn get_onchain_data_segment_length(state_changes_count: StateChangesCount) -> usize {
// For each newly modified contract:
// contract address (1 word).
// + 1 word with the following info: A flag indicating whether the class hash was updated, the
// number of entry updates, and the new nonce.
let mut onchain_data_segment_length = state_changes_count.n_modified_contracts * 2;
// For each class updated (through a deploy or a class replacement).
onchain_data_segment_length +=
state_changes_count.n_class_hash_updates * constants::CLASS_UPDATE_SIZE;
// For each modified storage cell: key, new value.
onchain_data_segment_length += state_changes_count.n_storage_updates * 2;
// For each compiled class updated (through declare): class_hash, compiled_class_hash
onchain_data_segment_length += state_changes_count.n_compiled_class_hash_updates * 2;
onchain_data_segment_length
}
/// Returns the gas cost of publishing the onchain data on L1.
pub fn get_onchain_data_cost(state_changes_count: StateChangesCount) -> usize {
let onchain_data_segment_length = get_onchain_data_segment_length(state_changes_count);
// TODO(Yoni, 1/5/2024): count the exact amount of nonzero bytes for each DA entry.
let naive_cost = onchain_data_segment_length * eth_gas_constants::SHARP_GAS_PER_DA_WORD;
// For each modified contract, the expected non-zeros bytes in the second word are:
// 1 bytes for class hash flag; 2 for number of storage updates (up to 64K);
// 3 for nonce update (up to 16M).
let modified_contract_cost = eth_gas_constants::get_calldata_word_cost(1 + 2 + 3);
let modified_contract_discount =
eth_gas_constants::GAS_PER_MEMORY_WORD - modified_contract_cost;
let mut discount = state_changes_count.n_modified_contracts * modified_contract_discount;
// Up to balance of 8*(10**10) ETH.
let fee_balance_value_cost = eth_gas_constants::get_calldata_word_cost(12);
discount += eth_gas_constants::GAS_PER_MEMORY_WORD - fee_balance_value_cost;
if naive_cost < discount {
// Cost must be non-negative after discount.
0
} else {
naive_cost - discount
}
}
/// Returns the number of felts added to the output messages segment as a result of adding
/// a transaction with the given parameters to a batch. Note that constant cells - such as the one
/// that holds the segment size - are not counted.
pub fn get_message_segment_length(
l2_to_l1_payloads_length: &[usize],
l1_handler_payload_size: Option<usize>,
) -> usize {
// Add L2-to-L1 message segment length; for each message, the OS outputs the following:
// to_address, from_address, payload_size, payload.
let mut message_segment_length = l2_to_l1_payloads_length
.iter()
.map(|payload_length| constants::L2_TO_L1_MSG_HEADER_SIZE + payload_length)
.sum();
if let Some(payload_size) = l1_handler_payload_size {
// The corresponding transaction is of type L1 handler; add the length of the L1-to-L2
// message sent by the sequencer (that will be outputted by the OS), which is of the
// following format: from_address=calldata[0], to_address=contract_address,
// nonce, selector, payload_size, payload=calldata[1:].
message_segment_length += constants::L1_TO_L2_MSG_HEADER_SIZE + payload_size;
}
message_segment_length
}
/// Returns the cost of ConsumedMessageToL2 event emissions caused by an L1 handler with the given
/// payload size.
pub fn get_consumed_message_to_l2_emissions_cost(l1_handler_payload_size: Option<usize>) -> usize {
match l1_handler_payload_size {
None => 0, // The corresponding transaction is not an L1 handler.,
Some(l1_handler_payload_size) => {
get_event_emission_cost(
constants::CONSUMED_MSG_TO_L2_N_TOPICS,
// We're assuming the existence of one (not indexed) payload array.
constants::CONSUMED_MSG_TO_L2_ENCODED_DATA_SIZE + l1_handler_payload_size,
)
}
}
}
/// Returns the cost of LogMessageToL1 event emissions caused by the given messages payload length.
pub fn get_log_message_to_l1_emissions_cost(l2_to_l1_payloads_length: &[usize]) -> usize {
l2_to_l1_payloads_length
.iter()
.map(|length| {
get_event_emission_cost(
constants::LOG_MSG_TO_L1_N_TOPICS,
// We're assuming the existence of one (not indexed) payload array.
constants::LOG_MSG_TO_L1_ENCODED_DATA_SIZE + *length,
)
})
.sum()
}
fn get_event_emission_cost(n_topics: usize, data_length: usize) -> usize {
eth_gas_constants::GAS_PER_LOG
+ (n_topics + constants::N_DEFAULT_TOPICS) * eth_gas_constants::GAS_PER_LOG_TOPIC
+ data_length * eth_gas_constants::GAS_PER_LOG_DATA_WORD
}
/// Return an estimated lower bound for the L1 gas on an account transaction.
pub fn estimate_minimal_l1_gas(
block_context: &BlockContext,
tx: &AccountTransaction,
) -> TransactionPreValidationResult<u128> {
// TODO(Dori, 1/8/2023): Give names to the constant VM step estimates and regression-test them.
let os_steps_for_type = OS_RESOURCES.resources_for_tx_type(&tx.tx_type()).n_steps;
let gas_cost: usize = match tx {
// We consider the following state changes: sender balance update (storage update) + nonce
// increment (contract modification) (we exclude the sequencer balance update and the ERC20
// contract modification since it occurs for every tx).
AccountTransaction::Declare(_) => get_onchain_data_cost(StateChangesCount {
n_storage_updates: 1,
n_class_hash_updates: 0,
n_compiled_class_hash_updates: 0,
n_modified_contracts: 1,
}),
AccountTransaction::Invoke(_) => get_onchain_data_cost(StateChangesCount {
n_storage_updates: 1,
n_class_hash_updates: 0,
n_compiled_class_hash_updates: 0,
n_modified_contracts: 1,
}),
// DeployAccount also updates the address -> class hash mapping.
AccountTransaction::DeployAccount(_) => get_onchain_data_cost(StateChangesCount {
n_storage_updates: 1,
n_class_hash_updates: 1,
n_compiled_class_hash_updates: 0,
n_modified_contracts: 1,
}),
};
let resources = ResourcesMapping(HashMap::from([
(constants::GAS_USAGE.to_string(), gas_cost),
(constants::N_STEPS_RESOURCE.to_string(), os_steps_for_type),
]));
Ok(calculate_tx_l1_gas_usage(&resources, block_context)?)
}
pub fn estimate_minimal_fee(
block_context: &BlockContext,
tx: &AccountTransaction,
) -> TransactionExecutionResult<Fee> {
let estimated_minimal_l1_gas = estimate_minimal_l1_gas(block_context, tx)?;
Ok(get_fee_by_l1_gas_usage(&block_context.block_info, estimated_minimal_l1_gas, &tx.fee_type()))
}