unc_vm_runner/logic/logic.rs
1use super::context::VMContext;
2use super::dependencies::{External, MemSlice, MemoryLike};
3use super::errors::{FunctionCallError, InconsistentStateError};
4use super::gas_counter::{FastGasCounter, GasCounter};
5use super::types::{PromiseIndex, PromiseResult, ReceiptIndex, ReturnData};
6use super::utils::split_method_names;
7use super::ValuePtr;
8use super::{HostError, VMLogicError};
9use crate::ProfileDataV3;
10use std::mem::size_of;
11use unc_crypto::Secp256K1Signature;
12use unc_parameters::vm::{Config, StorageGetMode};
13use unc_parameters::{
14 transfer_exec_fee, transfer_send_fee, ActionCosts, ExtCosts, RuntimeFeesConfig,
15};
16use unc_primitives_core::config::ViewConfig;
17use unc_primitives_core::types::{
18 AccountId, Balance, Compute, EpochHeight, Gas, GasWeight, StorageUsage,
19};
20use ExtCosts::*;
21
22pub type Result<T, E = VMLogicError> = ::std::result::Result<T, E>;
23
24#[cfg(feature = "io_trace")]
25fn base64(s: &[u8]) -> String {
26 use base64::Engine;
27 base64::engine::general_purpose::STANDARD.encode(s)
28}
29
30pub struct VMLogic<'a> {
31 /// Provides access to the components outside the Wasm runtime for operations on the trie and
32 /// receipts creation.
33 ext: &'a mut dyn External,
34 /// Part of Context API and Economics API that was extracted from the receipt.
35 context: VMContext,
36 /// All gas and economic parameters required during contract execution.
37 pub(crate) config: &'a Config,
38 /// Fees for creating (async) actions on runtime.
39 fees_config: &'a RuntimeFeesConfig,
40 /// If this method execution is invoked directly as a callback by one or more contract calls the
41 /// results of the methods that made the callback are stored in this collection.
42 promise_results: &'a [PromiseResult],
43 /// Pointer to the guest memory.
44 memory: super::vmstate::Memory<'a>,
45
46 /// Keeping track of the current account balance, which can decrease when we create promises
47 /// and attach balance to them.
48 current_account_balance: Balance,
49 /// Current amount of staking tokens, does not automatically change when staking transaction is
50 /// issued.
51 current_account_locked_balance: Balance,
52 /// Storage usage of the current account at the moment
53 current_storage_usage: StorageUsage,
54 gas_counter: GasCounter,
55 /// What method returns.
56 return_data: ReturnData,
57 /// Logs written by the runtime.
58 logs: Vec<String>,
59 /// Registers can be used by the guest to store blobs of data without moving them across
60 /// host-guest boundary.
61 registers: super::vmstate::Registers,
62
63 /// The DAG of promises, indexed by promise id.
64 promises: Vec<Promise>,
65 /// Tracks the total log length. The sum of length of all logs.
66 total_log_length: u64,
67
68 /// Stores the amount of stack space remaining
69 remaining_stack: u64,
70}
71
72/// Promises API allows to create a DAG-structure that defines dependencies between smart contract
73/// calls. A single promise can be created with zero or several dependencies on other promises.
74/// * If a promise was created from a receipt (using `promise_create` or `promise_then`) it's a
75/// `Receipt`;
76/// * If a promise was created by merging several promises (using `promise_and`) then
77/// it's a `NotReceipt`, but has receipts of all promises it depends on.
78#[derive(Debug)]
79enum Promise {
80 Receipt(ReceiptIndex),
81 NotReceipt(Vec<ReceiptIndex>),
82}
83
84/// Helper for calling `super::vmstate::get_memory_or_register`.
85///
86/// super::vmstate::get_memory_or_register has a whole lot of wordy arguments
87/// which are always the same when invoked inside of one of VMLogic method.
88/// This macro helps with that invocation.
89macro_rules! get_memory_or_register {
90 ($logic:expr, $offset:expr, $len:expr) => {
91 super::vmstate::get_memory_or_register(
92 &mut $logic.gas_counter,
93 &$logic.memory,
94 &$logic.registers,
95 $offset,
96 $len,
97 )
98 };
99}
100
101/// A wrapper for reading public key.
102///
103/// This exists for historical reasons because we must maintain when errors are
104/// returned. In the old days, between reading the public key and decoding it
105/// we could return unrelated error. Because of that we cannot change the code
106/// to return deserialisation errors immediately after reading the public key.
107///
108/// This struct abstracts away the fact that we’re deserialising the key
109/// immediately. Decoding errors are detected as soon as this object is created
110/// but they are communicated to the user only once they call [`Self::decode`].
111///
112/// Why not just keep the old ways without this noise? By doing deserialisation
113/// immediately we’re copying the data onto the stack without having to allocate
114/// a temporary vector.
115struct PublicKeyBuffer(Result<unc_crypto::PublicKey, ()>);
116
117impl PublicKeyBuffer {
118 fn new(data: &[u8]) -> Self {
119 Self(borsh::BorshDeserialize::try_from_slice(data).map_err(|_| ()))
120 }
121
122 fn decode(self) -> Result<unc_crypto::PublicKey> {
123 self.0.map_err(|_| HostError::InvalidPublicKey.into())
124 }
125}
126
127impl<'a> VMLogic<'a> {
128 pub fn new(
129 ext: &'a mut dyn External,
130 context: VMContext,
131 config: &'a Config,
132 fees_config: &'a RuntimeFeesConfig,
133 promise_results: &'a [PromiseResult],
134 memory: &'a mut dyn MemoryLike,
135 ) -> Self {
136 // Overflow should be checked before calling VMLogic.
137 let current_account_balance = context.account_balance + context.attached_deposit;
138 let current_storage_usage = context.storage_usage;
139 let max_gas_burnt = match context.view_config {
140 Some(ViewConfig { max_gas_burnt: max_gas_burnt_view }) => max_gas_burnt_view,
141 None => config.limit_config.max_gas_burnt,
142 };
143
144 let current_account_locked_balance = context.account_locked_balance;
145 let gas_counter = GasCounter::new(
146 config.ext_costs.clone(),
147 max_gas_burnt,
148 config.regular_op_cost,
149 context.prepaid_gas,
150 context.is_view(),
151 );
152 Self {
153 ext,
154 context,
155 config,
156 fees_config,
157 promise_results,
158 memory: super::vmstate::Memory::new(memory),
159 current_account_balance,
160 current_account_locked_balance,
161 current_storage_usage,
162 gas_counter,
163 return_data: ReturnData::None,
164 logs: vec![],
165 registers: Default::default(),
166 promises: vec![],
167 total_log_length: 0,
168 remaining_stack: u64::from(config.limit_config.max_stack_height),
169 }
170 }
171
172 /// Returns reference to logs that have been created so far.
173 pub fn logs(&self) -> &[String] {
174 &self.logs
175 }
176
177 #[cfg(test)]
178 pub(super) fn gas_counter(&self) -> &GasCounter {
179 &self.gas_counter
180 }
181
182 #[cfg(test)]
183 pub(super) fn config(&self) -> &Config {
184 &self.config
185 }
186
187 #[cfg(test)]
188 pub(super) fn memory(&mut self) -> &mut super::vmstate::Memory<'a> {
189 &mut self.memory
190 }
191
192 #[cfg(test)]
193 pub(super) fn registers(&mut self) -> &mut super::vmstate::Registers {
194 &mut self.registers
195 }
196
197 // #########################
198 // # Finite-wasm internals #
199 // #########################
200 pub fn finite_wasm_gas(&mut self, gas: u64) -> Result<()> {
201 self.gas(gas)
202 }
203
204 pub fn finite_wasm_stack(&mut self, operand_size: u64, frame_size: u64) -> Result<()> {
205 self.remaining_stack =
206 match self.remaining_stack.checked_sub(operand_size.saturating_add(frame_size)) {
207 Some(s) => s,
208 None => return Err(VMLogicError::HostError(HostError::MemoryAccessViolation)),
209 };
210 self.gas(((frame_size + 7) / 8) * u64::from(self.config.regular_op_cost))?;
211 Ok(())
212 }
213
214 pub fn finite_wasm_unstack(&mut self, operand_size: u64, frame_size: u64) -> Result<()> {
215 self.remaining_stack = self
216 .remaining_stack
217 .checked_add(operand_size.saturating_add(frame_size))
218 .expect("remaining stack integer overflow");
219 Ok(())
220 }
221
222 // #################
223 // # Registers API #
224 // #################
225
226 /// Convenience function for testing.
227 #[cfg(test)]
228 pub fn wrapped_internal_write_register(&mut self, register_id: u64, data: &[u8]) -> Result<()> {
229 self.registers.set(&mut self.gas_counter, &self.config.limit_config, register_id, data)
230 }
231
232 /// Writes the entire content from the register `register_id` into the memory of the guest starting with `ptr`.
233 ///
234 /// # Arguments
235 ///
236 /// * `register_id` -- a register id from where to read the data;
237 /// * `ptr` -- location on guest memory where to copy the data.
238 ///
239 /// # Errors
240 ///
241 /// * If the content extends outside the memory allocated to the guest. In Wasmer, it returns `MemoryAccessViolation` error message;
242 /// * If `register_id` is pointing to unused register returns `InvalidRegisterId` error message.
243 ///
244 /// # Undefined Behavior
245 ///
246 /// If the content of register extends outside the preallocated memory on the host side, or the pointer points to a
247 /// wrong location this function will overwrite memory that it is not supposed to overwrite causing an undefined behavior.
248 ///
249 /// # Cost
250 ///
251 /// `base + read_register_base + read_register_byte * num_bytes + write_memory_base + write_memory_byte * num_bytes`
252 pub fn read_register(&mut self, register_id: u64, ptr: u64) -> Result<()> {
253 self.gas_counter.pay_base(base)?;
254 let data = self.registers.get(&mut self.gas_counter, register_id)?;
255 self.memory.set(&mut self.gas_counter, ptr, data)
256 }
257
258 /// Returns the size of the blob stored in the given register.
259 /// * If register is used, then returns the size, which can potentially be zero;
260 /// * If register is not used, returns `u64::MAX`
261 ///
262 /// # Arguments
263 ///
264 /// * `register_id` -- a register id from where to read the data;
265 ///
266 /// # Cost
267 ///
268 /// `base`
269 pub fn register_len(&mut self, register_id: u64) -> Result<u64> {
270 self.gas_counter.pay_base(base)?;
271 Ok(self.registers.get_len(register_id).unwrap_or(u64::MAX))
272 }
273
274 /// Copies `data` from the guest memory into the register. If register is unused will initialize
275 /// it. If register has larger capacity than needed for `data` will not re-allocate it. The
276 /// register will lose the pre-existing data if any.
277 ///
278 /// # Arguments
279 ///
280 /// * `register_id` -- a register id where to write the data;
281 /// * `data_len` -- length of the data in bytes;
282 /// * `data_ptr` -- pointer in the guest memory where to read the data from.
283 ///
284 /// # Cost
285 ///
286 /// `base + read_memory_base + read_memory_bytes * num_bytes + write_register_base + write_register_bytes * num_bytes`
287 pub fn write_register(&mut self, register_id: u64, data_len: u64, data_ptr: u64) -> Result<()> {
288 self.gas_counter.pay_base(base)?;
289 let data =
290 self.memory.view(&mut self.gas_counter, MemSlice { ptr: data_ptr, len: data_len })?;
291 self.registers.set(&mut self.gas_counter, &self.config.limit_config, register_id, data)
292 }
293
294 // ###################################
295 // # String reading helper functions #
296 // ###################################
297
298 /// Helper function to read and return utf8-encoding string.
299 /// If `len == u64::MAX` then treats the string as null-terminated with character `'\0'`.
300 ///
301 /// # Errors
302 ///
303 /// * If string extends outside the memory of the guest with `MemoryAccessViolation`;
304 /// * If string is not UTF-8 returns `BadUtf8`.
305 /// * If number of bytes read + `total_log_length` exceeds the `max_total_log_length` returns
306 /// `TotalLogLengthExceeded`.
307 ///
308 /// # Cost
309 ///
310 /// For not nul-terminated string:
311 /// `read_memory_base + read_memory_byte * num_bytes + utf8_decoding_base + utf8_decoding_byte * num_bytes`
312 ///
313 /// For nul-terminated string:
314 /// `(read_memory_base + read_memory_byte) * num_bytes + utf8_decoding_base + utf8_decoding_byte * num_bytes`
315 fn get_utf8_string(&mut self, len: u64, ptr: u64) -> Result<String> {
316 self.gas_counter.pay_base(utf8_decoding_base)?;
317 let mut buf;
318 let max_len =
319 self.config.limit_config.max_total_log_length.saturating_sub(self.total_log_length);
320 if len != u64::MAX {
321 if len > max_len {
322 return self.total_log_length_exceeded(len);
323 }
324 buf = self.memory.view(&mut self.gas_counter, MemSlice { ptr, len })?.into_owned();
325 } else {
326 buf = vec![];
327 for i in 0..=max_len {
328 // self.memory_get_u8 will check for u64 overflow on the first iteration (i == 0)
329 let el = self.memory.get_u8(&mut self.gas_counter, ptr + i)?;
330 if el == 0 {
331 break;
332 }
333 if i == max_len {
334 return self.total_log_length_exceeded(max_len.saturating_add(1));
335 }
336 buf.push(el);
337 }
338 }
339 self.gas_counter.pay_per(utf8_decoding_byte, buf.len() as _)?;
340 String::from_utf8(buf).map_err(|_| HostError::BadUTF8.into())
341 }
342
343 /// Helper function to get utf8 string, for sandbox debug log. The difference with `get_utf8_string`:
344 /// * It's only available on sandbox node
345 /// * The cost is 0
346 /// * It's up to the caller to set correct len
347 #[cfg(feature = "sandbox")]
348 fn sandbox_get_utf8_string(&mut self, len: u64, ptr: u64) -> Result<String> {
349 let buf = self.memory.view_for_free(MemSlice { ptr, len })?.into_owned();
350 String::from_utf8(buf).map_err(|_| HostError::BadUTF8.into())
351 }
352
353 /// Helper function to read UTF-16 formatted string from guest memory.
354 /// # Errors
355 ///
356 /// * If string extends outside the memory of the guest with `MemoryAccessViolation`;
357 /// * If string is not UTF-16 returns `BadUtf16`.
358 /// * If number of bytes read + `total_log_length` exceeds the `max_total_log_length` returns
359 /// `TotalLogLengthExceeded`.
360 ///
361 /// # Cost
362 ///
363 /// For not nul-terminated string:
364 /// `read_memory_base + read_memory_byte * num_bytes + utf16_decoding_base + utf16_decoding_byte * num_bytes`
365 ///
366 /// For nul-terminated string:
367 /// `read_memory_base * num_bytes / 2 + read_memory_byte * num_bytes + utf16_decoding_base + utf16_decoding_byte * num_bytes`
368 fn get_utf16_string(&mut self, mut len: u64, ptr: u64) -> Result<String> {
369 self.gas_counter.pay_base(utf16_decoding_base)?;
370 let max_len =
371 self.config.limit_config.max_total_log_length.saturating_sub(self.total_log_length);
372
373 let mem_view = if len == u64::MAX {
374 len = self.get_nul_terminated_utf16_len(ptr, max_len)?;
375 self.memory.view_for_free(MemSlice { ptr, len })
376 } else {
377 self.memory.view(&mut self.gas_counter, MemSlice { ptr, len })
378 }?;
379
380 let input = stdx::as_chunks_exact(&mem_view).map_err(|_| HostError::BadUTF16)?;
381 if len > max_len {
382 return self.total_log_length_exceeded(len);
383 }
384
385 self.gas_counter.pay_per(utf16_decoding_byte, len)?;
386 char::decode_utf16(input.into_iter().copied().map(u16::from_le_bytes))
387 .collect::<Result<String, _>>()
388 .map_err(|_| HostError::BadUTF16.into())
389 }
390
391 /// Helper function to get length of NUL-terminated UTF-16 formatted string
392 /// in guest memory.
393 ///
394 /// In other words, counts how many bytes are there to first pair of NUL
395 /// bytes.
396 fn get_nul_terminated_utf16_len(&mut self, ptr: u64, max_len: u64) -> Result<u64> {
397 let mut len = 0;
398 loop {
399 if self.memory.get_u16(&mut self.gas_counter, ptr.saturating_add(len))? == 0 {
400 return Ok(len);
401 }
402 len = match len.checked_add(2) {
403 Some(len) if len <= max_len => len,
404 Some(len) => return self.total_log_length_exceeded(len),
405 None => return self.total_log_length_exceeded(u64::MAX),
406 };
407 }
408 }
409
410 // ####################################################
411 // # Helper functions to prevent code duplication API #
412 // ####################################################
413
414 /// Checks that the current log number didn't reach the limit yet, so we can add a new message.
415 fn check_can_add_a_log_message(&self) -> Result<()> {
416 if self.logs.len() as u64 >= self.config.limit_config.max_number_logs {
417 Err(HostError::NumberOfLogsExceeded { limit: self.config.limit_config.max_number_logs }
418 .into())
419 } else {
420 Ok(())
421 }
422 }
423
424 /// Adds a given promise to the vector of promises and returns a new promise index.
425 /// Throws `NumberPromisesExceeded` if the total number of promises exceeded the limit.
426 fn checked_push_promise(&mut self, promise: Promise) -> Result<PromiseIndex> {
427 let new_promise_idx = self.promises.len() as PromiseIndex;
428 self.promises.push(promise);
429 if self.promises.len() as u64
430 > self.config.limit_config.max_promises_per_function_call_action
431 {
432 Err(HostError::NumberPromisesExceeded {
433 number_of_promises: self.promises.len() as u64,
434 limit: self.config.limit_config.max_promises_per_function_call_action,
435 }
436 .into())
437 } else {
438 Ok(new_promise_idx)
439 }
440 }
441
442 fn checked_push_log(&mut self, message: String) -> Result<()> {
443 // The size of logged data can't be too large. No overflow.
444 self.total_log_length += message.len() as u64;
445 if self.total_log_length > self.config.limit_config.max_total_log_length {
446 return self.total_log_length_exceeded(0);
447 }
448 self.logs.push(message);
449 Ok(())
450 }
451
452 fn total_log_length_exceeded<T>(&self, add_len: u64) -> Result<T> {
453 Err(HostError::TotalLogLengthExceeded {
454 length: self.total_log_length.saturating_add(add_len),
455 limit: self.config.limit_config.max_total_log_length,
456 }
457 .into())
458 }
459
460 fn get_public_key(&mut self, ptr: u64, len: u64) -> Result<PublicKeyBuffer> {
461 Ok(PublicKeyBuffer::new(&get_memory_or_register!(self, ptr, len)?))
462 }
463
464 // ###############
465 // # Context API #
466 // ###############
467
468 /// Saves the account id of the current contract that we execute into the register.
469 ///
470 /// # Errors
471 ///
472 /// If the registers exceed the memory limit returns `MemoryAccessViolation`.
473 ///
474 /// # Cost
475 ///
476 /// `base + write_register_base + write_register_byte * num_bytes`
477 pub fn current_account_id(&mut self, register_id: u64) -> Result<()> {
478 self.gas_counter.pay_base(base)?;
479 self.registers.set(
480 &mut self.gas_counter,
481 &self.config.limit_config,
482 register_id,
483 self.context.current_account_id.as_bytes(),
484 )
485 }
486
487 /// All contract calls are a result of some transaction that was signed by some account using
488 /// some access key and submitted into a memory pool (either through the wallet using RPC or by
489 /// a node itself). This function returns the id of that account. Saves the bytes of the signer
490 /// account id into the register.
491 ///
492 /// # Errors
493 ///
494 /// * If the registers exceed the memory limit returns `MemoryAccessViolation`.
495 /// * If called as view function returns `ProhibitedInView`.
496 ///
497 /// # Cost
498 ///
499 /// `base + write_register_base + write_register_byte * num_bytes`
500 pub fn signer_account_id(&mut self, register_id: u64) -> Result<()> {
501 self.gas_counter.pay_base(base)?;
502
503 if self.context.is_view() {
504 return Err(HostError::ProhibitedInView {
505 method_name: "signer_account_id".to_string(),
506 }
507 .into());
508 }
509 self.registers.set(
510 &mut self.gas_counter,
511 &self.config.limit_config,
512 register_id,
513 self.context.signer_account_id.as_bytes(),
514 )
515 }
516
517 /// Saves the public key fo the access key that was used by the signer into the register. In
518 /// rare situations smart contract might want to know the exact access key that was used to send
519 /// the original transaction, e.g. to increase the allowance or manipulate with the public key.
520 ///
521 /// # Errors
522 ///
523 /// * If the registers exceed the memory limit returns `MemoryAccessViolation`.
524 /// * If called as view function returns `ProhibitedInView`.
525 ///
526 /// # Cost
527 ///
528 /// `base + write_register_base + write_register_byte * num_bytes`
529 pub fn signer_account_pk(&mut self, register_id: u64) -> Result<()> {
530 self.gas_counter.pay_base(base)?;
531
532 if self.context.is_view() {
533 return Err(HostError::ProhibitedInView {
534 method_name: "signer_account_pk".to_string(),
535 }
536 .into());
537 }
538 self.registers.set(
539 &mut self.gas_counter,
540 &self.config.limit_config,
541 register_id,
542 self.context.signer_account_pk.as_slice(),
543 )
544 }
545
546 /// All contract calls are a result of a receipt, this receipt might be created by a transaction
547 /// that does function invocation on the contract or another contract as a result of
548 /// cross-contract call. Saves the bytes of the predecessor account id into the register.
549 ///
550 /// # Errors
551 ///
552 /// * If the registers exceed the memory limit returns `MemoryAccessViolation`.
553 /// * If called as view function returns `ProhibitedInView`.
554 ///
555 /// # Cost
556 ///
557 /// `base + write_register_base + write_register_byte * num_bytes`
558 pub fn predecessor_account_id(&mut self, register_id: u64) -> Result<()> {
559 self.gas_counter.pay_base(base)?;
560
561 if self.context.is_view() {
562 return Err(HostError::ProhibitedInView {
563 method_name: "predecessor_account_id".to_string(),
564 }
565 .into());
566 }
567 self.registers.set(
568 &mut self.gas_counter,
569 &self.config.limit_config,
570 register_id,
571 self.context.predecessor_account_id.as_bytes(),
572 )
573 }
574
575 /// Reads input to the contract call into the register. Input is expected to be in JSON-format.
576 /// If input is provided saves the bytes (potentially zero) of input into register. If input is
577 /// not provided writes 0 bytes into the register.
578 ///
579 /// # Cost
580 ///
581 /// `base + write_register_base + write_register_byte * num_bytes`
582 pub fn input(&mut self, register_id: u64) -> Result<()> {
583 self.gas_counter.pay_base(base)?;
584
585 self.registers.set(
586 &mut self.gas_counter,
587 &self.config.limit_config,
588 register_id,
589 self.context.input.as_slice(),
590 )
591 }
592
593 /// Returns the current block height.
594 ///
595 /// It’s only due to historical reasons, this host function is called
596 /// `block_index` rather than `block_height`.
597 ///
598 /// # Cost
599 ///
600 /// `base`
601 pub fn block_index(&mut self) -> Result<u64> {
602 self.gas_counter.pay_base(base)?;
603 Ok(self.context.block_height)
604 }
605
606 /// Returns the current block timestamp (number of non-leap-nanoseconds since January 1, 1970 0:00:00 UTC).
607 ///
608 /// # Cost
609 ///
610 /// `base`
611 pub fn block_timestamp(&mut self) -> Result<u64> {
612 self.gas_counter.pay_base(base)?;
613 Ok(self.context.block_timestamp)
614 }
615
616 /// Returns the current epoch height.
617 ///
618 /// # Cost
619 ///
620 /// `base`
621 pub fn epoch_height(&mut self) -> Result<EpochHeight> {
622 self.gas_counter.pay_base(base)?;
623 Ok(self.context.epoch_height)
624 }
625
626 /// Get the stake of an account, if the account is currently a validator. Otherwise returns 0.
627 /// writes the value into the` u128` variable pointed by `stake_ptr`.
628 ///
629 /// # Cost
630 ///
631 /// `base + memory_write_base + memory_write_size * 16 + utf8_decoding_base + utf8_decoding_byte * account_id_len + validator_stake_base`.
632 pub fn validator_stake(
633 &mut self,
634 account_id_len: u64,
635 account_id_ptr: u64,
636 stake_ptr: u64,
637 ) -> Result<()> {
638 self.gas_counter.pay_base(base)?;
639 let account_id = self.read_and_parse_account_id(account_id_ptr, account_id_len)?;
640 self.gas_counter.pay_base(validator_pledge_base)?;
641 let stake = self.ext.validator_stake(&account_id)?.unwrap_or_default();
642 self.memory.set_u128(&mut self.gas_counter, stake_ptr, stake)
643 }
644
645 pub fn validator_power(
646 &mut self,
647 account_id_len: u64,
648 account_id_ptr: u64,
649 power_ptr: u64,
650 ) -> Result<()> {
651 self.gas_counter.pay_base(base)?;
652 let account_id = self.read_and_parse_account_id(account_id_ptr, account_id_len)?;
653 self.gas_counter.pay_base(validator_power_base)?;
654 let power = self.ext.validator_power(&account_id)?.unwrap_or_default();
655 self.memory.set_u128(&mut self.gas_counter, power_ptr, power)
656 }
657
658 /// Get the total validator stake of the current epoch.
659 /// Write the u128 value into `stake_ptr`.
660 /// writes the value into the` u128` variable pointed by `stake_ptr`.
661 ///
662 /// # Cost
663 ///
664 /// `base + memory_write_base + memory_write_size * 16 + validator_total_stake_base`
665 ///
666 pub fn validator_total_stake(&mut self, stake_ptr: u64) -> Result<()> {
667 self.gas_counter.pay_base(base)?;
668 self.gas_counter.pay_base(validator_total_pledge_base)?;
669 let total_stake = self.ext.validator_total_stake()?;
670 self.memory.set_u128(&mut self.gas_counter, stake_ptr, total_stake)
671 }
672 pub fn validator_total_power(&mut self, power_ptr: u64) -> Result<()> {
673 self.gas_counter.pay_base(base)?;
674 self.gas_counter.pay_base(validator_total_power_base)?;
675 let total_power = self.ext.validator_total_power()?;
676 self.memory.set_u128(&mut self.gas_counter, power_ptr, total_power)
677 }
678
679 /// Returns the number of bytes used by the contract if it was saved to the trie as of the
680 /// invocation. This includes:
681 /// * The data written with storage_* functions during current and previous execution;
682 /// * The bytes needed to store the access keys of the given account.
683 /// * The contract code size
684 /// * A small fixed overhead for account metadata.
685 ///
686 /// # Cost
687 ///
688 /// `base`
689 pub fn storage_usage(&mut self) -> Result<StorageUsage> {
690 self.gas_counter.pay_base(base)?;
691 Ok(self.current_storage_usage)
692 }
693
694 // #################
695 // # Economics API #
696 // #################
697
698 /// The current balance of the given account. This includes the attached_deposit that was
699 /// attached to the transaction.
700 ///
701 /// # Cost
702 ///
703 /// `base + memory_write_base + memory_write_size * 16`
704 pub fn account_balance(&mut self, balance_ptr: u64) -> Result<()> {
705 self.gas_counter.pay_base(base)?;
706 self.memory.set_u128(&mut self.gas_counter, balance_ptr, self.current_account_balance)
707 }
708
709 /// The current amount of tokens Staking due to staking.
710 ///
711 /// # Cost
712 ///
713 /// `base + memory_write_base + memory_write_size * 16`
714 pub fn account_locked_balance(&mut self, balance_ptr: u64) -> Result<()> {
715 self.gas_counter.pay_base(base)?;
716 self.memory.set_u128(
717 &mut self.gas_counter,
718 balance_ptr,
719 self.current_account_locked_balance,
720 )
721 }
722
723 /// The balance that was attached to the call that will be immediately deposited before the
724 /// contract execution starts.
725 ///
726 /// # Errors
727 ///
728 /// If called as view function returns `ProhibitedInView``.
729 ///
730 /// # Cost
731 ///
732 /// `base + memory_write_base + memory_write_size * 16`
733 pub fn attached_deposit(&mut self, balance_ptr: u64) -> Result<()> {
734 self.gas_counter.pay_base(base)?;
735
736 self.memory.set_u128(&mut self.gas_counter, balance_ptr, self.context.attached_deposit)
737 }
738
739 /// The amount of gas attached to the call that can be used to pay for the gas fees.
740 ///
741 /// # Errors
742 ///
743 /// If called as view function returns `ProhibitedInView`.
744 ///
745 /// # Cost
746 ///
747 /// `base`
748 pub fn prepaid_gas(&mut self) -> Result<Gas> {
749 self.gas_counter.pay_base(base)?;
750 if self.context.is_view() {
751 return Err(
752 HostError::ProhibitedInView { method_name: "prepaid_gas".to_string() }.into()
753 );
754 }
755 Ok(self.context.prepaid_gas)
756 }
757
758 /// The gas that was already burnt during the contract execution (cannot exceed `prepaid_gas`)
759 ///
760 /// # Errors
761 ///
762 /// If called as view function returns `ProhibitedInView`.
763 ///
764 /// # Cost
765 ///
766 /// `base`
767 pub fn used_gas(&mut self) -> Result<Gas> {
768 self.gas_counter.pay_base(base)?;
769 if self.context.is_view() {
770 return Err(HostError::ProhibitedInView { method_name: "used_gas".to_string() }.into());
771 }
772 Ok(self.gas_counter.used_gas())
773 }
774
775 // ############
776 // # Math API #
777 // ############
778
779 /// Computes multiexp on alt_bn128 curve using Pippenger's algorithm \sum_i
780 /// mul_i g_{1 i} should be equal result.
781 ///
782 /// # Arguments
783 ///
784 /// * `value` - sequence of (g1:G1, fr:Fr), where
785 /// G1 is point (x:Fq, y:Fq) on alt_bn128,
786 /// alt_bn128 is Y^2 = X^3 + 3 curve over Fq.
787 ///
788 /// `value` is encoded as packed, little-endian
789 /// `[((u256, u256), u256)]` slice.
790 ///
791 /// # Errors
792 ///
793 /// If `value_len + value_ptr` points outside the memory or the registers
794 /// use more memory than the limit, the function returns
795 /// `MemoryAccessViolation`.
796 ///
797 /// If point coordinates are not on curve, point is not in the subgroup,
798 /// scalar is not in the field or `value.len()%96!=0`, the function returns
799 /// `AltBn128InvalidInput`.
800 ///
801 /// # Cost
802 ///
803 /// `base + write_register_base + write_register_byte * num_bytes +
804 /// alt_bn128_g1_multiexp_base +
805 /// alt_bn128_g1_multiexp_element * num_elements`
806 pub fn alt_bn128_g1_multiexp(
807 &mut self,
808 value_len: u64,
809 value_ptr: u64,
810 register_id: u64,
811 ) -> Result<()> {
812 self.gas_counter.pay_base(alt_bn128_g1_multiexp_base)?;
813 let data = get_memory_or_register!(self, value_ptr, value_len)?;
814
815 let elements = super::alt_bn128::split_elements(&data)?;
816 self.gas_counter.pay_per(alt_bn128_g1_multiexp_element, elements.len() as u64)?;
817
818 let res = super::alt_bn128::g1_multiexp(elements)?;
819
820 self.registers.set(&mut self.gas_counter, &self.config.limit_config, register_id, res)
821 }
822
823 /// Computes sum for signed g1 group elements on alt_bn128 curve \sum_i
824 /// (-1)^{sign_i} g_{1 i} should be equal result.
825 ///
826 /// # Arguments
827 ///
828 /// * `value` - sequence of (sign:bool, g1:G1), where
829 /// G1 is point (x:Fq, y:Fq) on alt_bn128,
830 /// alt_bn128 is Y^2 = X^3 + 3 curve over Fq.
831 ///
832 /// `value` is encoded as packed, little-endian
833 /// `[(u8, (u256, u256))]` slice. `0u8` is postive sign,
834 /// `1u8` -- negative.
835 ///
836 /// # Errors
837 ///
838 /// If `value_len + value_ptr` points outside the memory or the registers
839 /// use more memory than the limit, the function returns `MemoryAccessViolation`.
840 ///
841 /// If point coordinates are not on curve, point is not in the subgroup,
842 /// scalar is not in the field, sign is not 0 or 1, or `value.len()%65!=0`,
843 /// the function returns `AltBn128InvalidInput`.
844 ///
845 /// # Cost
846 ///
847 /// `base + write_register_base + write_register_byte * num_bytes +
848 /// alt_bn128_g1_sum_base + alt_bn128_g1_sum_element * num_elements`
849 pub fn alt_bn128_g1_sum(
850 &mut self,
851 value_len: u64,
852 value_ptr: u64,
853 register_id: u64,
854 ) -> Result<()> {
855 self.gas_counter.pay_base(alt_bn128_g1_sum_base)?;
856 let data = get_memory_or_register!(self, value_ptr, value_len)?;
857
858 let elements = super::alt_bn128::split_elements(&data)?;
859 self.gas_counter.pay_per(alt_bn128_g1_sum_element, elements.len() as u64)?;
860
861 let res = super::alt_bn128::g1_sum(elements)?;
862
863 self.registers.set(&mut self.gas_counter, &self.config.limit_config, register_id, res)
864 }
865
866 /// Computes pairing check on alt_bn128 curve.
867 /// \sum_i e(g_{1 i}, g_{2 i}) should be equal one (in additive notation), e(g1, g2) is Ate pairing
868 ///
869 /// # Arguments
870 ///
871 /// * `value` - sequence of (g1:G1, g2:G2), where
872 /// G2 is Fr-ordered subgroup point (x:Fq2, y:Fq2) on alt_bn128 twist,
873 /// alt_bn128 twist is Y^2 = X^3 + 3/(i+9) curve over Fq2
874 /// Fq2 is complex field element (re: Fq, im: Fq)
875 /// G1 is point (x:Fq, y:Fq) on alt_bn128,
876 /// alt_bn128 is Y^2 = X^3 + 3 curve over Fq
877 ///
878 /// `value` is encoded a as packed, little-endian
879 /// `[((u256, u256), ((u256, u256), (u256, u256)))]` slice.
880 ///
881 /// # Errors
882 ///
883 /// If `value_len + value_ptr` points outside the memory or the registers use more memory than
884 /// the function returns `MemoryAccessViolation`.
885 ///
886 /// If point coordinates are not on curve, point is not in the subgroup, scalar
887 /// is not in the field or data are wrong serialized, for example,
888 /// `value.len()%192!=0`, the function returns `AltBn128InvalidInput`.
889 ///
890 /// # Cost
891 ///
892 /// `base + write_register_base + write_register_byte * num_bytes + alt_bn128_pairing_base + alt_bn128_pairing_element * num_elements`
893 pub fn alt_bn128_pairing_check(&mut self, value_len: u64, value_ptr: u64) -> Result<u64> {
894 self.gas_counter.pay_base(alt_bn128_pairing_check_base)?;
895 let data = get_memory_or_register!(self, value_ptr, value_len)?;
896
897 let elements = super::alt_bn128::split_elements(&data)?;
898 self.gas_counter.pay_per(alt_bn128_pairing_check_element, elements.len() as u64)?;
899
900 let res = super::alt_bn128::pairing_check(elements)?;
901
902 Ok(res as u64)
903 }
904
905 /// Writes random seed into the register.
906 ///
907 /// # Errors
908 ///
909 /// If the size of the registers exceed the set limit `MemoryAccessViolation`.
910 ///
911 /// # Cost
912 ///
913 /// `base + write_register_base + write_register_byte * num_bytes`.
914 pub fn random_seed(&mut self, register_id: u64) -> Result<()> {
915 self.gas_counter.pay_base(base)?;
916 self.registers.set(
917 &mut self.gas_counter,
918 &self.config.limit_config,
919 register_id,
920 self.context.random_seed.as_slice(),
921 )
922 }
923
924 /// Hashes the given value using sha256 and returns it into `register_id`.
925 ///
926 /// # Errors
927 ///
928 /// If `value_len + value_ptr` points outside the memory or the registers use more memory than
929 /// the limit with `MemoryAccessViolation`.
930 ///
931 /// # Cost
932 ///
933 /// `base + write_register_base + write_register_byte * num_bytes + sha256_base + sha256_byte * num_bytes`
934 pub fn sha256(&mut self, value_len: u64, value_ptr: u64, register_id: u64) -> Result<()> {
935 self.gas_counter.pay_base(sha256_base)?;
936 let value = get_memory_or_register!(self, value_ptr, value_len)?;
937 self.gas_counter.pay_per(sha256_byte, value.len() as u64)?;
938
939 use sha2::Digest;
940
941 let value_hash = sha2::Sha256::digest(&value);
942 self.registers.set(
943 &mut self.gas_counter,
944 &self.config.limit_config,
945 register_id,
946 value_hash.as_slice(),
947 )
948 }
949
950 /// Hashes the given value using keccak256 and returns it into `register_id`.
951 ///
952 /// # Errors
953 ///
954 /// If `value_len + value_ptr` points outside the memory or the registers use more memory than
955 /// the limit with `MemoryAccessViolation`.
956 ///
957 /// # Cost
958 ///
959 /// `base + write_register_base + write_register_byte * num_bytes + keccak256_base + keccak256_byte * num_bytes`
960 pub fn keccak256(&mut self, value_len: u64, value_ptr: u64, register_id: u64) -> Result<()> {
961 self.gas_counter.pay_base(keccak256_base)?;
962 let value = get_memory_or_register!(self, value_ptr, value_len)?;
963 self.gas_counter.pay_per(keccak256_byte, value.len() as u64)?;
964
965 use sha3::Digest;
966
967 let value_hash = sha3::Keccak256::digest(&value);
968 self.registers.set(
969 &mut self.gas_counter,
970 &self.config.limit_config,
971 register_id,
972 value_hash.as_slice(),
973 )
974 }
975
976 /// Hashes the given value using keccak512 and returns it into `register_id`.
977 ///
978 /// # Errors
979 ///
980 /// If `value_len + value_ptr` points outside the memory or the registers use more memory than
981 /// the limit with `MemoryAccessViolation`.
982 ///
983 /// # Cost
984 ///
985 /// `base + write_register_base + write_register_byte * num_bytes + keccak512_base + keccak512_byte * num_bytes`
986 pub fn keccak512(&mut self, value_len: u64, value_ptr: u64, register_id: u64) -> Result<()> {
987 self.gas_counter.pay_base(keccak512_base)?;
988 let value = get_memory_or_register!(self, value_ptr, value_len)?;
989 self.gas_counter.pay_per(keccak512_byte, value.len() as u64)?;
990
991 use sha3::Digest;
992
993 let value_hash = sha3::Keccak512::digest(&value);
994 self.registers.set(
995 &mut self.gas_counter,
996 &self.config.limit_config,
997 register_id,
998 value_hash.as_slice(),
999 )
1000 }
1001
1002 /// Hashes the given value using RIPEMD-160 and returns it into `register_id`.
1003 ///
1004 /// # Errors
1005 ///
1006 /// If `value_len + value_ptr` points outside the memory or the registers use more memory than
1007 /// the limit with `MemoryAccessViolation`.
1008 ///
1009 /// # Cost
1010 ///
1011 /// Where `message_blocks` is `(value_len + 9).div_ceil(64)`.
1012 ///
1013 /// `base + write_register_base + write_register_byte * num_bytes + ripemd160_base + ripemd160_block * message_blocks`
1014 pub fn ripemd160(&mut self, value_len: u64, value_ptr: u64, register_id: u64) -> Result<()> {
1015 self.gas_counter.pay_base(ripemd160_base)?;
1016 let value = get_memory_or_register!(self, value_ptr, value_len)?;
1017
1018 let message_blocks = value
1019 .len()
1020 .checked_add(8)
1021 .ok_or(VMLogicError::HostError(HostError::IntegerOverflow))?
1022 / 64
1023 + 1;
1024
1025 self.gas_counter.pay_per(ripemd160_block, message_blocks as u64)?;
1026
1027 use ripemd::Digest;
1028
1029 let value_hash = ripemd::Ripemd160::digest(&value);
1030 self.registers.set(
1031 &mut self.gas_counter,
1032 &self.config.limit_config,
1033 register_id,
1034 value_hash.as_slice(),
1035 )
1036 }
1037
1038 /// Recovers an ECDSA signer address and returns it into `register_id`.
1039 ///
1040 /// Takes in an additional flag to check for malleability of the signature
1041 /// which is generally only ideal for transactions.
1042 ///
1043 /// Returns a bool indicating success or failure as a `u64`.
1044 ///
1045 /// # Malleability Flags
1046 ///
1047 /// 0 - No extra checks.
1048 /// 1 - Rejecting upper range.
1049 ///
1050 /// # Errors
1051 ///
1052 /// * If `hash_ptr`, `r_ptr`, or `s_ptr` point outside the memory or the registers use more
1053 /// memory than the limit, then returns `MemoryAccessViolation`.
1054 ///
1055 /// # Cost
1056 ///
1057 /// `base + write_register_base + write_register_byte * 64 + ecrecover_base`
1058 pub fn ecrecover(
1059 &mut self,
1060 hash_len: u64,
1061 hash_ptr: u64,
1062 sig_len: u64,
1063 sig_ptr: u64,
1064 v: u64,
1065 malleability_flag: u64,
1066 register_id: u64,
1067 ) -> Result<u64> {
1068 self.gas_counter.pay_base(ecrecover_base)?;
1069
1070 let signature = {
1071 let vec = get_memory_or_register!(self, sig_ptr, sig_len)?;
1072 if vec.len() != 64 {
1073 return Err(VMLogicError::HostError(HostError::ECRecoverError {
1074 msg: format!(
1075 "The length of the signature: {}, exceeds the limit of 64 bytes",
1076 vec.len()
1077 ),
1078 }));
1079 }
1080
1081 let mut bytes = [0u8; 65];
1082 bytes[0..64].copy_from_slice(&vec);
1083
1084 if v < 4 {
1085 bytes[64] = v as u8;
1086 Secp256K1Signature::from(bytes)
1087 } else {
1088 return Err(VMLogicError::HostError(HostError::ECRecoverError {
1089 msg: format!("V recovery byte 0 through 3 are valid but was provided {}", v),
1090 }));
1091 }
1092 };
1093
1094 let hash = {
1095 let vec = get_memory_or_register!(self, hash_ptr, hash_len)?;
1096 if vec.len() != 32 {
1097 return Err(VMLogicError::HostError(HostError::ECRecoverError {
1098 msg: format!(
1099 "The length of the hash: {}, exceeds the limit of 32 bytes",
1100 vec.len()
1101 ),
1102 }));
1103 }
1104
1105 let mut bytes = [0u8; 32];
1106 bytes.copy_from_slice(&vec);
1107 bytes
1108 };
1109
1110 if malleability_flag != 0 && malleability_flag != 1 {
1111 return Err(VMLogicError::HostError(HostError::ECRecoverError {
1112 msg: format!(
1113 "Malleability flag needs to be 0 or 1, but is instead {}",
1114 malleability_flag
1115 ),
1116 }));
1117 }
1118
1119 if !signature.check_signature_values(malleability_flag != 0) {
1120 return Ok(false as u64);
1121 }
1122
1123 if let Ok(pk) = signature.recover(hash) {
1124 self.registers.set(
1125 &mut self.gas_counter,
1126 &self.config.limit_config,
1127 register_id,
1128 pk.as_ref(),
1129 )?;
1130 return Ok(true as u64);
1131 };
1132
1133 Ok(false as u64)
1134 }
1135
1136 /// Verify an ED25519 signature given a message and a public key.
1137 ///
1138 /// Returns a bool indicating success (1) or failure (0) as a `u64`.
1139 ///
1140 /// # Errors
1141 ///
1142 /// * If the public key's size is not equal to 32, or signature size is not
1143 /// equal to 64, returns [HostError::Ed25519VerifyInvalidInput].
1144 /// * If any of the signature, message or public key arguments are out of
1145 /// memory bounds, returns [`HostError::MemoryAccessViolation`]
1146 ///
1147 /// # Cost
1148 ///
1149 /// Each input can either be in memory or in a register. Set the length of
1150 /// the input to `u64::MAX` to declare that the input is a register number
1151 /// and not a pointer. Each input has a gas cost input_cost(num_bytes) that
1152 /// depends on whether it is from memory or from a register. It is either
1153 /// read_memory_base + num_bytes * read_memory_byte in the former case or
1154 /// read_register_base + num_bytes * read_register_byte in the latter. This
1155 /// function is labeled as `input_cost` below.
1156 ///
1157 /// `input_cost(num_bytes_signature) + input_cost(num_bytes_message) +
1158 /// input_cost(num_bytes_public_key) + ed25519_verify_base +
1159 /// ed25519_verify_byte * num_bytes_message`
1160 pub fn ed25519_verify(
1161 &mut self,
1162 signature_len: u64,
1163 signature_ptr: u64,
1164 message_len: u64,
1165 message_ptr: u64,
1166 public_key_len: u64,
1167 public_key_ptr: u64,
1168 ) -> Result<u64> {
1169 use ed25519_dalek::Verifier;
1170
1171 self.gas_counter.pay_base(ed25519_verify_base)?;
1172
1173 let signature: ed25519_dalek::Signature = {
1174 let vec = get_memory_or_register!(self, signature_ptr, signature_len)?;
1175 let b = <&[u8; ed25519_dalek::SIGNATURE_LENGTH]>::try_from(&vec[..]).map_err(|_| {
1176 VMLogicError::HostError(HostError::Ed25519VerifyInvalidInput {
1177 msg: "invalid signature length".to_string(),
1178 })
1179 })?;
1180 // Sanity-check that was performed by ed25519-dalek in from_bytes before version 2,
1181 // but was removed with version 2. It is not actually any good a check, but we need
1182 // it to avoid costs changing.
1183 if b[ed25519_dalek::SIGNATURE_LENGTH - 1] & 0b1110_0000 != 0 {
1184 return Ok(false as u64);
1185 }
1186 ed25519_dalek::Signature::from_bytes(b)
1187 };
1188
1189 let message = get_memory_or_register!(self, message_ptr, message_len)?;
1190 self.gas_counter.pay_per(ed25519_verify_byte, message.len() as u64)?;
1191
1192 let public_key: ed25519_dalek::VerifyingKey = {
1193 let vec = get_memory_or_register!(self, public_key_ptr, public_key_len)?;
1194 let b =
1195 <&[u8; ed25519_dalek::PUBLIC_KEY_LENGTH]>::try_from(&vec[..]).map_err(|_| {
1196 VMLogicError::HostError(HostError::Ed25519VerifyInvalidInput {
1197 msg: "invalid public key length".to_string(),
1198 })
1199 })?;
1200 match ed25519_dalek::VerifyingKey::from_bytes(b) {
1201 Ok(public_key) => public_key,
1202 Err(_) => return Ok(false as u64),
1203 }
1204 };
1205
1206 match public_key.verify(&message, &signature) {
1207 Err(_) => Ok(false as u64),
1208 Ok(()) => Ok(true as u64),
1209 }
1210 }
1211
1212 /// Consume gas. Counts both towards `burnt_gas` and `used_gas`.
1213 ///
1214 /// # Errors
1215 ///
1216 /// * If passed gas amount somehow overflows internal gas counters returns `IntegerOverflow`;
1217 /// * If we exceed usage limit imposed on burnt gas returns `GasLimitExceeded`;
1218 /// * If we exceed the `prepaid_gas` then returns `GasExceeded`.
1219 pub fn gas(&mut self, gas: Gas) -> Result<()> {
1220 self.gas_counter.burn_gas(Gas::from(gas))
1221 }
1222
1223 pub fn gas_opcodes(&mut self, opcodes: u32) -> Result<()> {
1224 self.gas(opcodes as u64 * self.config.regular_op_cost as u64)
1225 }
1226
1227 /// This is the function that is exposed to WASM contracts under the name `gas`.
1228 ///
1229 /// For now it is consuming the gas for `gas` opcodes. When we switch to finite-wasm it’ll
1230 /// be made to be a no-op.
1231 ///
1232 /// This function might be intrinsified.
1233 pub fn gas_seen_from_wasm(&mut self, gas: u32) -> Result<()> {
1234 self.gas_opcodes(gas)
1235 }
1236
1237 // ################
1238 // # Promises API #
1239 // ################
1240
1241 /// A helper function to pay gas fee for creating a new receipt without actions.
1242 /// # Args:
1243 /// * `sir`: whether contract call is addressed to itself;
1244 /// * `data_dependencies`: other contracts that this execution will be waiting on (or rather
1245 /// their data receipts), where bool indicates whether this is sender=receiver communication.
1246 ///
1247 /// # Cost
1248 ///
1249 /// This is a convenience function that encapsulates several costs:
1250 /// `burnt_gas := dispatch cost of the receipt + base dispatch cost cost of the data receipt`
1251 /// `used_gas := burnt_gas + exec cost of the receipt + base exec cost cost of the data receipt`
1252 /// Notice that we prepay all base cost upon the creation of the data dependency, we are going to
1253 /// pay for the content transmitted through the dependency upon the actual creation of the
1254 /// DataReceipt.
1255 fn pay_gas_for_new_receipt(&mut self, sir: bool, data_dependencies: &[bool]) -> Result<()> {
1256 let fees_config_cfg = &self.fees_config;
1257 let mut burn_gas = fees_config_cfg.fee(ActionCosts::new_action_receipt).send_fee(sir);
1258 let mut use_gas = fees_config_cfg.fee(ActionCosts::new_action_receipt).exec_fee();
1259 for dep in data_dependencies {
1260 // Both creation and execution for data receipts are considered burnt gas.
1261 burn_gas = burn_gas
1262 .checked_add(fees_config_cfg.fee(ActionCosts::new_data_receipt_base).send_fee(*dep))
1263 .ok_or(HostError::IntegerOverflow)?
1264 .checked_add(fees_config_cfg.fee(ActionCosts::new_data_receipt_base).exec_fee())
1265 .ok_or(HostError::IntegerOverflow)?;
1266 }
1267 use_gas = use_gas.checked_add(burn_gas).ok_or(HostError::IntegerOverflow)?;
1268 // This should go to `new_data_receipt_base` and `new_action_receipt` in parts.
1269 // But we have to keep charing these two together unless we make a protocol change.
1270 self.gas_counter.pay_action_accumulated(burn_gas, use_gas, ActionCosts::new_action_receipt)
1271 }
1272
1273 /// A helper function to subtract balance on transfer or attached deposit for promises.
1274 /// # Args:
1275 /// * `amount`: the amount to deduct from the current account balance.
1276 fn deduct_balance(&mut self, amount: Balance) -> Result<()> {
1277 self.current_account_balance =
1278 self.current_account_balance.checked_sub(amount).ok_or(HostError::BalanceExceeded)?;
1279 Ok(())
1280 }
1281
1282 /// Creates a promise that will execute a method on account with given arguments and attaches
1283 /// the given amount and gas. `amount_ptr` point to slices of bytes representing `u128`.
1284 ///
1285 /// # Errors
1286 ///
1287 /// * If `account_id_len + account_id_ptr` or `method_name_len + method_name_ptr` or
1288 /// `arguments_len + arguments_ptr` or `amount_ptr + 16` points outside the memory of the guest
1289 /// or host returns `MemoryAccessViolation`.
1290 /// * If called as view function returns `ProhibitedInView`.
1291 ///
1292 /// # Returns
1293 ///
1294 /// Index of the new promise that uniquely identifies it within the current execution of the
1295 /// method.
1296 ///
1297 /// # Cost
1298 ///
1299 /// Since `promise_create` is a convenience wrapper around `promise_batch_create` and
1300 /// `promise_batch_action_function_call`. This also means it charges `base` cost twice.
1301 pub fn promise_create(
1302 &mut self,
1303 account_id_len: u64,
1304 account_id_ptr: u64,
1305 method_name_len: u64,
1306 method_name_ptr: u64,
1307 arguments_len: u64,
1308 arguments_ptr: u64,
1309 amount_ptr: u64,
1310 gas: Gas,
1311 ) -> Result<u64> {
1312 let new_promise_idx = self.promise_batch_create(account_id_len, account_id_ptr)?;
1313 self.promise_batch_action_function_call(
1314 new_promise_idx,
1315 method_name_len,
1316 method_name_ptr,
1317 arguments_len,
1318 arguments_ptr,
1319 amount_ptr,
1320 gas,
1321 )?;
1322 Ok(new_promise_idx)
1323 }
1324
1325 /// Attaches the callback that is executed after promise pointed by `promise_idx` is complete.
1326 ///
1327 /// # Errors
1328 ///
1329 /// * If `promise_idx` does not correspond to an existing promise returns `InvalidPromiseIndex`;
1330 /// * If `account_id_len + account_id_ptr` or `method_name_len + method_name_ptr` or
1331 /// `arguments_len + arguments_ptr` or `amount_ptr + 16` points outside the memory of the
1332 /// guest or host returns `MemoryAccessViolation`.
1333 /// * If called as view function returns `ProhibitedInView`.
1334 ///
1335 /// # Returns
1336 ///
1337 /// Index of the new promise that uniquely identifies it within the current execution of the
1338 /// method.
1339 ///
1340 /// # Cost
1341 ///
1342 /// Since `promise_create` is a convenience wrapper around `promise_batch_then` and
1343 /// `promise_batch_action_function_call`. This also means it charges `base` cost twice.
1344 pub fn promise_then(
1345 &mut self,
1346 promise_idx: u64,
1347 account_id_len: u64,
1348 account_id_ptr: u64,
1349 method_name_len: u64,
1350 method_name_ptr: u64,
1351 arguments_len: u64,
1352 arguments_ptr: u64,
1353 amount_ptr: u64,
1354 gas: u64,
1355 ) -> Result<u64> {
1356 let new_promise_idx =
1357 self.promise_batch_then(promise_idx, account_id_len, account_id_ptr)?;
1358 self.promise_batch_action_function_call(
1359 new_promise_idx,
1360 method_name_len,
1361 method_name_ptr,
1362 arguments_len,
1363 arguments_ptr,
1364 amount_ptr,
1365 gas,
1366 )?;
1367 Ok(new_promise_idx)
1368 }
1369
1370 /// Creates a new promise which completes when time all promises passed as arguments complete.
1371 /// Cannot be used with registers. `promise_idx_ptr` points to an array of `u64` elements, with
1372 /// `promise_idx_count` denoting the number of elements. The array contains indices of promises
1373 /// that need to be waited on jointly.
1374 ///
1375 /// # Errors
1376 ///
1377 /// * If `promise_ids_ptr + 8 * promise_idx_count` extend outside the guest memory returns
1378 /// `MemoryAccessViolation`;
1379 /// * If any of the promises in the array do not correspond to existing promises returns
1380 /// `InvalidPromiseIndex`.
1381 /// * If called as view function returns `ProhibitedInView`.
1382 /// * If the total number of receipt dependencies exceeds `max_number_input_data_dependencies`
1383 /// limit returns `NumInputDataDependenciesExceeded`.
1384 /// * If the total number of promises exceeds `max_promises_per_function_call_action` limit
1385 /// returns `NumPromisesExceeded`.
1386 ///
1387 /// # Returns
1388 ///
1389 /// Index of the new promise that uniquely identifies it within the current execution of the
1390 /// method.
1391 ///
1392 /// # Cost
1393 ///
1394 /// `base + promise_and_base + promise_and_per_promise * num_promises + cost of reading promise ids from memory`.
1395 pub fn promise_and(
1396 &mut self,
1397 promise_idx_ptr: u64,
1398 promise_idx_count: u64,
1399 ) -> Result<PromiseIndex> {
1400 self.gas_counter.pay_base(base)?;
1401 if self.context.is_view() {
1402 return Err(
1403 HostError::ProhibitedInView { method_name: "promise_and".to_string() }.into()
1404 );
1405 }
1406 self.gas_counter.pay_base(promise_and_base)?;
1407 let memory_len = promise_idx_count
1408 .checked_mul(size_of::<u64>() as u64)
1409 .ok_or(HostError::IntegerOverflow)?;
1410 self.gas_counter.pay_per(promise_and_per_promise, memory_len)?;
1411
1412 // Read indices as little endian u64.
1413 let promise_indices = self
1414 .memory
1415 .view(&mut self.gas_counter, MemSlice { ptr: promise_idx_ptr, len: memory_len })?;
1416 let promise_indices = stdx::as_chunks_exact::<{ size_of::<u64>() }, u8>(&promise_indices)
1417 .unwrap()
1418 .into_iter()
1419 .map(|bytes| u64::from_le_bytes(*bytes));
1420
1421 let mut receipt_dependencies = vec![];
1422 for promise_idx in promise_indices {
1423 let promise = self
1424 .promises
1425 .get(promise_idx as usize)
1426 .ok_or(HostError::InvalidPromiseIndex { promise_idx })?;
1427 match &promise {
1428 Promise::Receipt(receipt_idx) => {
1429 receipt_dependencies.push(*receipt_idx);
1430 }
1431 Promise::NotReceipt(receipt_indices) => {
1432 receipt_dependencies.extend(receipt_indices.clone());
1433 }
1434 }
1435 // Checking this in the loop to prevent abuse of too many joined vectors.
1436 if receipt_dependencies.len() as u64
1437 > self.config.limit_config.max_number_input_data_dependencies
1438 {
1439 return Err(HostError::NumberInputDataDependenciesExceeded {
1440 number_of_input_data_dependencies: receipt_dependencies.len() as u64,
1441 limit: self.config.limit_config.max_number_input_data_dependencies,
1442 }
1443 .into());
1444 }
1445 }
1446 self.checked_push_promise(Promise::NotReceipt(receipt_dependencies))
1447 }
1448
1449 /// Creates a new promise towards given `account_id` without any actions attached to it.
1450 ///
1451 /// # Errors
1452 ///
1453 /// * If `account_id_len + account_id_ptr` points outside the memory of the guest or host
1454 /// returns `MemoryAccessViolation`.
1455 /// * If called as view function returns `ProhibitedInView`.
1456 /// * If the total number of promises exceeds `max_promises_per_function_call_action` limit
1457 /// returns `NumPromisesExceeded`.
1458 ///
1459 /// # Returns
1460 ///
1461 /// Index of the new promise that uniquely identifies it within the current execution of the
1462 /// method.
1463 ///
1464 /// # Cost
1465 ///
1466 /// `burnt_gas := base + cost of reading and decoding the account id + dispatch cost of the receipt`.
1467 /// `used_gas := burnt_gas + exec cost of the receipt`.
1468 pub fn promise_batch_create(
1469 &mut self,
1470 account_id_len: u64,
1471 account_id_ptr: u64,
1472 ) -> Result<u64> {
1473 self.gas_counter.pay_base(base)?;
1474 if self.context.is_view() {
1475 return Err(HostError::ProhibitedInView {
1476 method_name: "promise_batch_create".to_string(),
1477 }
1478 .into());
1479 }
1480 let account_id = self.read_and_parse_account_id(account_id_ptr, account_id_len)?;
1481 let sir = account_id == self.context.current_account_id;
1482 self.pay_gas_for_new_receipt(sir, &[])?;
1483 let new_receipt_idx = self.ext.create_receipt(vec![], account_id)?;
1484
1485 self.checked_push_promise(Promise::Receipt(new_receipt_idx))
1486 }
1487
1488 /// Creates a new promise towards given `account_id` without any actions attached, that is
1489 /// executed after promise pointed by `promise_idx` is complete.
1490 ///
1491 /// # Errors
1492 ///
1493 /// * If `promise_idx` does not correspond to an existing promise returns `InvalidPromiseIndex`;
1494 /// * If `account_id_len + account_id_ptr` points outside the memory of the guest or host
1495 /// returns `MemoryAccessViolation`.
1496 /// * If called as view function returns `ProhibitedInView`.
1497 /// * If the total number of promises exceeds `max_promises_per_function_call_action` limit
1498 /// returns `NumPromisesExceeded`.
1499 ///
1500 /// # Returns
1501 ///
1502 /// Index of the new promise that uniquely identifies it within the current execution of the
1503 /// method.
1504 ///
1505 /// # Cost
1506 ///
1507 /// `base + cost of reading and decoding the account id + dispatch&execution cost of the receipt
1508 /// + dispatch&execution base cost for each data dependency`
1509 pub fn promise_batch_then(
1510 &mut self,
1511 promise_idx: u64,
1512 account_id_len: u64,
1513 account_id_ptr: u64,
1514 ) -> Result<u64> {
1515 self.gas_counter.pay_base(base)?;
1516 if self.context.is_view() {
1517 return Err(HostError::ProhibitedInView {
1518 method_name: "promise_batch_then".to_string(),
1519 }
1520 .into());
1521 }
1522 let account_id = self.read_and_parse_account_id(account_id_ptr, account_id_len)?;
1523 // Update the DAG and return new promise idx.
1524 let promise = self
1525 .promises
1526 .get(promise_idx as usize)
1527 .ok_or(HostError::InvalidPromiseIndex { promise_idx })?;
1528 let receipt_dependencies = match &promise {
1529 Promise::Receipt(receipt_idx) => vec![*receipt_idx],
1530 Promise::NotReceipt(receipt_indices) => receipt_indices.clone(),
1531 };
1532
1533 let sir = account_id == self.context.current_account_id;
1534 let deps: Vec<_> = receipt_dependencies
1535 .iter()
1536 .map(|&receipt_idx| self.ext.get_receipt_receiver(receipt_idx) == &account_id)
1537 .collect();
1538 self.pay_gas_for_new_receipt(sir, &deps)?;
1539
1540 let new_receipt_idx = self.ext.create_receipt(receipt_dependencies, account_id)?;
1541
1542 self.checked_push_promise(Promise::Receipt(new_receipt_idx))
1543 }
1544
1545 /// Helper function to return the receipt index corresponding to the given promise index.
1546 /// It also pulls account ID for the given receipt and compares it with the current account ID
1547 /// to return whether the receipt's account ID is the same.
1548 fn promise_idx_to_receipt_idx_with_sir(
1549 &self,
1550 promise_idx: u64,
1551 ) -> Result<(ReceiptIndex, bool)> {
1552 let promise = self
1553 .promises
1554 .get(promise_idx as usize)
1555 .ok_or(HostError::InvalidPromiseIndex { promise_idx })?;
1556 let receipt_idx = match &promise {
1557 Promise::Receipt(receipt_idx) => Ok(*receipt_idx),
1558 Promise::NotReceipt(_) => Err(HostError::CannotAppendActionToJointPromise),
1559 }?;
1560
1561 let account_id = self.ext.get_receipt_receiver(receipt_idx);
1562 let sir = account_id == &self.context.current_account_id;
1563 Ok((receipt_idx, sir))
1564 }
1565
1566 /// Appends `CreateAccount` action to the batch of actions for the given promise pointed by
1567 /// `promise_idx`.
1568 ///
1569 /// # Errors
1570 ///
1571 /// * If `promise_idx` does not correspond to an existing promise returns `InvalidPromiseIndex`.
1572 /// * If the promise pointed by the `promise_idx` is an ephemeral promise created by
1573 /// `promise_and` returns `CannotAppendActionToJointPromise`.
1574 /// * If called as view function returns `ProhibitedInView`.
1575 ///
1576 /// # Cost
1577 ///
1578 /// `burnt_gas := base + dispatch action fee`
1579 /// `used_gas := burnt_gas + exec action fee`
1580 pub fn promise_batch_action_create_account(&mut self, promise_idx: u64) -> Result<()> {
1581 self.gas_counter.pay_base(base)?;
1582 if self.context.is_view() {
1583 return Err(HostError::ProhibitedInView {
1584 method_name: "promise_batch_action_create_account".to_string(),
1585 }
1586 .into());
1587 }
1588 let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?;
1589
1590 self.pay_action_base(ActionCosts::create_account, sir)?;
1591
1592 self.ext.append_action_create_account(receipt_idx)?;
1593 Ok(())
1594 }
1595
1596 /// Appends `DeployContract` action to the batch of actions for the given promise pointed by
1597 /// `promise_idx`.
1598 ///
1599 /// # Errors
1600 ///
1601 /// * If `promise_idx` does not correspond to an existing promise returns `InvalidPromiseIndex`.
1602 /// * If the promise pointed by the `promise_idx` is an ephemeral promise created by
1603 /// `promise_and` returns `CannotAppendActionToJointPromise`.
1604 /// * If `code_len + code_ptr` points outside the memory of the guest or host returns
1605 /// `MemoryAccessViolation`.
1606 /// * If called as view function returns `ProhibitedInView`.
1607 /// * If the contract code length exceeds `max_contract_size` returns `ContractSizeExceeded`.
1608 ///
1609 /// # Cost
1610 ///
1611 /// `burnt_gas := base + dispatch action base fee + dispatch action per byte fee * num bytes + cost of reading vector from memory `
1612 /// `used_gas := burnt_gas + exec action base fee + exec action per byte fee * num bytes`
1613 pub fn promise_batch_action_deploy_contract(
1614 &mut self,
1615 promise_idx: u64,
1616 code_len: u64,
1617 code_ptr: u64,
1618 ) -> Result<()> {
1619 self.gas_counter.pay_base(base)?;
1620 if self.context.is_view() {
1621 return Err(HostError::ProhibitedInView {
1622 method_name: "promise_batch_action_deploy_contract".to_string(),
1623 }
1624 .into());
1625 }
1626 let code = get_memory_or_register!(self, code_ptr, code_len)?;
1627 let code_len = code.len() as u64;
1628 let limit = self.config.limit_config.max_contract_size;
1629 if code_len > limit {
1630 return Err(HostError::ContractSizeExceeded { size: code_len, limit }.into());
1631 }
1632 let code = code.into_owned();
1633
1634 let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?;
1635
1636 self.pay_action_base(ActionCosts::deploy_contract_base, sir)?;
1637 self.pay_action_per_byte(ActionCosts::deploy_contract_byte, code_len, sir)?;
1638
1639 self.ext.append_action_deploy_contract(receipt_idx, code)?;
1640 Ok(())
1641 }
1642
1643 /// Appends `FunctionCall` action to the batch of actions for the given promise pointed by
1644 /// `promise_idx`.
1645 ///
1646 /// # Errors
1647 ///
1648 /// * If `promise_idx` does not correspond to an existing promise returns `InvalidPromiseIndex`.
1649 /// * If the promise pointed by the `promise_idx` is an ephemeral promise created by
1650 /// `promise_and` returns `CannotAppendActionToJointPromise`.
1651 /// * If `method_name_len + method_name_ptr` or `arguments_len + arguments_ptr` or
1652 /// `amount_ptr + 16` points outside the memory of the guest or host returns
1653 /// `MemoryAccessViolation`.
1654 /// * If called as view function returns `ProhibitedInView`.
1655 ///
1656 /// # Cost
1657 ///
1658 /// `burnt_gas := base + dispatch action base fee + dispatch action per byte fee * num bytes + cost of reading vector from memory
1659 /// + cost of reading u128, method_name and arguments from the memory`
1660 /// `used_gas := burnt_gas + exec action base fee + exec action per byte fee * num bytes`
1661 pub fn promise_batch_action_function_call(
1662 &mut self,
1663 promise_idx: u64,
1664 method_name_len: u64,
1665 method_name_ptr: u64,
1666 arguments_len: u64,
1667 arguments_ptr: u64,
1668 amount_ptr: u64,
1669 gas: Gas,
1670 ) -> Result<()> {
1671 self.promise_batch_action_function_call_weight(
1672 promise_idx,
1673 method_name_len,
1674 method_name_ptr,
1675 arguments_len,
1676 arguments_ptr,
1677 amount_ptr,
1678 gas,
1679 0,
1680 )
1681 }
1682
1683 /// Appends `FunctionCall` action to the batch of actions for the given promise pointed by
1684 /// `promise_idx`. This function allows not specifying a specific gas value and allowing the
1685 /// runtime to assign remaining gas based on a weight.
1686 ///
1687 /// # Gas
1688 ///
1689 /// Gas can be specified using a static amount, a weight of remaining prepaid gas, or a mixture
1690 /// of both. To omit a static gas amount, `0` can be passed for the `gas` parameter.
1691 /// To omit assigning remaining gas, `0` can be passed as the `gas_weight` parameter.
1692 ///
1693 /// The gas weight parameter works as the following:
1694 ///
1695 /// All unused prepaid gas from the current function call is split among all function calls
1696 /// which supply this gas weight. The amount attached to each respective call depends on the
1697 /// value of the weight.
1698 ///
1699 /// For example, if 40 gas is leftover from the current method call and three functions specify
1700 /// the weights 1, 5, 2 then 5, 25, 10 gas will be added to each function call respectively,
1701 /// using up all remaining available gas.
1702 ///
1703 /// If the `gas_weight` parameter is set as a large value, the amount of distributed gas
1704 /// to each action can be 0 or a very low value because the amount of gas per weight is
1705 /// based on the floor division of the amount of gas by the sum of weights.
1706 ///
1707 /// Any remaining gas will be distributed to the last scheduled function call with a weight
1708 /// specified.
1709 ///
1710 /// # Errors
1711 ///
1712 /// * If `promise_idx` does not correspond to an existing promise returns `InvalidPromiseIndex`.
1713 /// * If the promise pointed by the `promise_idx` is an ephemeral promise created by
1714 /// `promise_and` returns `CannotAppendActionToJointPromise`.
1715 /// * If `method_name_len + method_name_ptr` or `arguments_len + arguments_ptr` or
1716 /// `amount_ptr + 16` points outside the memory of the guest or host returns
1717 /// `MemoryAccessViolation`.
1718 /// * If called as view function returns `ProhibitedInView`.
1719 pub fn promise_batch_action_function_call_weight(
1720 &mut self,
1721 promise_idx: u64,
1722 method_name_len: u64,
1723 method_name_ptr: u64,
1724 arguments_len: u64,
1725 arguments_ptr: u64,
1726 amount_ptr: u64,
1727 gas: Gas,
1728 gas_weight: u64,
1729 ) -> Result<()> {
1730 self.gas_counter.pay_base(base)?;
1731 if self.context.is_view() {
1732 return Err(HostError::ProhibitedInView {
1733 method_name: "promise_batch_action_function_call".to_string(),
1734 }
1735 .into());
1736 }
1737 let amount = self.memory.get_u128(&mut self.gas_counter, amount_ptr)?;
1738 let method_name = get_memory_or_register!(self, method_name_ptr, method_name_len)?;
1739 if method_name.is_empty() {
1740 return Err(HostError::EmptyMethodName.into());
1741 }
1742 let arguments = get_memory_or_register!(self, arguments_ptr, arguments_len)?;
1743
1744 let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?;
1745
1746 let method_name = method_name.into_owned();
1747 let arguments = arguments.into_owned();
1748 // Input can't be large enough to overflow
1749 let num_bytes = method_name.len() as u64 + arguments.len() as u64;
1750 self.pay_action_base(ActionCosts::function_call_base, sir)?;
1751 self.pay_action_per_byte(ActionCosts::function_call_byte, num_bytes, sir)?;
1752 // Prepaid gas
1753 self.gas_counter.prepay_gas(gas)?;
1754
1755 self.deduct_balance(amount)?;
1756
1757 self.ext.append_action_function_call_weight(
1758 receipt_idx,
1759 method_name,
1760 arguments,
1761 amount,
1762 gas,
1763 GasWeight(gas_weight),
1764 )
1765 }
1766
1767 /// Appends `Transfer` action to the batch of actions for the given promise pointed by
1768 /// `promise_idx`.
1769 ///
1770 /// # Errors
1771 ///
1772 /// * If `promise_idx` does not correspond to an existing promise returns `InvalidPromiseIndex`.
1773 /// * If the promise pointed by the `promise_idx` is an ephemeral promise created by
1774 /// `promise_and` returns `CannotAppendActionToJointPromise`.
1775 /// * If `amount_ptr + 16` points outside the memory of the guest or host returns
1776 /// `MemoryAccessViolation`.
1777 /// * If called as view function returns `ProhibitedInView`.
1778 ///
1779 /// # Cost
1780 ///
1781 /// `burnt_gas := base + dispatch action base fee + dispatch action per byte fee * num bytes + cost of reading u128 from memory `
1782 /// `used_gas := burnt_gas + exec action base fee + exec action per byte fee * num bytes`
1783 pub fn promise_batch_action_transfer(
1784 &mut self,
1785 promise_idx: u64,
1786 amount_ptr: u64,
1787 ) -> Result<()> {
1788 self.gas_counter.pay_base(base)?;
1789 if self.context.is_view() {
1790 return Err(HostError::ProhibitedInView {
1791 method_name: "promise_batch_action_transfer".to_string(),
1792 }
1793 .into());
1794 }
1795 let amount = self.memory.get_u128(&mut self.gas_counter, amount_ptr)?;
1796
1797 let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?;
1798 let receiver_id = self.ext.get_receipt_receiver(receipt_idx);
1799 let send_fee = transfer_send_fee(
1800 self.fees_config,
1801 sir,
1802 self.config.implicit_account_creation,
1803 self.config.eth_accounts,
1804 receiver_id.get_account_type(),
1805 );
1806 let exec_fee = transfer_exec_fee(
1807 self.fees_config,
1808 self.config.implicit_account_creation,
1809 self.config.eth_accounts,
1810 receiver_id.get_account_type(),
1811 );
1812 let burn_gas = send_fee;
1813 let use_gas = burn_gas.checked_add(exec_fee).ok_or(HostError::IntegerOverflow)?;
1814 self.gas_counter.pay_action_accumulated(burn_gas, use_gas, ActionCosts::transfer)?;
1815
1816 self.deduct_balance(amount)?;
1817
1818 self.ext.append_action_transfer(receipt_idx, amount)?;
1819 Ok(())
1820 }
1821
1822 /// Appends `Stake` action to the batch of actions for the given promise pointed by
1823 /// `promise_idx`.
1824 ///
1825 /// # Errors
1826 ///
1827 /// * If `promise_idx` does not correspond to an existing promise returns `InvalidPromiseIndex`.
1828 /// * If the promise pointed by the `promise_idx` is an ephemeral promise created by
1829 /// `promise_and` returns `CannotAppendActionToJointPromise`.
1830 /// * If the given public key is not a valid (e.g. wrong length) returns `InvalidPublicKey`.
1831 /// * If `amount_ptr + 16` or `public_key_len + public_key_ptr` points outside the memory of the
1832 /// guest or host returns `MemoryAccessViolation`.
1833 /// * If called as view function returns `ProhibitedInView`.
1834 ///
1835 /// # Cost
1836 ///
1837 /// `burnt_gas := base + dispatch action base fee + dispatch action per byte fee * num bytes + cost of reading public key from memory `
1838 /// `used_gas := burnt_gas + exec action base fee + exec action per byte fee * num bytes`
1839 pub fn promise_batch_action_stake(
1840 &mut self,
1841 promise_idx: u64,
1842 amount_ptr: u64,
1843 public_key_len: u64,
1844 public_key_ptr: u64,
1845 ) -> Result<()> {
1846 self.gas_counter.pay_base(base)?;
1847 if self.context.is_view() {
1848 return Err(HostError::ProhibitedInView {
1849 method_name: "promise_batch_action_stake".to_string(),
1850 }
1851 .into());
1852 }
1853 let amount = self.memory.get_u128(&mut self.gas_counter, amount_ptr)?;
1854 let public_key = self.get_public_key(public_key_ptr, public_key_len)?;
1855 let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?;
1856 self.pay_action_base(ActionCosts::pledge, sir)?;
1857 self.ext.append_action_pledge(receipt_idx, amount, public_key.decode()?);
1858 Ok(())
1859 }
1860
1861 /// Appends `AddKey` action to the batch of actions for the given promise pointed by
1862 /// `promise_idx`. The access key will have `FullAccess` permission.
1863 ///
1864 /// # Errors
1865 ///
1866 /// * If `promise_idx` does not correspond to an existing promise returns `InvalidPromiseIndex`.
1867 /// * If the promise pointed by the `promise_idx` is an ephemeral promise created by
1868 /// `promise_and` returns `CannotAppendActionToJointPromise`.
1869 /// * If the given public key is not a valid (e.g. wrong length) returns `InvalidPublicKey`.
1870 /// * If `public_key_len + public_key_ptr` points outside the memory of the guest or host
1871 /// returns `MemoryAccessViolation`.
1872 /// * If called as view function returns `ProhibitedInView`.
1873 ///
1874 /// # Cost
1875 ///
1876 /// `burnt_gas := base + dispatch action base fee + dispatch action per byte fee * num bytes + cost of reading public key from memory `
1877 /// `used_gas := burnt_gas + exec action base fee + exec action per byte fee * num bytes`
1878 pub fn promise_batch_action_add_key_with_full_access(
1879 &mut self,
1880 promise_idx: u64,
1881 public_key_len: u64,
1882 public_key_ptr: u64,
1883 nonce: u64,
1884 ) -> Result<()> {
1885 self.gas_counter.pay_base(base)?;
1886 if self.context.is_view() {
1887 return Err(HostError::ProhibitedInView {
1888 method_name: "promise_batch_action_add_key_with_full_access".to_string(),
1889 }
1890 .into());
1891 }
1892 let public_key = self.get_public_key(public_key_ptr, public_key_len)?;
1893 let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?;
1894 self.pay_action_base(ActionCosts::add_full_access_key, sir)?;
1895 self.ext.append_action_add_key_with_full_access(receipt_idx, public_key.decode()?, nonce);
1896 Ok(())
1897 }
1898
1899 /// Appends `AddKey` action to the batch of actions for the given promise pointed by
1900 /// `promise_idx`. The access key will have `FunctionCall` permission.
1901 ///
1902 /// # Errors
1903 ///
1904 /// * If `promise_idx` does not correspond to an existing promise returns `InvalidPromiseIndex`.
1905 /// * If the promise pointed by the `promise_idx` is an ephemeral promise created by
1906 /// `promise_and` returns `CannotAppendActionToJointPromise`.
1907 /// * If the given public key is not a valid (e.g. wrong length) returns `InvalidPublicKey`.
1908 /// * If `public_key_len + public_key_ptr`, `allowance_ptr + 16`,
1909 /// `receiver_id_len + receiver_id_ptr` or `method_names_len + method_names_ptr` points outside
1910 /// the memory of the guest or host returns `MemoryAccessViolation`.
1911 /// * If called as view function returns `ProhibitedInView`.
1912 ///
1913 /// # Cost
1914 ///
1915 /// `burnt_gas := base + dispatch action base fee + dispatch action per byte fee * num bytes + cost of reading vector from memory
1916 /// + cost of reading u128, method_names and public key from the memory + cost of reading and parsing account name`
1917 /// `used_gas := burnt_gas + exec action base fee + exec action per byte fee * num bytes`
1918 pub fn promise_batch_action_add_key_with_function_call(
1919 &mut self,
1920 promise_idx: u64,
1921 public_key_len: u64,
1922 public_key_ptr: u64,
1923 nonce: u64,
1924 allowance_ptr: u64,
1925 receiver_id_len: u64,
1926 receiver_id_ptr: u64,
1927 method_names_len: u64,
1928 method_names_ptr: u64,
1929 ) -> Result<()> {
1930 self.gas_counter.pay_base(base)?;
1931 if self.context.is_view() {
1932 return Err(HostError::ProhibitedInView {
1933 method_name: "promise_batch_action_add_key_with_function_call".to_string(),
1934 }
1935 .into());
1936 }
1937 let public_key = self.get_public_key(public_key_ptr, public_key_len)?;
1938 let allowance = self.memory.get_u128(&mut self.gas_counter, allowance_ptr)?;
1939 let allowance = if allowance > 0 { Some(allowance) } else { None };
1940 let receiver_id = self.read_and_parse_account_id(receiver_id_ptr, receiver_id_len)?;
1941 let raw_method_names = get_memory_or_register!(self, method_names_ptr, method_names_len)?;
1942 let method_names = split_method_names(&raw_method_names)?;
1943
1944 let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?;
1945
1946 // +1 is to account for null-terminating characters.
1947 let num_bytes = method_names.iter().map(|v| v.len() as u64 + 1).sum::<u64>();
1948 self.pay_action_base(ActionCosts::add_function_call_key_base, sir)?;
1949 self.pay_action_per_byte(ActionCosts::add_function_call_key_byte, num_bytes, sir)?;
1950
1951 self.ext.append_action_add_key_with_function_call(
1952 receipt_idx,
1953 public_key.decode()?,
1954 nonce,
1955 allowance,
1956 receiver_id,
1957 method_names,
1958 )?;
1959 Ok(())
1960 }
1961
1962 /// Appends `DeleteKey` action to the batch of actions for the given promise pointed by
1963 /// `promise_idx`.
1964 ///
1965 /// # Errors
1966 ///
1967 /// * If `promise_idx` does not correspond to an existing promise returns `InvalidPromiseIndex`.
1968 /// * If the promise pointed by the `promise_idx` is an ephemeral promise created by
1969 /// `promise_and` returns `CannotAppendActionToJointPromise`.
1970 /// * If the given public key is not a valid (e.g. wrong length) returns `InvalidPublicKey`.
1971 /// * If `public_key_len + public_key_ptr` points outside the memory of the guest or host
1972 /// returns `MemoryAccessViolation`.
1973 /// * If called as view function returns `ProhibitedInView`.
1974 ///
1975 /// # Cost
1976 ///
1977 /// `burnt_gas := base + dispatch action base fee + dispatch action per byte fee * num bytes + cost of reading public key from memory `
1978 /// `used_gas := burnt_gas + exec action base fee + exec action per byte fee * num bytes`
1979 pub fn promise_batch_action_delete_key(
1980 &mut self,
1981 promise_idx: u64,
1982 public_key_len: u64,
1983 public_key_ptr: u64,
1984 ) -> Result<()> {
1985 self.gas_counter.pay_base(base)?;
1986 if self.context.is_view() {
1987 return Err(HostError::ProhibitedInView {
1988 method_name: "promise_batch_action_delete_key".to_string(),
1989 }
1990 .into());
1991 }
1992 let public_key = self.get_public_key(public_key_ptr, public_key_len)?;
1993 let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?;
1994 self.pay_action_base(ActionCosts::delete_key, sir)?;
1995 self.ext.append_action_delete_key(receipt_idx, public_key.decode()?);
1996 Ok(())
1997 }
1998
1999 /// Appends `DeleteAccount` action to the batch of actions for the given promise pointed by
2000 /// `promise_idx`.
2001 ///
2002 /// # Errors
2003 ///
2004 /// * If `promise_idx` does not correspond to an existing promise returns `InvalidPromiseIndex`.
2005 /// * If the promise pointed by the `promise_idx` is an ephemeral promise created by
2006 /// `promise_and` returns `CannotAppendActionToJointPromise`.
2007 /// * If `beneficiary_id_len + beneficiary_id_ptr` points outside the memory of the guest or
2008 /// host returns `MemoryAccessViolation`.
2009 /// * If called as view function returns `ProhibitedInView`.
2010 ///
2011 /// # Cost
2012 ///
2013 /// `burnt_gas := base + dispatch action base fee + dispatch action per byte fee * num bytes + cost of reading and parsing account id from memory `
2014 /// `used_gas := burnt_gas + exec action base fee + exec action per byte fee * num bytes + fees for transferring funds to the beneficiary`
2015 pub fn promise_batch_action_delete_account(
2016 &mut self,
2017 promise_idx: u64,
2018 beneficiary_id_len: u64,
2019 beneficiary_id_ptr: u64,
2020 ) -> Result<()> {
2021 self.gas_counter.pay_base(base)?;
2022 if self.context.is_view() {
2023 return Err(HostError::ProhibitedInView {
2024 method_name: "promise_batch_action_delete_account".to_string(),
2025 }
2026 .into());
2027 }
2028 let beneficiary_id =
2029 self.read_and_parse_account_id(beneficiary_id_ptr, beneficiary_id_len)?;
2030
2031 let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?;
2032 self.pay_action_base(ActionCosts::delete_account, sir)?;
2033
2034 self.ext.append_action_delete_account(receipt_idx, beneficiary_id)?;
2035 Ok(())
2036 }
2037
2038 /// If the current function is invoked by a callback we can access the execution results of the
2039 /// promises that caused the callback. This function returns the number of complete and
2040 /// incomplete callbacks.
2041 ///
2042 /// Note, we are only going to have incomplete callbacks once we have promise_or combinator.
2043 ///
2044 ///
2045 /// * If there is only one callback returns `1`;
2046 /// * If there are multiple callbacks (e.g. created through `promise_and`) returns their number;
2047 /// * If the function was called not through the callback returns `0`.
2048 ///
2049 /// # Cost
2050 ///
2051 /// `base`
2052 pub fn promise_results_count(&mut self) -> Result<u64> {
2053 self.gas_counter.pay_base(base)?;
2054 if self.context.is_view() {
2055 return Err(HostError::ProhibitedInView {
2056 method_name: "promise_results_count".to_string(),
2057 }
2058 .into());
2059 }
2060 Ok(self.promise_results.len() as _)
2061 }
2062
2063 /// If the current function is invoked by a callback we can access the execution results of the
2064 /// promises that caused the callback. This function returns the result in blob format and
2065 /// places it into the register.
2066 ///
2067 /// * If promise result is complete and successful copies its blob into the register;
2068 /// * If promise result is complete and failed or incomplete keeps register unused;
2069 ///
2070 /// # Returns
2071 ///
2072 /// * If promise result is not complete returns `0`;
2073 /// * If promise result is complete and successful returns `1`;
2074 /// * If promise result is complete and failed returns `2`.
2075 ///
2076 /// # Errors
2077 ///
2078 /// * If `result_id` does not correspond to an existing result returns `InvalidPromiseResultIndex`;
2079 /// * If copying the blob exhausts the memory limit it returns `MemoryAccessViolation`.
2080 /// * If called as view function returns `ProhibitedInView`.
2081 ///
2082 /// # Cost
2083 ///
2084 /// `base + cost of writing data into a register`
2085 pub fn promise_result(&mut self, result_idx: u64, register_id: u64) -> Result<u64> {
2086 self.gas_counter.pay_base(base)?;
2087 if self.context.is_view() {
2088 return Err(
2089 HostError::ProhibitedInView { method_name: "promise_result".to_string() }.into()
2090 );
2091 }
2092 match self
2093 .promise_results
2094 .get(result_idx as usize)
2095 .ok_or(HostError::InvalidPromiseResultIndex { result_idx })?
2096 {
2097 PromiseResult::NotReady => Ok(0),
2098 PromiseResult::Successful(data) => {
2099 self.registers.set(
2100 &mut self.gas_counter,
2101 &self.config.limit_config,
2102 register_id,
2103 data.as_slice(),
2104 )?;
2105 Ok(1)
2106 }
2107 PromiseResult::Failed => Ok(2),
2108 }
2109 }
2110
2111 /// When promise `promise_idx` finishes executing its result is considered to be the result of
2112 /// the current function.
2113 ///
2114 /// # Errors
2115 ///
2116 /// * If `promise_idx` does not correspond to an existing promise returns `InvalidPromiseIndex`.
2117 /// * If called as view function returns `ProhibitedInView`.
2118 ///
2119 /// # Cost
2120 ///
2121 /// `base + promise_return`
2122 pub fn promise_return(&mut self, promise_idx: u64) -> Result<()> {
2123 self.gas_counter.pay_base(base)?;
2124 self.gas_counter.pay_base(promise_return)?;
2125 if self.context.is_view() {
2126 return Err(
2127 HostError::ProhibitedInView { method_name: "promise_return".to_string() }.into()
2128 );
2129 }
2130 match self
2131 .promises
2132 .get(promise_idx as usize)
2133 .ok_or(HostError::InvalidPromiseIndex { promise_idx })?
2134 {
2135 Promise::Receipt(receipt_idx) => {
2136 self.return_data = ReturnData::ReceiptIndex(*receipt_idx);
2137 Ok(())
2138 }
2139 Promise::NotReceipt(_) => Err(HostError::CannotReturnJointPromise.into()),
2140 }
2141 }
2142
2143 // #####################
2144 // # Miscellaneous API #
2145 // #####################
2146
2147 /// Sets the blob of data as the return value of the contract.
2148 ///
2149 /// # Errors
2150 ///
2151 /// * If `value_len + value_ptr` exceeds the memory container or points to an unused register it
2152 /// returns `MemoryAccessViolation`.
2153 /// * if the length of the returned data exceeds `max_length_returned_data` returns
2154 /// `ReturnedValueLengthExceeded`.
2155 ///
2156 /// # Cost
2157 /// `base + cost of reading return value from memory or register + dispatch&exec cost per byte of the data sent * num data receivers`
2158 pub fn value_return(&mut self, value_len: u64, value_ptr: u64) -> Result<()> {
2159 self.gas_counter.pay_base(base)?;
2160 let return_val = get_memory_or_register!(self, value_ptr, value_len)?;
2161 let mut burn_gas: Gas = 0;
2162 let num_bytes = return_val.len() as u64;
2163 if num_bytes > self.config.limit_config.max_length_returned_data {
2164 return Err(HostError::ReturnedValueLengthExceeded {
2165 length: num_bytes,
2166 limit: self.config.limit_config.max_length_returned_data,
2167 }
2168 .into());
2169 }
2170 for data_receiver in &self.context.output_data_receivers {
2171 let sir = data_receiver == &self.context.current_account_id;
2172 // We deduct for execution here too, because if we later have an OR combinator
2173 // for promises then we might have some valid data receipts that arrive too late
2174 // to be picked up by the execution that waits on them (because it has started
2175 // after it receives the first data receipt) and then we need to issue a special
2176 // refund in this situation. Which we avoid by just paying for execution of
2177 // data receipt that might not be performed.
2178 // The gas here is considered burnt, cause we'll prepay for it upfront.
2179 burn_gas = burn_gas
2180 .checked_add(
2181 self.fees_config
2182 .fee(ActionCosts::new_data_receipt_byte)
2183 .send_fee(sir)
2184 .checked_add(
2185 self.fees_config.fee(ActionCosts::new_data_receipt_byte).exec_fee(),
2186 )
2187 .ok_or(HostError::IntegerOverflow)?
2188 .checked_mul(num_bytes)
2189 .ok_or(HostError::IntegerOverflow)?,
2190 )
2191 .ok_or(HostError::IntegerOverflow)?;
2192 }
2193 self.gas_counter.pay_action_accumulated(
2194 burn_gas,
2195 burn_gas,
2196 ActionCosts::new_data_receipt_byte,
2197 )?;
2198 self.return_data = ReturnData::Value(return_val.into_owned());
2199 Ok(())
2200 }
2201
2202 /// Terminates the execution of the program with panic `GuestPanic`.
2203 ///
2204 /// # Cost
2205 ///
2206 /// `base`
2207 pub fn panic(&mut self) -> Result<()> {
2208 self.gas_counter.pay_base(base)?;
2209 Err(HostError::GuestPanic { panic_msg: "explicit guest panic".to_string() }.into())
2210 }
2211
2212 /// Guest panics with the UTF-8 encoded string.
2213 /// If `len == u64::MAX` then treats the string as null-terminated with character `'\0'`.
2214 ///
2215 /// # Errors
2216 ///
2217 /// * If string extends outside the memory of the guest with `MemoryAccessViolation`;
2218 /// * If string is not UTF-8 returns `BadUtf8`.
2219 /// * If string is longer than `max_log_len` returns `TotalLogLengthExceeded`.
2220 ///
2221 /// # Cost
2222 /// `base + cost of reading and decoding a utf8 string`
2223 pub fn panic_utf8(&mut self, len: u64, ptr: u64) -> Result<()> {
2224 self.gas_counter.pay_base(base)?;
2225 Err(HostError::GuestPanic { panic_msg: self.get_utf8_string(len, ptr)? }.into())
2226 }
2227
2228 /// Logs the UTF-8 encoded string.
2229 /// If `len == u64::MAX` then treats the string as null-terminated with character `'\0'`.
2230 ///
2231 /// # Errors
2232 ///
2233 /// * If string extends outside the memory of the guest with `MemoryAccessViolation`;
2234 /// * If string is not UTF-8 returns `BadUtf8`.
2235 /// * If number of bytes read + `total_log_length` exceeds the `max_total_log_length` returns
2236 /// `TotalLogLengthExceeded`.
2237 /// * If the total number of logs will exceed the `max_number_logs` returns
2238 /// `NumberOfLogsExceeded`.
2239 ///
2240 /// # Cost
2241 ///
2242 /// `base + log_base + log_byte + num_bytes + utf8 decoding cost`
2243 pub fn log_utf8(&mut self, len: u64, ptr: u64) -> Result<()> {
2244 self.gas_counter.pay_base(base)?;
2245 self.check_can_add_a_log_message()?;
2246 let message = self.get_utf8_string(len, ptr)?;
2247 self.gas_counter.pay_base(log_base)?;
2248 self.gas_counter.pay_per(log_byte, message.len() as u64)?;
2249 self.checked_push_log(message)
2250 }
2251
2252 /// Logs the UTF-16 encoded string. If `len == u64::MAX` then treats the string as
2253 /// null-terminated with two-byte sequence of `0x00 0x00`.
2254 ///
2255 /// # Errors
2256 ///
2257 /// * If string extends outside the memory of the guest with `MemoryAccessViolation`;
2258 /// * If string is not UTF-16 returns `BadUtf16`.
2259 /// * If number of bytes read + `total_log_length` exceeds the `max_total_log_length` returns
2260 /// `TotalLogLengthExceeded`.
2261 /// * If the total number of logs will exceed the `max_number_logs` returns
2262 /// `NumberOfLogsExceeded`.
2263 ///
2264 /// # Cost
2265 ///
2266 /// `base + log_base + log_byte * num_bytes + utf16 decoding cost`
2267 pub fn log_utf16(&mut self, len: u64, ptr: u64) -> Result<()> {
2268 self.gas_counter.pay_base(base)?;
2269 self.check_can_add_a_log_message()?;
2270 let message = self.get_utf16_string(len, ptr)?;
2271 self.gas_counter.pay_base(log_base)?;
2272 // Let's not use `encode_utf16` for gas per byte here, since it's a lot of compute.
2273 self.gas_counter.pay_per(log_byte, message.len() as u64)?;
2274 self.checked_push_log(message)
2275 }
2276
2277 /// Special import kept for compatibility with AssemblyScript contracts. Not called by smart
2278 /// contracts directly, but instead called by the code generated by AssemblyScript.
2279 ///
2280 /// # Errors
2281 ///
2282 /// * If string extends outside the memory of the guest with `MemoryAccessViolation`;
2283 /// * If string is not UTF-8 returns `BadUtf8`.
2284 /// * If number of bytes read + `total_log_length` exceeds the `max_total_log_length` returns
2285 /// `TotalLogLengthExceeded`.
2286 /// * If the total number of logs will exceed the `max_number_logs` returns
2287 /// `NumberOfLogsExceeded`.
2288 ///
2289 /// # Cost
2290 ///
2291 /// `base + log_base + log_byte * num_bytes + utf16 decoding cost`
2292 pub fn abort(&mut self, msg_ptr: u32, filename_ptr: u32, line: u32, col: u32) -> Result<()> {
2293 self.gas_counter.pay_base(base)?;
2294 if msg_ptr < 4 || filename_ptr < 4 {
2295 return Err(HostError::BadUTF16.into());
2296 }
2297 self.check_can_add_a_log_message()?;
2298
2299 // Underflow checked above.
2300 let msg_len = self.memory.get_u32(&mut self.gas_counter, (msg_ptr - 4) as u64)?;
2301 let filename_len = self.memory.get_u32(&mut self.gas_counter, (filename_ptr - 4) as u64)?;
2302
2303 let msg = self.get_utf16_string(msg_len as u64, msg_ptr as u64)?;
2304 let filename = self.get_utf16_string(filename_len as u64, filename_ptr as u64)?;
2305
2306 let message = format!("{}, filename: \"{}\" line: {} col: {}", msg, filename, line, col);
2307 self.gas_counter.pay_base(log_base)?;
2308 self.gas_counter.pay_per(log_byte, message.as_bytes().len() as u64)?;
2309 self.checked_push_log(format!("ABORT: {}", message))?;
2310
2311 Err(HostError::GuestPanic { panic_msg: message }.into())
2312 }
2313
2314 // ###############
2315 // # Storage API #
2316 // ###############
2317
2318 /// Reads account id from the given location in memory.
2319 ///
2320 /// # Errors
2321 ///
2322 /// * If account is not UTF-8 encoded then returns `BadUtf8`;
2323 /// * If account is not valid then returns `InvalidAccountId`.
2324 ///
2325 /// # Cost
2326 ///
2327 /// This is a helper function that encapsulates the following costs:
2328 /// cost of reading buffer from register or memory,
2329 /// `utf8_decoding_base + utf8_decoding_byte * num_bytes`.
2330 fn read_and_parse_account_id(&mut self, ptr: u64, len: u64) -> Result<AccountId> {
2331 let buf = get_memory_or_register!(self, ptr, len)?;
2332 self.gas_counter.pay_base(utf8_decoding_base)?;
2333 self.gas_counter.pay_per(utf8_decoding_byte, buf.len() as u64)?;
2334
2335 // We return an illegally constructed AccountId here for the sake of ensuring
2336 // backwards compatibility. For paths previously involving validation, like receipts
2337 // we retain validation further down the line in node-runtime/verifier.rs#fn(validate_receipt)
2338 // mimicing previous behaviour.
2339 let account_id = String::from_utf8(buf.into_owned())
2340 .map(
2341 #[allow(deprecated)]
2342 AccountId::new_unvalidated,
2343 )
2344 .map_err(|_| HostError::BadUTF8)?;
2345 Ok(account_id)
2346 }
2347
2348 /// Writes key-value into storage.
2349 /// * If key is not in use it inserts the key-value pair and does not modify the register. Returns `0`;
2350 /// * If key is in use it inserts the key-value and copies the old value into the `register_id`. Returns `1`.
2351 ///
2352 /// # Errors
2353 ///
2354 /// * If `key_len + key_ptr` or `value_len + value_ptr` exceeds the memory container or points
2355 /// to an unused register it returns `MemoryAccessViolation`;
2356 /// * If returning the preempted value into the registers exceed the memory container it returns
2357 /// `MemoryAccessViolation`.
2358 /// * If the length of the key exceeds `max_length_storage_key` returns `KeyLengthExceeded`.
2359 /// * If the length of the value exceeds `max_length_storage_value` returns
2360 /// `ValueLengthExceeded`.
2361 /// * If called as view function returns `ProhibitedInView``.
2362 ///
2363 /// # Cost
2364 ///
2365 /// `base + storage_write_base + storage_write_key_byte * num_key_bytes + storage_write_value_byte * num_value_bytes
2366 /// + get_vec_from_memory_or_register_cost x 2`.
2367 ///
2368 /// If a value was evicted it costs additional `storage_write_value_evicted_byte * num_evicted_bytes + internal_write_register_cost`.
2369 pub fn storage_write(
2370 &mut self,
2371 key_len: u64,
2372 key_ptr: u64,
2373 value_len: u64,
2374 value_ptr: u64,
2375 register_id: u64,
2376 ) -> Result<u64> {
2377 self.gas_counter.pay_base(base)?;
2378 if self.context.is_view() {
2379 return Err(
2380 HostError::ProhibitedInView { method_name: "storage_write".to_string() }.into()
2381 );
2382 }
2383 self.gas_counter.pay_base(storage_write_base)?;
2384 let key = get_memory_or_register!(self, key_ptr, key_len)?;
2385 if key.len() as u64 > self.config.limit_config.max_length_storage_key {
2386 return Err(HostError::KeyLengthExceeded {
2387 length: key.len() as u64,
2388 limit: self.config.limit_config.max_length_storage_key,
2389 }
2390 .into());
2391 }
2392 let value = get_memory_or_register!(self, value_ptr, value_len)?;
2393 if value.len() as u64 > self.config.limit_config.max_length_storage_value {
2394 return Err(HostError::ValueLengthExceeded {
2395 length: value.len() as u64,
2396 limit: self.config.limit_config.max_length_storage_value,
2397 }
2398 .into());
2399 }
2400 self.gas_counter.pay_per(storage_write_key_byte, key.len() as u64)?;
2401 self.gas_counter.pay_per(storage_write_value_byte, value.len() as u64)?;
2402 let nodes_before = self.ext.get_trie_nodes_count();
2403 // For storage write, we need to first perform a read on the key to calculate the TTN cost.
2404 // This storage_get must be performed through trie instead of through FlatStorage
2405 let evicted_ptr = self.ext.storage_get(&key, StorageGetMode::Trie)?;
2406 let evicted =
2407 Self::deref_value(&mut self.gas_counter, storage_write_evicted_byte, evicted_ptr)?;
2408 let nodes_delta = self
2409 .ext
2410 .get_trie_nodes_count()
2411 .checked_sub(&nodes_before)
2412 .ok_or(InconsistentStateError::IntegerOverflow)?;
2413
2414 #[cfg(feature = "io_trace")]
2415 tracing::trace!(
2416 target = "io_tracer",
2417 storage_op = "write",
2418 key = base64(&key),
2419 size = value_len,
2420 evicted_len = evicted.as_ref().map(Vec::len),
2421 tn_mem_reads = nodes_delta.mem_reads,
2422 tn_db_reads = nodes_delta.db_reads,
2423 );
2424
2425 self.gas_counter.add_trie_fees(&nodes_delta)?;
2426 self.ext.storage_set(&key, &value)?;
2427 let storage_config = &self.fees_config.storage_usage_config;
2428 match evicted {
2429 Some(old_value) => {
2430 // Inner value can't overflow, because the value length is limited.
2431 self.current_storage_usage = self
2432 .current_storage_usage
2433 .checked_sub(old_value.len() as u64)
2434 .ok_or(InconsistentStateError::IntegerOverflow)?;
2435 // Inner value can't overflow, because the value length is limited.
2436 self.current_storage_usage = self
2437 .current_storage_usage
2438 .checked_add(value.len() as u64)
2439 .ok_or(InconsistentStateError::IntegerOverflow)?;
2440 self.registers.set(
2441 &mut self.gas_counter,
2442 &self.config.limit_config,
2443 register_id,
2444 old_value,
2445 )?;
2446 Ok(1)
2447 }
2448 None => {
2449 // Inner value can't overflow, because the key/value length is limited.
2450 self.current_storage_usage = self
2451 .current_storage_usage
2452 .checked_add(
2453 value.len() as u64
2454 + key.len() as u64
2455 + storage_config.num_extra_bytes_record,
2456 )
2457 .ok_or(InconsistentStateError::IntegerOverflow)?;
2458 Ok(0)
2459 }
2460 }
2461 }
2462
2463 fn deref_value<'s>(
2464 gas_counter: &mut GasCounter,
2465 cost_per_byte: ExtCosts,
2466 value_ptr: Option<Box<dyn ValuePtr + 's>>,
2467 ) -> Result<Option<Vec<u8>>> {
2468 match value_ptr {
2469 Some(value_ptr) => {
2470 gas_counter.pay_per(cost_per_byte, value_ptr.len() as u64)?;
2471 value_ptr.deref().map(Some)
2472 }
2473 None => Ok(None),
2474 }
2475 }
2476
2477 /// Reads the value stored under the given key.
2478 /// * If key is used copies the content of the value into the `register_id`, even if the content
2479 /// is zero bytes. Returns `1`;
2480 /// * If key is not present then does not modify the register. Returns `0`;
2481 ///
2482 /// # Errors
2483 ///
2484 /// * If `key_len + key_ptr` exceeds the memory container or points to an unused register it
2485 /// returns `MemoryAccessViolation`;
2486 /// * If returning the preempted value into the registers exceed the memory container it returns
2487 /// `MemoryAccessViolation`.
2488 /// * If the length of the key exceeds `max_length_storage_key` returns `KeyLengthExceeded`.
2489 ///
2490 /// # Cost
2491 ///
2492 /// `base + storage_read_base + storage_read_key_byte * num_key_bytes + storage_read_value_byte + num_value_bytes
2493 /// cost to read key from register + cost to write value into register`.
2494 pub fn storage_read(&mut self, key_len: u64, key_ptr: u64, register_id: u64) -> Result<u64> {
2495 self.gas_counter.pay_base(base)?;
2496 self.gas_counter.pay_base(storage_read_base)?;
2497 let key = get_memory_or_register!(self, key_ptr, key_len)?;
2498 if key.len() as u64 > self.config.limit_config.max_length_storage_key {
2499 return Err(HostError::KeyLengthExceeded {
2500 length: key.len() as u64,
2501 limit: self.config.limit_config.max_length_storage_key,
2502 }
2503 .into());
2504 }
2505 self.gas_counter.pay_per(storage_read_key_byte, key.len() as u64)?;
2506 let nodes_before = self.ext.get_trie_nodes_count();
2507 let read = self.ext.storage_get(&key, self.config.storage_get_mode);
2508 let nodes_delta = self
2509 .ext
2510 .get_trie_nodes_count()
2511 .checked_sub(&nodes_before)
2512 .ok_or(InconsistentStateError::IntegerOverflow)?;
2513 self.gas_counter.add_trie_fees(&nodes_delta)?;
2514 let read = Self::deref_value(&mut self.gas_counter, storage_read_value_byte, read?)?;
2515
2516 #[cfg(feature = "io_trace")]
2517 tracing::trace!(
2518 target = "io_tracer",
2519 storage_op = "read",
2520 key = base64(&key),
2521 size = read.as_ref().map(Vec::len),
2522 tn_db_reads = nodes_delta.db_reads,
2523 tn_mem_reads = nodes_delta.mem_reads,
2524 );
2525
2526 match read {
2527 Some(value) => {
2528 self.registers.set(
2529 &mut self.gas_counter,
2530 &self.config.limit_config,
2531 register_id,
2532 value,
2533 )?;
2534 Ok(1)
2535 }
2536 None => Ok(0),
2537 }
2538 }
2539
2540 /// Removes the value stored under the given key.
2541 /// * If key is used, removes the key-value from the trie and copies the content of the value
2542 /// into the `register_id`, even if the content is zero bytes. Returns `1`;
2543 /// * If key is not present then does not modify the register. Returns `0`.
2544 ///
2545 /// # Errors
2546 ///
2547 /// * If `key_len + key_ptr` exceeds the memory container or points to an unused register it
2548 /// returns `MemoryAccessViolation`;
2549 /// * If the registers exceed the memory limit returns `MemoryAccessViolation`;
2550 /// * If returning the preempted value into the registers exceed the memory container it returns
2551 /// `MemoryAccessViolation`.
2552 /// * If the length of the key exceeds `max_length_storage_key` returns `KeyLengthExceeded`.
2553 /// * If called as view function returns `ProhibitedInView``.
2554 ///
2555 /// # Cost
2556 ///
2557 /// `base + storage_remove_base + storage_remove_key_byte * num_key_bytes + storage_remove_ret_value_byte * num_value_bytes
2558 /// + cost to read the key + cost to write the value`.
2559 pub fn storage_remove(&mut self, key_len: u64, key_ptr: u64, register_id: u64) -> Result<u64> {
2560 self.gas_counter.pay_base(base)?;
2561 if self.context.is_view() {
2562 return Err(
2563 HostError::ProhibitedInView { method_name: "storage_remove".to_string() }.into()
2564 );
2565 }
2566 self.gas_counter.pay_base(storage_remove_base)?;
2567 let key = get_memory_or_register!(self, key_ptr, key_len)?;
2568 if key.len() as u64 > self.config.limit_config.max_length_storage_key {
2569 return Err(HostError::KeyLengthExceeded {
2570 length: key.len() as u64,
2571 limit: self.config.limit_config.max_length_storage_key,
2572 }
2573 .into());
2574 }
2575 self.gas_counter.pay_per(storage_remove_key_byte, key.len() as u64)?;
2576 let nodes_before = self.ext.get_trie_nodes_count();
2577 // To delete a key, we need to first perform a read on the key to calculate the TTN cost.
2578 // This storage_get must be performed through trie instead of through FlatStorage
2579 let removed_ptr = self.ext.storage_get(&key, StorageGetMode::Trie)?;
2580 let removed =
2581 Self::deref_value(&mut self.gas_counter, storage_remove_ret_value_byte, removed_ptr)?;
2582
2583 self.ext.storage_remove(&key)?;
2584 let nodes_delta = self
2585 .ext
2586 .get_trie_nodes_count()
2587 .checked_sub(&nodes_before)
2588 .ok_or(InconsistentStateError::IntegerOverflow)?;
2589
2590 #[cfg(feature = "io_trace")]
2591 tracing::trace!(
2592 target = "io_tracer",
2593 storage_op = "remove",
2594 key = base64(&key),
2595 evicted_len = removed.as_ref().map(Vec::len),
2596 tn_mem_reads = nodes_delta.mem_reads,
2597 tn_db_reads = nodes_delta.db_reads,
2598 );
2599
2600 self.gas_counter.add_trie_fees(&nodes_delta)?;
2601 let storage_config = &self.fees_config.storage_usage_config;
2602 match removed {
2603 Some(value) => {
2604 // Inner value can't overflow, because the key/value length is limited.
2605 self.current_storage_usage = self
2606 .current_storage_usage
2607 .checked_sub(
2608 value.len() as u64
2609 + key.len() as u64
2610 + storage_config.num_extra_bytes_record,
2611 )
2612 .ok_or(InconsistentStateError::IntegerOverflow)?;
2613 self.registers.set(
2614 &mut self.gas_counter,
2615 &self.config.limit_config,
2616 register_id,
2617 value,
2618 )?;
2619 Ok(1)
2620 }
2621 None => Ok(0),
2622 }
2623 }
2624
2625 /// Checks if there is a key-value pair.
2626 /// * If key is used returns `1`, even if the value is zero bytes;
2627 /// * Otherwise returns `0`.
2628 ///
2629 /// # Errors
2630 ///
2631 /// * If `key_len + key_ptr` exceeds the memory container it returns `MemoryAccessViolation`.
2632 /// * If the length of the key exceeds `max_length_storage_key` returns `KeyLengthExceeded`.
2633 ///
2634 /// # Cost
2635 ///
2636 /// `base + storage_has_key_base + storage_has_key_byte * num_bytes + cost of reading key`
2637 pub fn storage_has_key(&mut self, key_len: u64, key_ptr: u64) -> Result<u64> {
2638 self.gas_counter.pay_base(base)?;
2639 self.gas_counter.pay_base(storage_has_key_base)?;
2640 let key = get_memory_or_register!(self, key_ptr, key_len)?;
2641 if key.len() as u64 > self.config.limit_config.max_length_storage_key {
2642 return Err(HostError::KeyLengthExceeded {
2643 length: key.len() as u64,
2644 limit: self.config.limit_config.max_length_storage_key,
2645 }
2646 .into());
2647 }
2648 self.gas_counter.pay_per(storage_has_key_byte, key.len() as u64)?;
2649 let nodes_before = self.ext.get_trie_nodes_count();
2650 let res = self.ext.storage_has_key(&key, self.config.storage_get_mode);
2651 let nodes_delta = self
2652 .ext
2653 .get_trie_nodes_count()
2654 .checked_sub(&nodes_before)
2655 .ok_or(InconsistentStateError::IntegerOverflow)?;
2656
2657 #[cfg(feature = "io_trace")]
2658 tracing::trace!(
2659 target = "io_tracer",
2660 storage_op = "exists",
2661 key = base64(&key),
2662 tn_mem_reads = nodes_delta.mem_reads,
2663 tn_db_reads = nodes_delta.db_reads,
2664 );
2665
2666 self.gas_counter.add_trie_fees(&nodes_delta)?;
2667 Ok(res? as u64)
2668 }
2669
2670 /// Debug print given utf-8 string to node log. It's only available in Sandbox node
2671 ///
2672 /// # Errors
2673 ///
2674 /// * If string is not UTF-8 returns `BadUtf8`
2675 /// * If the log is over available memory in wasm runner, returns `MemoryAccessViolation`
2676 ///
2677 /// # Cost
2678 ///
2679 /// 0
2680 #[cfg(feature = "sandbox")]
2681 pub fn sandbox_debug_log(&mut self, len: u64, ptr: u64) -> Result<()> {
2682 let message = self.sandbox_get_utf8_string(len, ptr)?;
2683 tracing::debug!(target: "sandbox", message = &message[..]);
2684 Ok(())
2685 }
2686
2687 /// DEPRECATED
2688 /// Creates an iterator object inside the host. Returns the identifier that uniquely
2689 /// differentiates the given iterator from other iterators that can be simultaneously created.
2690 /// * It iterates over the keys that have the provided prefix. The order of iteration is defined
2691 /// by the lexicographic order of the bytes in the keys;
2692 /// * If there are no keys, it creates an empty iterator, see below on empty iterators.
2693 ///
2694 /// # Errors
2695 ///
2696 /// * If `prefix_len + prefix_ptr` exceeds the memory container it returns
2697 /// `MemoryAccessViolation`.
2698 /// * If the length of the prefix exceeds `max_length_storage_key` returns `KeyLengthExceeded`.
2699 ///
2700 /// # Cost
2701 ///
2702 /// `base + storage_iter_create_prefix_base + storage_iter_create_key_byte * num_prefix_bytes
2703 /// cost of reading the prefix`.
2704 pub fn storage_iter_prefix(&mut self, _prefix_len: u64, _prefix_ptr: u64) -> Result<u64> {
2705 Err(VMLogicError::HostError(HostError::Deprecated {
2706 method_name: "storage_iter_prefix".to_string(),
2707 }))
2708 }
2709
2710 /// DEPRECATED
2711 /// Iterates over all key-values such that keys are between `start` and `end`, where `start` is
2712 /// inclusive and `end` is exclusive. Unless lexicographically `start < end`, it creates an
2713 /// empty iterator. Note, this definition allows for `start` or `end` keys to not actually exist
2714 /// on the given trie.
2715 ///
2716 /// # Errors
2717 ///
2718 /// * If `start_len + start_ptr` or `end_len + end_ptr` exceeds the memory container or points to
2719 /// an unused register it returns `MemoryAccessViolation`.
2720 /// * If the length of the `start` exceeds `max_length_storage_key` returns `KeyLengthExceeded`.
2721 /// * If the length of the `end` exceeds `max_length_storage_key` returns `KeyLengthExceeded`.
2722 ///
2723 /// # Cost
2724 ///
2725 /// `base + storage_iter_create_range_base + storage_iter_create_from_byte * num_from_bytes
2726 /// + storage_iter_create_to_byte * num_to_bytes + reading from prefix + reading to prefix`.
2727 pub fn storage_iter_range(
2728 &mut self,
2729 _start_len: u64,
2730 _start_ptr: u64,
2731 _end_len: u64,
2732 _end_ptr: u64,
2733 ) -> Result<u64> {
2734 Err(VMLogicError::HostError(HostError::Deprecated {
2735 method_name: "storage_iter_range".to_string(),
2736 }))
2737 }
2738
2739 /// DEPRECATED
2740 /// Advances iterator and saves the next key and value in the register.
2741 /// * If iterator is not empty (after calling next it points to a key-value), copies the key
2742 /// into `key_register_id` and value into `value_register_id` and returns `1`;
2743 /// * If iterator is empty returns `0`;
2744 /// This allows us to iterate over the keys that have zero bytes stored in values.
2745 ///
2746 /// # Errors
2747 ///
2748 /// * If `key_register_id == value_register_id` returns `MemoryAccessViolation`;
2749 /// * If the registers exceed the memory limit returns `MemoryAccessViolation`;
2750 /// * If `iterator_id` does not correspond to an existing iterator returns `InvalidIteratorId`;
2751 /// * If between the creation of the iterator and calling `storage_iter_next` the range over
2752 /// which it iterates was modified returns `IteratorWasInvalidated`. Specifically, if
2753 /// `storage_write` or `storage_remove` was invoked on the key key such that:
2754 /// * in case of `storage_iter_prefix`. `key` has the given prefix and:
2755 /// * Iterator was not called next yet.
2756 /// * `next` was already called on the iterator and it is currently pointing at the `key`
2757 /// `curr` such that `curr <= key`.
2758 /// * in case of `storage_iter_range`. `start<=key<end` and:
2759 /// * Iterator was not called `next` yet.
2760 /// * `next` was already called on the iterator and it is currently pointing at the key
2761 /// `curr` such that `curr<=key<end`.
2762 ///
2763 /// # Cost
2764 ///
2765 /// `base + storage_iter_next_base + storage_iter_next_key_byte * num_key_bytes + storage_iter_next_value_byte * num_value_bytes
2766 /// + writing key to register + writing value to register`.
2767 pub fn storage_iter_next(
2768 &mut self,
2769 _iterator_id: u64,
2770 _key_register_id: u64,
2771 _value_register_id: u64,
2772 ) -> Result<u64> {
2773 Err(VMLogicError::HostError(HostError::Deprecated {
2774 method_name: "storage_iter_next".to_string(),
2775 }))
2776 }
2777
2778 /// Computes the outcome of the execution.
2779 ///
2780 /// If `FunctionCallWeight` protocol feature (127) is enabled, unused gas will be
2781 /// distributed to functions that specify a gas weight. If there are no functions with
2782 /// a gas weight, the outcome will contain unused gas as usual.
2783 pub fn compute_outcome(self) -> VMOutcome {
2784 let burnt_gas = self.gas_counter.burnt_gas();
2785 let used_gas = self.gas_counter.used_gas();
2786
2787 let mut profile = self.gas_counter.profile_data();
2788 profile.compute_wasm_instruction_cost(burnt_gas);
2789 let compute_usage = profile.total_compute_usage(&self.config.ext_costs);
2790
2791 VMOutcome {
2792 balance: self.current_account_balance,
2793 storage_usage: self.current_storage_usage,
2794 return_data: self.return_data,
2795 burnt_gas,
2796 used_gas,
2797 compute_usage,
2798 logs: self.logs,
2799 profile,
2800 aborted: None,
2801 }
2802 }
2803
2804 /// Add a cost for loading the contract code in the VM.
2805 ///
2806 /// This cost does not consider the structure of the contract code, only the
2807 /// size. This is currently the only loading fee. A fee that takes the code
2808 /// structure into consideration could be added. But since that would have
2809 /// to happen after loading, we cannot pre-charge it. This is the main
2810 /// motivation to (only) have this simple fee.
2811 pub fn add_contract_loading_fee(&mut self, code_len: u64) -> Result<()> {
2812 self.gas_counter.pay_per(contract_loading_bytes, code_len)?;
2813 self.gas_counter.pay_base(contract_loading_base)
2814 }
2815
2816 /// Gets pointer to the fast gas counter.
2817 pub fn gas_counter_pointer(&mut self) -> *mut FastGasCounter {
2818 self.gas_counter.gas_counter_raw_ptr()
2819 }
2820
2821 /// Properly handles gas limit exceeded error.
2822 pub fn process_gas_limit(&mut self) -> HostError {
2823 let new_burn_gas = self.gas_counter.burnt_gas();
2824 let new_used_gas = self.gas_counter.used_gas();
2825 self.gas_counter.process_gas_limit(new_burn_gas, new_used_gas)
2826 }
2827
2828 /// A helper function to pay base cost gas fee for batching an action.
2829 pub fn pay_action_base(&mut self, action: ActionCosts, sir: bool) -> Result<()> {
2830 let base_fee = self.fees_config.fee(action);
2831 let burn_gas = base_fee.send_fee(sir);
2832 let use_gas =
2833 burn_gas.checked_add(base_fee.exec_fee()).ok_or(HostError::IntegerOverflow)?;
2834 self.gas_counter.pay_action_accumulated(burn_gas, use_gas, action)
2835 }
2836
2837 /// A helper function to pay per byte gas fee for batching an action.
2838 pub fn pay_action_per_byte(
2839 &mut self,
2840 action: ActionCosts,
2841 num_bytes: u64,
2842 sir: bool,
2843 ) -> Result<()> {
2844 let per_byte_fee = self.fees_config.fee(action);
2845 let burn_gas =
2846 num_bytes.checked_mul(per_byte_fee.send_fee(sir)).ok_or(HostError::IntegerOverflow)?;
2847 let use_gas = burn_gas
2848 .checked_add(
2849 num_bytes.checked_mul(per_byte_fee.exec_fee()).ok_or(HostError::IntegerOverflow)?,
2850 )
2851 .ok_or(HostError::IntegerOverflow)?;
2852 self.gas_counter.pay_action_accumulated(burn_gas, use_gas, action)
2853 }
2854
2855 /// VM independent setup before loading the executable.
2856 ///
2857 /// Does VM independent checks that happen after the instantiation of
2858 /// VMLogic but before loading the executable. This includes pre-charging gas
2859 /// costs for loading the executable, which depends on the size of the WASM code.
2860 pub fn before_loading_executable(
2861 &mut self,
2862 method_name: &str,
2863 wasm_code_bytes: usize,
2864 ) -> std::result::Result<(), super::errors::FunctionCallError> {
2865 if method_name.is_empty() {
2866 let error = super::errors::FunctionCallError::MethodResolveError(
2867 super::errors::MethodResolveError::MethodEmptyName,
2868 );
2869 return Err(error);
2870 }
2871 if self.config.fix_contract_loading_cost {
2872 if self.add_contract_loading_fee(wasm_code_bytes as u64).is_err() {
2873 let error =
2874 super::errors::FunctionCallError::HostError(super::HostError::GasExceeded);
2875 return Err(error);
2876 }
2877 }
2878 Ok(())
2879 }
2880
2881 /// Legacy code to preserve old gas charging behaviour in old protocol versions.
2882 pub fn after_loading_executable(
2883 &mut self,
2884 wasm_code_bytes: usize,
2885 ) -> std::result::Result<(), super::errors::FunctionCallError> {
2886 if !self.config.fix_contract_loading_cost {
2887 if self.add_contract_loading_fee(wasm_code_bytes as u64).is_err() {
2888 return Err(super::errors::FunctionCallError::HostError(
2889 super::HostError::GasExceeded,
2890 ));
2891 }
2892 }
2893 Ok(())
2894 }
2895}
2896
2897#[derive(PartialEq)]
2898pub struct VMOutcome {
2899 pub balance: Balance,
2900 pub storage_usage: StorageUsage,
2901 pub return_data: ReturnData,
2902 pub burnt_gas: Gas,
2903 pub used_gas: Gas,
2904 pub compute_usage: Compute,
2905 pub logs: Vec<String>,
2906 /// Data collected from making a contract call
2907 pub profile: ProfileDataV3,
2908 pub aborted: Option<FunctionCallError>,
2909}
2910
2911impl VMOutcome {
2912 /// Consumes the `VMLogic` object and computes the final outcome with the
2913 /// given error that stopped execution from finishing successfully.
2914 pub fn abort(logic: VMLogic, error: FunctionCallError) -> VMOutcome {
2915 let mut outcome = logic.compute_outcome();
2916 outcome.aborted = Some(error);
2917 outcome
2918 }
2919
2920 /// Consumes the `VMLogic` object and computes the final outcome for a
2921 /// successful execution.
2922 pub fn ok(logic: VMLogic) -> VMOutcome {
2923 logic.compute_outcome()
2924 }
2925
2926 /// Creates an outcome with a no-op outcome.
2927 pub fn nop_outcome(error: FunctionCallError) -> VMOutcome {
2928 VMOutcome {
2929 // Note: Balance and storage fields are ignored on a failed outcome.
2930 balance: 0,
2931 storage_usage: 0,
2932 // Note: Fields below are added or merged when processing the
2933 // outcome. With 0 or the empty set, those are no-ops.
2934 return_data: ReturnData::None,
2935 burnt_gas: 0,
2936 used_gas: 0,
2937 compute_usage: 0,
2938 logs: Vec::new(),
2939 profile: ProfileDataV3::default(),
2940 aborted: Some(error),
2941 }
2942 }
2943
2944 /// Like `Self::abort()` but without feature `FixContractLoadingCost` it
2945 /// will return a NOP outcome. This is used for backwards-compatibility only.
2946 pub fn abort_but_nop_outcome_in_old_protocol(
2947 logic: VMLogic,
2948 error: FunctionCallError,
2949 ) -> VMOutcome {
2950 if logic.config.fix_contract_loading_cost {
2951 Self::abort(logic, error)
2952 } else {
2953 Self::nop_outcome(error)
2954 }
2955 }
2956}
2957
2958impl std::fmt::Debug for VMOutcome {
2959 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2960 let return_data_str = match &self.return_data {
2961 ReturnData::None => "None".to_string(),
2962 ReturnData::ReceiptIndex(_) => "Receipt".to_string(),
2963 ReturnData::Value(v) => format!("Value [{} bytes]", v.len()),
2964 };
2965 write!(
2966 f,
2967 "VMOutcome: balance {} storage_usage {} return data {} burnt gas {} used gas {}",
2968 self.balance, self.storage_usage, return_data_str, self.burnt_gas, self.used_gas
2969 )?;
2970 if let Some(err) = &self.aborted {
2971 write!(f, " failed with {err}")?;
2972 }
2973 Ok(())
2974 }
2975}