pchain_runtime/
gas.rs

1/*
2    Copyright © 2023, ParallelChain Lab
3    Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
4*/
5
6//! Defines formulas in calculation of gas which is a measurement unit for transaction
7//! execution. The constants and functions in module follow the content in gas section of the
8//! [Parallelchain Mainnet Protocol](https://github.com/parallelchain-io/parallelchain-protocol).
9//!
10//! The mapping of equations or variables in the protocol to this module is as following:
11//!
12//! |Name       | Related Function / Constants      |
13//! |:---       |:---                       |
14//! |G_wread    | [wasm_memory_read_cost]   |
15//! |G_wwrite   | [wasm_memory_write_cost]  |
16//! |G_txdata   | [BLOCKCHAIN_WRITE_PER_BYTE_COST]  |
17//! |G_mincmdrcpsize| [minimum_receipt_size]        |
18//! |G_acckeylen    | [ACCOUNT_STATE_KEY_LENGTH]    |
19//! |G_sget         | [get_cost], [get_code_cost]   |
20//! |G_sset         | [set_cost_read_key], [set_cost_delete_old_value], [set_cost_write_new_value], [set_cost_rehash] |
21//! |G_scontains    | [contains_cost]       |
22//! |G_swrite   | [MPT_WRITE_PER_BYTE_COST] |
23//! |G_sread    | [MPT_READ_PER_BYTE_COST]  |
24//! |G_straverse| [MPT_TRAVERSE_PER_BYTE_COST]      |
25//! |G_srehash  | [MPT_REHASH_PER_BYTE_COST]        |
26//! |G_srefund  | [MPT_WRITE_REFUND_PROPORTION]     |
27//! |G_sgetcontractdisc  | [MPT_GET_CODE_DISCOUNT_PROPORTION] |
28//! |G_wsha256  | [CRYPTO_SHA256_PER_BYTE]          |
29//! |G_wkeccak256   | [CRYPTO_KECCAK256_PER_BYTE]   |
30//! |G_wripemd160   | [CRYPTO_RIPEMD160_PER_BYTE]   |
31//! |G_wvrfy25519   | [crypto_verify_ed25519_signature_cost]  |
32//! |G_txincl       | [tx_inclusion_cost] |
33//!
34
35/* ↓↓↓ Gas Costs for WASM opcode execution ↓↓↓ */
36
37use wasmer::wasmparser::Operator;
38
39/// wasm_opcode_gas_schedule maps between a WASM Operator to the cost of executing it. It
40/// specifies the gas cost of executing every legal opcode for the smart contract method calls.
41pub fn wasm_opcode_gas_schedule(operator: &Operator) -> u64 {
42    match operator {
43        // Constants
44        Operator::I32Const { .. } | Operator::I64Const { .. } => 0,
45
46        // Type parameteric operators
47        Operator::Drop => 2,
48        Operator::Select => 3,
49
50        // Flow control
51        Operator::Nop
52        | Operator::Unreachable
53        | Operator::Loop { .. }
54        | Operator::Else
55        | Operator::If { .. } => 0,
56        Operator::Br { .. }
57        | Operator::BrTable { .. }
58        | Operator::Return
59        | Operator::Call { .. }
60        | Operator::CallIndirect { .. } => 2,
61        Operator::BrIf { .. } => 3,
62
63        // Registers
64        Operator::GlobalGet { .. }
65        | Operator::GlobalSet { .. }
66        | Operator::LocalGet { .. }
67        | Operator::LocalSet { .. } => 3,
68
69        // Reference Types
70        Operator::RefIsNull
71        | Operator::RefFunc { .. }
72        | Operator::RefNull { .. }
73        | Operator::ReturnCall { .. }
74        | Operator::ReturnCallIndirect { .. } => 2,
75
76        // Exception Handling
77        Operator::CatchAll
78        | Operator::Throw { .. }
79        | Operator::Rethrow { .. }
80        | Operator::Delegate { .. } => 2,
81
82        // Bluk Memory Operations
83        Operator::ElemDrop { .. } | Operator::DataDrop { .. } => 1,
84        Operator::TableInit { .. } => 2,
85        Operator::MemoryCopy { .. }
86        | Operator::MemoryFill { .. }
87        | Operator::TableCopy { .. }
88        | Operator::TableFill { .. } => 3,
89
90        // Memory Operations
91        Operator::I32Load { .. }
92        | Operator::I64Load { .. }
93        | Operator::I32Store { .. }
94        | Operator::I64Store { .. }
95        | Operator::I32Store8 { .. }
96        | Operator::I32Store16 { .. }
97        | Operator::I32Load8S { .. }
98        | Operator::I32Load8U { .. }
99        | Operator::I32Load16S { .. }
100        | Operator::I32Load16U { .. }
101        | Operator::I64Load8S { .. }
102        | Operator::I64Load8U { .. }
103        | Operator::I64Load16S { .. }
104        | Operator::I64Load16U { .. }
105        | Operator::I64Load32S { .. }
106        | Operator::I64Load32U { .. }
107        | Operator::I64Store8 { .. }
108        | Operator::I64Store16 { .. }
109        | Operator::I64Store32 { .. } => 3,
110
111        // 32 and 64-bit Integer Arithmetic Operations
112        Operator::I32Add
113        | Operator::I32Sub
114        | Operator::I64Add
115        | Operator::I64Sub
116        | Operator::I64LtS
117        | Operator::I64LtU
118        | Operator::I64GtS
119        | Operator::I64GtU
120        | Operator::I64LeS
121        | Operator::I64LeU
122        | Operator::I64GeS
123        | Operator::I64GeU
124        | Operator::I32Eqz
125        | Operator::I32Eq
126        | Operator::I32Ne
127        | Operator::I32LtS
128        | Operator::I32LtU
129        | Operator::I32GtS
130        | Operator::I32GtU
131        | Operator::I32LeS
132        | Operator::I32LeU
133        | Operator::I32GeS
134        | Operator::I32GeU
135        | Operator::I64Eqz
136        | Operator::I64Eq
137        | Operator::I64Ne
138        | Operator::I32And
139        | Operator::I32Or
140        | Operator::I32Xor
141        | Operator::I64And
142        | Operator::I64Or
143        | Operator::I64Xor => 1,
144        Operator::I32Shl
145        | Operator::I32ShrU
146        | Operator::I32ShrS
147        | Operator::I32Rotl
148        | Operator::I32Rotr
149        | Operator::I64Shl
150        | Operator::I64ShrU
151        | Operator::I64ShrS
152        | Operator::I64Rotl
153        | Operator::I64Rotr => 2,
154        Operator::I32Mul | Operator::I64Mul => 3,
155        Operator::I32DivS
156        | Operator::I32DivU
157        | Operator::I32RemS
158        | Operator::I32RemU
159        | Operator::I64DivS
160        | Operator::I64DivU
161        | Operator::I64RemS
162        | Operator::I64RemU => 80,
163        Operator::I32Clz | Operator::I64Clz => 105,
164
165        // Type Casting & Truncation Operations
166        Operator::I32WrapI64
167        | Operator::I64ExtendI32S
168        | Operator::I64ExtendI32U
169        | Operator::I32Extend8S
170        | Operator::I32Extend16S
171        | Operator::I64Extend8S
172        | Operator::I64Extend16S
173        | Operator::I64Extend32S => 3,
174
175        // Everything Else is 1
176        _ => 1,
177    }
178}
179
180/* ↓↓↓ Gas Costs for Accessing WASM memory from host functions ↓↓↓ */
181
182/// WASM_MEMORY_WRITE_PER64_BITS_COST is the cost of writing into the WASM linear memory *per 64 bits*.
183pub const WASM_MEMORY_WRITE_PER64_BITS_COST: u64 = 3;
184/// WASM_MEMORY_READ_PER64_BITS_COST (C_I64Load) is the cost of writing into the WASM linear memory *per 64 bits*.
185pub const WASM_MEMORY_READ_PER64_BITS_COST: u64 = 3;
186/// WASM_BYTE_CODE_PER_BYTE_COST (C_I64Store) is the cost of checking whether input byte code satisfy CBI.
187pub const WASM_BYTE_CODE_PER_BYTE_COST: u64 = 3;
188
189/// Cost of reading `len` bytes from the guest's memory.
190pub const fn wasm_memory_read_cost(len: usize) -> u64 {
191    let cost = ceil_div_8(len as u64).saturating_mul(WASM_MEMORY_READ_PER64_BITS_COST);
192    if cost == 0 {
193        return 1;
194    } // = max(cost, 1) to make sure charging for a non-zero cost
195    cost
196}
197
198///Cost of writing `len` bytes into the guest's memory.
199pub const fn wasm_memory_write_cost(len: usize) -> u64 {
200    let cost = ceil_div_8(len as u64).saturating_mul(WASM_MEMORY_WRITE_PER64_BITS_COST);
201    if cost == 0 {
202        return 1;
203    } // = max(cost, 1) to make sure charging for a non-zero cost
204    cost
205}
206
207/// Ceiling of the value after dividing by 8.
208pub const fn ceil_div_8(l: u64) -> u64 {
209    l.saturating_add(7).saturating_div(8)
210}
211
212/* ↓↓↓ Gas Costs for Transaction-related data storage ↓↓↓ */
213
214/// Cost of including 1 byte of data in a Block as part of a transaction or a receipt.
215pub const BLOCKCHAIN_WRITE_PER_BYTE_COST: u64 = 30;
216/// Serialized size of a receipt containing empty command receipts.
217pub const MIN_RECP_SIZE: u64 = 4;
218/// Serialized size of a minimum-size command receipt.
219pub const MIN_CMDRECP_SIZE: u64 = 17;
220
221/// tx_inclusion_cost is the minimum cost for a transaction to be included in the blockchain.
222///
223/// It consists of:
224/// 1. cost for storing transaction in a block
225/// 2. cost for storing minimum-sized receipt(s) in a block
226/// 3. cost for 5 read-write operations for
227///     - signer's nonce
228///     - signer's balance during two phases
229///     - proposer's balance
230///     - treasury's balance
231pub fn tx_inclusion_cost(tx_size: usize, commands_len: usize) -> u64 {
232    // (1) Transaction storage size
233    let tx_size = tx_size as u64;
234    // (2) Minimum size of receipt
235    let min_receipt_size = minimum_receipt_size(commands_len);
236    // (3) Cost for 5 read-write operations
237    let rw_key_cost = (
238        // Read cost
239        get_cost(ACCOUNT_STATE_KEY_LENGTH, 8)
240            // Write cost
241            .saturating_add(set_cost_write_new_value(8))
242            .saturating_add(set_cost_rehash(ACCOUNT_STATE_KEY_LENGTH))
243    )
244    .saturating_mul(5);
245
246    // Multiply by blockchain storage write cost and add the cost of 5 read-write operations.
247    tx_size
248        .saturating_add(min_receipt_size)
249        .saturating_mul(BLOCKCHAIN_WRITE_PER_BYTE_COST)
250        .saturating_add(rw_key_cost)
251}
252
253/// Serialized size of a receipt containing `commands_len` minimum-sized command receipts.
254pub const fn minimum_receipt_size(commands_len: usize) -> u64 {
255    MIN_RECP_SIZE.saturating_add(MIN_CMDRECP_SIZE.saturating_mul(commands_len as u64))
256}
257
258/// blockchain_return_values_cost calculates the cost of writing return data into the receipt.
259pub const fn blockchain_return_values_cost(data_len: usize) -> u64 {
260    // data_len * C_txdata
261    (data_len as u64).saturating_mul(BLOCKCHAIN_WRITE_PER_BYTE_COST)
262}
263
264/// blockchain_log_cost calculates the cost of writing log into the receipt.
265pub const fn blockchain_log_cost(topic_len: usize, val_len: usize) -> u64 {
266    let topic_len = topic_len as u64;
267    let val_len = val_len as u64;
268    let log_len = topic_len.saturating_add(val_len);
269
270    // Ceil(l/8) * C_wasmread
271    (ceil_div_8(log_len).saturating_mul(WASM_MEMORY_READ_PER64_BITS_COST))
272        // t * C_sha256
273        .saturating_add(topic_len.saturating_mul(CRYPTO_SHA256_PER_BYTE))
274        // l X Z
275        .saturating_add(log_len.saturating_mul(BLOCKCHAIN_WRITE_PER_BYTE_COST))
276}
277
278/* ↓↓↓ World state storage and access ↓↓↓ */
279
280/// The length of keys in the root world state MPT.
281pub const ACCOUNT_STATE_KEY_LENGTH: usize = 33;
282/// Cost of writing a single byte into the world state.
283pub const MPT_WRITE_PER_BYTE_COST: u64 = 2500;
284/// Cost of reading a single byte from the world state.
285pub const MPT_READ_PER_BYTE_COST: u64 = 50;
286/// Cost of traversing 1 byte (2 nibbles) down an MPT.
287pub const MPT_TRAVERSE_PER_BYTE_COST: u64 = 20;
288/// Cost of traversing 1 byte up (2 nibbles) and recomputing the SHA256 hashes of 2 nodes
289/// in an MPT after it or one of its descendants is changed.
290pub const MPT_REHASH_PER_BYTE_COST: u64 = 130;
291/// Proportion of the cost of writing a tuple into an MPT that is refunded when that tuple is re-set or deleted.
292pub const MPT_WRITE_REFUND_PROPORTION: u64 = 50;
293/// Proportion of which is discounted if the tuple contains a contract.
294pub const MPT_GET_CODE_DISCOUNT_PROPORTION: u64 = 50;
295
296/// get_cost calculates the cost of reading data from the World State.
297pub const fn get_cost(key_len: usize, value_len: usize) -> u64 {
298    let get_cost_1 = (value_len as u64).saturating_mul(MPT_READ_PER_BYTE_COST);
299    let get_cost_2 = (key_len as u64).saturating_mul(MPT_TRAVERSE_PER_BYTE_COST);
300    get_cost_1.saturating_add(get_cost_2)
301}
302
303/// get_code_cost calculates the cost of reading contract code from the World State.
304pub const fn get_code_cost(code_len: usize) -> u64 {
305    // Get Cost
306    get_cost(ACCOUNT_STATE_KEY_LENGTH, code_len)
307        // Code Discount
308        .saturating_mul(MPT_GET_CODE_DISCOUNT_PROPORTION)
309        .saturating_div(100)
310}
311
312/// Set Cost (1): Cost for getting the key.
313pub const fn set_cost_read_key(key_len: usize, value_len: usize) -> u64 {
314    get_cost(key_len, value_len)
315}
316
317/// Set Cost (2): Cost for deleting the old value for a refund.
318#[allow(clippy::double_comparisons)]
319pub const fn set_cost_delete_old_value(
320    key_len: usize,
321    old_val_len: usize,
322    new_val_len: usize,
323) -> u64 {
324    let old_val_len = old_val_len as u64; // (a)
325    let new_val_len = new_val_len as u64; // (b)
326
327    if (old_val_len > 0 || old_val_len == 0) && new_val_len > 0 {
328        // a * C_write * C_refund
329        old_val_len
330            .saturating_mul(MPT_WRITE_PER_BYTE_COST * MPT_WRITE_REFUND_PROPORTION)
331            .saturating_div(100)
332    } else if old_val_len > 0 && new_val_len == 0 {
333        // (k + a) * C_write * C_refund
334        ((key_len as u64).saturating_add(old_val_len))
335            .saturating_mul(MPT_WRITE_PER_BYTE_COST * MPT_WRITE_REFUND_PROPORTION)
336            .saturating_div(100)
337    } else {
338        // old_val_len == 0 && new_val_len == 0
339        0
340    }
341}
342
343/// Set Cost (3): Cost for writing a new value.
344pub const fn set_cost_write_new_value(new_val_len: usize) -> u64 {
345    // b * C_write
346    (new_val_len as u64).saturating_mul(MPT_WRITE_PER_BYTE_COST)
347}
348
349/// Set Cost (4): Cost for recomputing node hashes until the root.
350pub const fn set_cost_rehash(key_len: usize) -> u64 {
351    // k * C_rehash
352    (key_len as u64).saturating_mul(MPT_REHASH_PER_BYTE_COST)
353}
354
355/// contains_cost calculates the cost of checking key existence in the World State.
356pub const fn contains_cost(key_len: usize) -> u64 {
357    (key_len as u64).saturating_mul(MPT_TRAVERSE_PER_BYTE_COST)
358}
359
360/* ↓↓↓ Gas Costs for crypto functions ↓↓↓ */
361
362/// Multiplier of computing the SHA256 hash over the length of a message.
363pub const CRYPTO_SHA256_PER_BYTE: u64 = 16;
364/// Multiplier of computing the Keccak256 hash over the length of a message.
365pub const CRYPTO_KECCAK256_PER_BYTE: u64 = 16;
366/// Multiplier of computing the RIPEMD160  hash over the length of a message.
367pub const CRYPTO_RIPEMD160_PER_BYTE: u64 = 16;
368/// Cost of verifying whether an Ed25519 signature over a message of length.
369pub const fn crypto_verify_ed25519_signature_cost(msg_len: usize) -> u64 {
370    // Base Cost (1400000) + 16 * Message Length
371    1_400_000_u64.saturating_add((msg_len as u64).saturating_mul(16_u64))
372}