use super::context::VMContext;
use super::dependencies::{External, MemSlice, MemoryLike};
use super::errors::{FunctionCallError, InconsistentStateError};
use super::gas_counter::{FastGasCounter, GasCounter};
use super::types::{PromiseIndex, PromiseResult, ReceiptIndex, ReturnData};
use super::utils::split_method_names;
use super::ValuePtr;
use super::{HostError, VMLogicError};
use crate::ProfileDataV3;
use std::mem::size_of;
use unc_crypto::Secp256K1Signature;
use unc_parameters::vm::{Config, StorageGetMode};
use unc_parameters::{
transfer_exec_fee, transfer_send_fee, ActionCosts, ExtCosts, RuntimeFeesConfig,
};
use unc_primitives_core::config::ViewConfig;
use unc_primitives_core::types::{
AccountId, Balance, Compute, EpochHeight, Gas, GasWeight, StorageUsage,
};
use ExtCosts::*;
pub type Result<T, E = VMLogicError> = ::std::result::Result<T, E>;
#[cfg(feature = "io_trace")]
fn base64(s: &[u8]) -> String {
use base64::Engine;
base64::engine::general_purpose::STANDARD.encode(s)
}
pub struct VMLogic<'a> {
ext: &'a mut dyn External,
context: VMContext,
pub(crate) config: &'a Config,
fees_config: &'a RuntimeFeesConfig,
promise_results: &'a [PromiseResult],
memory: super::vmstate::Memory<'a>,
current_account_balance: Balance,
current_account_locked_balance: Balance,
current_storage_usage: StorageUsage,
gas_counter: GasCounter,
return_data: ReturnData,
logs: Vec<String>,
registers: super::vmstate::Registers,
promises: Vec<Promise>,
total_log_length: u64,
remaining_stack: u64,
}
#[derive(Debug)]
enum Promise {
Receipt(ReceiptIndex),
NotReceipt(Vec<ReceiptIndex>),
}
macro_rules! get_memory_or_register {
($logic:expr, $offset:expr, $len:expr) => {
super::vmstate::get_memory_or_register(
&mut $logic.gas_counter,
&$logic.memory,
&$logic.registers,
$offset,
$len,
)
};
}
struct PublicKeyBuffer(Result<unc_crypto::PublicKey, ()>);
impl PublicKeyBuffer {
fn new(data: &[u8]) -> Self {
Self(borsh::BorshDeserialize::try_from_slice(data).map_err(|_| ()))
}
fn decode(self) -> Result<unc_crypto::PublicKey> {
self.0.map_err(|_| HostError::InvalidPublicKey.into())
}
}
impl<'a> VMLogic<'a> {
pub fn new(
ext: &'a mut dyn External,
context: VMContext,
config: &'a Config,
fees_config: &'a RuntimeFeesConfig,
promise_results: &'a [PromiseResult],
memory: &'a mut dyn MemoryLike,
) -> Self {
let current_account_balance = context.account_balance + context.attached_deposit;
let current_storage_usage = context.storage_usage;
let max_gas_burnt = match context.view_config {
Some(ViewConfig { max_gas_burnt: max_gas_burnt_view }) => max_gas_burnt_view,
None => config.limit_config.max_gas_burnt,
};
let current_account_locked_balance = context.account_locked_balance;
let gas_counter = GasCounter::new(
config.ext_costs.clone(),
max_gas_burnt,
config.regular_op_cost,
context.prepaid_gas,
context.is_view(),
);
Self {
ext,
context,
config,
fees_config,
promise_results,
memory: super::vmstate::Memory::new(memory),
current_account_balance,
current_account_locked_balance,
current_storage_usage,
gas_counter,
return_data: ReturnData::None,
logs: vec![],
registers: Default::default(),
promises: vec![],
total_log_length: 0,
remaining_stack: u64::from(config.limit_config.max_stack_height),
}
}
pub fn logs(&self) -> &[String] {
&self.logs
}
#[cfg(test)]
pub(super) fn gas_counter(&self) -> &GasCounter {
&self.gas_counter
}
#[cfg(test)]
pub(super) fn config(&self) -> &Config {
&self.config
}
#[cfg(test)]
pub(super) fn memory(&mut self) -> &mut super::vmstate::Memory<'a> {
&mut self.memory
}
#[cfg(test)]
pub(super) fn registers(&mut self) -> &mut super::vmstate::Registers {
&mut self.registers
}
pub fn finite_wasm_gas(&mut self, gas: u64) -> Result<()> {
self.gas(gas)
}
pub fn finite_wasm_stack(&mut self, operand_size: u64, frame_size: u64) -> Result<()> {
self.remaining_stack =
match self.remaining_stack.checked_sub(operand_size.saturating_add(frame_size)) {
Some(s) => s,
None => return Err(VMLogicError::HostError(HostError::MemoryAccessViolation)),
};
self.gas(((frame_size + 7) / 8) * u64::from(self.config.regular_op_cost))?;
Ok(())
}
pub fn finite_wasm_unstack(&mut self, operand_size: u64, frame_size: u64) -> Result<()> {
self.remaining_stack = self
.remaining_stack
.checked_add(operand_size.saturating_add(frame_size))
.expect("remaining stack integer overflow");
Ok(())
}
#[cfg(test)]
pub fn wrapped_internal_write_register(&mut self, register_id: u64, data: &[u8]) -> Result<()> {
self.registers.set(&mut self.gas_counter, &self.config.limit_config, register_id, data)
}
pub fn read_register(&mut self, register_id: u64, ptr: u64) -> Result<()> {
self.gas_counter.pay_base(base)?;
let data = self.registers.get(&mut self.gas_counter, register_id)?;
self.memory.set(&mut self.gas_counter, ptr, data)
}
pub fn register_len(&mut self, register_id: u64) -> Result<u64> {
self.gas_counter.pay_base(base)?;
Ok(self.registers.get_len(register_id).unwrap_or(u64::MAX))
}
pub fn write_register(&mut self, register_id: u64, data_len: u64, data_ptr: u64) -> Result<()> {
self.gas_counter.pay_base(base)?;
let data =
self.memory.view(&mut self.gas_counter, MemSlice { ptr: data_ptr, len: data_len })?;
self.registers.set(&mut self.gas_counter, &self.config.limit_config, register_id, data)
}
fn get_utf8_string(&mut self, len: u64, ptr: u64) -> Result<String> {
self.gas_counter.pay_base(utf8_decoding_base)?;
let mut buf;
let max_len =
self.config.limit_config.max_total_log_length.saturating_sub(self.total_log_length);
if len != u64::MAX {
if len > max_len {
return self.total_log_length_exceeded(len);
}
buf = self.memory.view(&mut self.gas_counter, MemSlice { ptr, len })?.into_owned();
} else {
buf = vec![];
for i in 0..=max_len {
let el = self.memory.get_u8(&mut self.gas_counter, ptr + i)?;
if el == 0 {
break;
}
if i == max_len {
return self.total_log_length_exceeded(max_len.saturating_add(1));
}
buf.push(el);
}
}
self.gas_counter.pay_per(utf8_decoding_byte, buf.len() as _)?;
String::from_utf8(buf).map_err(|_| HostError::BadUTF8.into())
}
#[cfg(feature = "sandbox")]
fn sandbox_get_utf8_string(&mut self, len: u64, ptr: u64) -> Result<String> {
let buf = self.memory.view_for_free(MemSlice { ptr, len })?.into_owned();
String::from_utf8(buf).map_err(|_| HostError::BadUTF8.into())
}
fn get_utf16_string(&mut self, mut len: u64, ptr: u64) -> Result<String> {
self.gas_counter.pay_base(utf16_decoding_base)?;
let max_len =
self.config.limit_config.max_total_log_length.saturating_sub(self.total_log_length);
let mem_view = if len == u64::MAX {
len = self.get_nul_terminated_utf16_len(ptr, max_len)?;
self.memory.view_for_free(MemSlice { ptr, len })
} else {
self.memory.view(&mut self.gas_counter, MemSlice { ptr, len })
}?;
let input = stdx::as_chunks_exact(&mem_view).map_err(|_| HostError::BadUTF16)?;
if len > max_len {
return self.total_log_length_exceeded(len);
}
self.gas_counter.pay_per(utf16_decoding_byte, len)?;
char::decode_utf16(input.into_iter().copied().map(u16::from_le_bytes))
.collect::<Result<String, _>>()
.map_err(|_| HostError::BadUTF16.into())
}
fn get_nul_terminated_utf16_len(&mut self, ptr: u64, max_len: u64) -> Result<u64> {
let mut len = 0;
loop {
if self.memory.get_u16(&mut self.gas_counter, ptr.saturating_add(len))? == 0 {
return Ok(len);
}
len = match len.checked_add(2) {
Some(len) if len <= max_len => len,
Some(len) => return self.total_log_length_exceeded(len),
None => return self.total_log_length_exceeded(u64::MAX),
};
}
}
fn check_can_add_a_log_message(&self) -> Result<()> {
if self.logs.len() as u64 >= self.config.limit_config.max_number_logs {
Err(HostError::NumberOfLogsExceeded { limit: self.config.limit_config.max_number_logs }
.into())
} else {
Ok(())
}
}
fn checked_push_promise(&mut self, promise: Promise) -> Result<PromiseIndex> {
let new_promise_idx = self.promises.len() as PromiseIndex;
self.promises.push(promise);
if self.promises.len() as u64
> self.config.limit_config.max_promises_per_function_call_action
{
Err(HostError::NumberPromisesExceeded {
number_of_promises: self.promises.len() as u64,
limit: self.config.limit_config.max_promises_per_function_call_action,
}
.into())
} else {
Ok(new_promise_idx)
}
}
fn checked_push_log(&mut self, message: String) -> Result<()> {
self.total_log_length += message.len() as u64;
if self.total_log_length > self.config.limit_config.max_total_log_length {
return self.total_log_length_exceeded(0);
}
self.logs.push(message);
Ok(())
}
fn total_log_length_exceeded<T>(&self, add_len: u64) -> Result<T> {
Err(HostError::TotalLogLengthExceeded {
length: self.total_log_length.saturating_add(add_len),
limit: self.config.limit_config.max_total_log_length,
}
.into())
}
fn get_public_key(&mut self, ptr: u64, len: u64) -> Result<PublicKeyBuffer> {
Ok(PublicKeyBuffer::new(&get_memory_or_register!(self, ptr, len)?))
}
pub fn current_account_id(&mut self, register_id: u64) -> Result<()> {
self.gas_counter.pay_base(base)?;
self.registers.set(
&mut self.gas_counter,
&self.config.limit_config,
register_id,
self.context.current_account_id.as_bytes(),
)
}
pub fn signer_account_id(&mut self, register_id: u64) -> Result<()> {
self.gas_counter.pay_base(base)?;
if self.context.is_view() {
return Err(HostError::ProhibitedInView {
method_name: "signer_account_id".to_string(),
}
.into());
}
self.registers.set(
&mut self.gas_counter,
&self.config.limit_config,
register_id,
self.context.signer_account_id.as_bytes(),
)
}
pub fn signer_account_pk(&mut self, register_id: u64) -> Result<()> {
self.gas_counter.pay_base(base)?;
if self.context.is_view() {
return Err(HostError::ProhibitedInView {
method_name: "signer_account_pk".to_string(),
}
.into());
}
self.registers.set(
&mut self.gas_counter,
&self.config.limit_config,
register_id,
self.context.signer_account_pk.as_slice(),
)
}
pub fn predecessor_account_id(&mut self, register_id: u64) -> Result<()> {
self.gas_counter.pay_base(base)?;
if self.context.is_view() {
return Err(HostError::ProhibitedInView {
method_name: "predecessor_account_id".to_string(),
}
.into());
}
self.registers.set(
&mut self.gas_counter,
&self.config.limit_config,
register_id,
self.context.predecessor_account_id.as_bytes(),
)
}
pub fn input(&mut self, register_id: u64) -> Result<()> {
self.gas_counter.pay_base(base)?;
self.registers.set(
&mut self.gas_counter,
&self.config.limit_config,
register_id,
self.context.input.as_slice(),
)
}
pub fn block_index(&mut self) -> Result<u64> {
self.gas_counter.pay_base(base)?;
Ok(self.context.block_height)
}
pub fn block_timestamp(&mut self) -> Result<u64> {
self.gas_counter.pay_base(base)?;
Ok(self.context.block_timestamp)
}
pub fn epoch_height(&mut self) -> Result<EpochHeight> {
self.gas_counter.pay_base(base)?;
Ok(self.context.epoch_height)
}
pub fn validator_stake(
&mut self,
account_id_len: u64,
account_id_ptr: u64,
stake_ptr: u64,
) -> Result<()> {
self.gas_counter.pay_base(base)?;
let account_id = self.read_and_parse_account_id(account_id_ptr, account_id_len)?;
self.gas_counter.pay_base(validator_pledge_base)?;
let stake = self.ext.validator_stake(&account_id)?.unwrap_or_default();
self.memory.set_u128(&mut self.gas_counter, stake_ptr, stake)
}
pub fn validator_power(
&mut self,
account_id_len: u64,
account_id_ptr: u64,
power_ptr: u64,
) -> Result<()> {
self.gas_counter.pay_base(base)?;
let account_id = self.read_and_parse_account_id(account_id_ptr, account_id_len)?;
self.gas_counter.pay_base(validator_power_base)?;
let power = self.ext.validator_power(&account_id)?.unwrap_or_default();
self.memory.set_u128(&mut self.gas_counter, power_ptr, power)
}
pub fn validator_total_stake(&mut self, stake_ptr: u64) -> Result<()> {
self.gas_counter.pay_base(base)?;
self.gas_counter.pay_base(validator_total_pledge_base)?;
let total_stake = self.ext.validator_total_stake()?;
self.memory.set_u128(&mut self.gas_counter, stake_ptr, total_stake)
}
pub fn validator_total_power(&mut self, power_ptr: u64) -> Result<()> {
self.gas_counter.pay_base(base)?;
self.gas_counter.pay_base(validator_total_power_base)?;
let total_power = self.ext.validator_total_power()?;
self.memory.set_u128(&mut self.gas_counter, power_ptr, total_power)
}
pub fn storage_usage(&mut self) -> Result<StorageUsage> {
self.gas_counter.pay_base(base)?;
Ok(self.current_storage_usage)
}
pub fn account_balance(&mut self, balance_ptr: u64) -> Result<()> {
self.gas_counter.pay_base(base)?;
self.memory.set_u128(&mut self.gas_counter, balance_ptr, self.current_account_balance)
}
pub fn account_locked_balance(&mut self, balance_ptr: u64) -> Result<()> {
self.gas_counter.pay_base(base)?;
self.memory.set_u128(
&mut self.gas_counter,
balance_ptr,
self.current_account_locked_balance,
)
}
pub fn attached_deposit(&mut self, balance_ptr: u64) -> Result<()> {
self.gas_counter.pay_base(base)?;
self.memory.set_u128(&mut self.gas_counter, balance_ptr, self.context.attached_deposit)
}
pub fn prepaid_gas(&mut self) -> Result<Gas> {
self.gas_counter.pay_base(base)?;
if self.context.is_view() {
return Err(
HostError::ProhibitedInView { method_name: "prepaid_gas".to_string() }.into()
);
}
Ok(self.context.prepaid_gas)
}
pub fn used_gas(&mut self) -> Result<Gas> {
self.gas_counter.pay_base(base)?;
if self.context.is_view() {
return Err(HostError::ProhibitedInView { method_name: "used_gas".to_string() }.into());
}
Ok(self.gas_counter.used_gas())
}
pub fn alt_bn128_g1_multiexp(
&mut self,
value_len: u64,
value_ptr: u64,
register_id: u64,
) -> Result<()> {
self.gas_counter.pay_base(alt_bn128_g1_multiexp_base)?;
let data = get_memory_or_register!(self, value_ptr, value_len)?;
let elements = super::alt_bn128::split_elements(&data)?;
self.gas_counter.pay_per(alt_bn128_g1_multiexp_element, elements.len() as u64)?;
let res = super::alt_bn128::g1_multiexp(elements)?;
self.registers.set(&mut self.gas_counter, &self.config.limit_config, register_id, res)
}
pub fn alt_bn128_g1_sum(
&mut self,
value_len: u64,
value_ptr: u64,
register_id: u64,
) -> Result<()> {
self.gas_counter.pay_base(alt_bn128_g1_sum_base)?;
let data = get_memory_or_register!(self, value_ptr, value_len)?;
let elements = super::alt_bn128::split_elements(&data)?;
self.gas_counter.pay_per(alt_bn128_g1_sum_element, elements.len() as u64)?;
let res = super::alt_bn128::g1_sum(elements)?;
self.registers.set(&mut self.gas_counter, &self.config.limit_config, register_id, res)
}
pub fn alt_bn128_pairing_check(&mut self, value_len: u64, value_ptr: u64) -> Result<u64> {
self.gas_counter.pay_base(alt_bn128_pairing_check_base)?;
let data = get_memory_or_register!(self, value_ptr, value_len)?;
let elements = super::alt_bn128::split_elements(&data)?;
self.gas_counter.pay_per(alt_bn128_pairing_check_element, elements.len() as u64)?;
let res = super::alt_bn128::pairing_check(elements)?;
Ok(res as u64)
}
pub fn random_seed(&mut self, register_id: u64) -> Result<()> {
self.gas_counter.pay_base(base)?;
self.registers.set(
&mut self.gas_counter,
&self.config.limit_config,
register_id,
self.context.random_seed.as_slice(),
)
}
pub fn sha256(&mut self, value_len: u64, value_ptr: u64, register_id: u64) -> Result<()> {
self.gas_counter.pay_base(sha256_base)?;
let value = get_memory_or_register!(self, value_ptr, value_len)?;
self.gas_counter.pay_per(sha256_byte, value.len() as u64)?;
use sha2::Digest;
let value_hash = sha2::Sha256::digest(&value);
self.registers.set(
&mut self.gas_counter,
&self.config.limit_config,
register_id,
value_hash.as_slice(),
)
}
pub fn keccak256(&mut self, value_len: u64, value_ptr: u64, register_id: u64) -> Result<()> {
self.gas_counter.pay_base(keccak256_base)?;
let value = get_memory_or_register!(self, value_ptr, value_len)?;
self.gas_counter.pay_per(keccak256_byte, value.len() as u64)?;
use sha3::Digest;
let value_hash = sha3::Keccak256::digest(&value);
self.registers.set(
&mut self.gas_counter,
&self.config.limit_config,
register_id,
value_hash.as_slice(),
)
}
pub fn keccak512(&mut self, value_len: u64, value_ptr: u64, register_id: u64) -> Result<()> {
self.gas_counter.pay_base(keccak512_base)?;
let value = get_memory_or_register!(self, value_ptr, value_len)?;
self.gas_counter.pay_per(keccak512_byte, value.len() as u64)?;
use sha3::Digest;
let value_hash = sha3::Keccak512::digest(&value);
self.registers.set(
&mut self.gas_counter,
&self.config.limit_config,
register_id,
value_hash.as_slice(),
)
}
pub fn ripemd160(&mut self, value_len: u64, value_ptr: u64, register_id: u64) -> Result<()> {
self.gas_counter.pay_base(ripemd160_base)?;
let value = get_memory_or_register!(self, value_ptr, value_len)?;
let message_blocks = value
.len()
.checked_add(8)
.ok_or(VMLogicError::HostError(HostError::IntegerOverflow))?
/ 64
+ 1;
self.gas_counter.pay_per(ripemd160_block, message_blocks as u64)?;
use ripemd::Digest;
let value_hash = ripemd::Ripemd160::digest(&value);
self.registers.set(
&mut self.gas_counter,
&self.config.limit_config,
register_id,
value_hash.as_slice(),
)
}
pub fn ecrecover(
&mut self,
hash_len: u64,
hash_ptr: u64,
sig_len: u64,
sig_ptr: u64,
v: u64,
malleability_flag: u64,
register_id: u64,
) -> Result<u64> {
self.gas_counter.pay_base(ecrecover_base)?;
let signature = {
let vec = get_memory_or_register!(self, sig_ptr, sig_len)?;
if vec.len() != 64 {
return Err(VMLogicError::HostError(HostError::ECRecoverError {
msg: format!(
"The length of the signature: {}, exceeds the limit of 64 bytes",
vec.len()
),
}));
}
let mut bytes = [0u8; 65];
bytes[0..64].copy_from_slice(&vec);
if v < 4 {
bytes[64] = v as u8;
Secp256K1Signature::from(bytes)
} else {
return Err(VMLogicError::HostError(HostError::ECRecoverError {
msg: format!("V recovery byte 0 through 3 are valid but was provided {}", v),
}));
}
};
let hash = {
let vec = get_memory_or_register!(self, hash_ptr, hash_len)?;
if vec.len() != 32 {
return Err(VMLogicError::HostError(HostError::ECRecoverError {
msg: format!(
"The length of the hash: {}, exceeds the limit of 32 bytes",
vec.len()
),
}));
}
let mut bytes = [0u8; 32];
bytes.copy_from_slice(&vec);
bytes
};
if malleability_flag != 0 && malleability_flag != 1 {
return Err(VMLogicError::HostError(HostError::ECRecoverError {
msg: format!(
"Malleability flag needs to be 0 or 1, but is instead {}",
malleability_flag
),
}));
}
if !signature.check_signature_values(malleability_flag != 0) {
return Ok(false as u64);
}
if let Ok(pk) = signature.recover(hash) {
self.registers.set(
&mut self.gas_counter,
&self.config.limit_config,
register_id,
pk.as_ref(),
)?;
return Ok(true as u64);
};
Ok(false as u64)
}
pub fn ed25519_verify(
&mut self,
signature_len: u64,
signature_ptr: u64,
message_len: u64,
message_ptr: u64,
public_key_len: u64,
public_key_ptr: u64,
) -> Result<u64> {
use ed25519_dalek::Verifier;
self.gas_counter.pay_base(ed25519_verify_base)?;
let signature: ed25519_dalek::Signature = {
let vec = get_memory_or_register!(self, signature_ptr, signature_len)?;
let b = <&[u8; ed25519_dalek::SIGNATURE_LENGTH]>::try_from(&vec[..]).map_err(|_| {
VMLogicError::HostError(HostError::Ed25519VerifyInvalidInput {
msg: "invalid signature length".to_string(),
})
})?;
if b[ed25519_dalek::SIGNATURE_LENGTH - 1] & 0b1110_0000 != 0 {
return Ok(false as u64);
}
ed25519_dalek::Signature::from_bytes(b)
};
let message = get_memory_or_register!(self, message_ptr, message_len)?;
self.gas_counter.pay_per(ed25519_verify_byte, message.len() as u64)?;
let public_key: ed25519_dalek::VerifyingKey = {
let vec = get_memory_or_register!(self, public_key_ptr, public_key_len)?;
let b =
<&[u8; ed25519_dalek::PUBLIC_KEY_LENGTH]>::try_from(&vec[..]).map_err(|_| {
VMLogicError::HostError(HostError::Ed25519VerifyInvalidInput {
msg: "invalid public key length".to_string(),
})
})?;
match ed25519_dalek::VerifyingKey::from_bytes(b) {
Ok(public_key) => public_key,
Err(_) => return Ok(false as u64),
}
};
match public_key.verify(&message, &signature) {
Err(_) => Ok(false as u64),
Ok(()) => Ok(true as u64),
}
}
pub fn gas(&mut self, gas: Gas) -> Result<()> {
self.gas_counter.burn_gas(Gas::from(gas))
}
pub fn gas_opcodes(&mut self, opcodes: u32) -> Result<()> {
self.gas(opcodes as u64 * self.config.regular_op_cost as u64)
}
pub fn gas_seen_from_wasm(&mut self, gas: u32) -> Result<()> {
self.gas_opcodes(gas)
}
fn pay_gas_for_new_receipt(&mut self, sir: bool, data_dependencies: &[bool]) -> Result<()> {
let fees_config_cfg = &self.fees_config;
let mut burn_gas = fees_config_cfg.fee(ActionCosts::new_action_receipt).send_fee(sir);
let mut use_gas = fees_config_cfg.fee(ActionCosts::new_action_receipt).exec_fee();
for dep in data_dependencies {
burn_gas = burn_gas
.checked_add(fees_config_cfg.fee(ActionCosts::new_data_receipt_base).send_fee(*dep))
.ok_or(HostError::IntegerOverflow)?
.checked_add(fees_config_cfg.fee(ActionCosts::new_data_receipt_base).exec_fee())
.ok_or(HostError::IntegerOverflow)?;
}
use_gas = use_gas.checked_add(burn_gas).ok_or(HostError::IntegerOverflow)?;
self.gas_counter.pay_action_accumulated(burn_gas, use_gas, ActionCosts::new_action_receipt)
}
fn deduct_balance(&mut self, amount: Balance) -> Result<()> {
self.current_account_balance =
self.current_account_balance.checked_sub(amount).ok_or(HostError::BalanceExceeded)?;
Ok(())
}
pub fn promise_create(
&mut self,
account_id_len: u64,
account_id_ptr: u64,
method_name_len: u64,
method_name_ptr: u64,
arguments_len: u64,
arguments_ptr: u64,
amount_ptr: u64,
gas: Gas,
) -> Result<u64> {
let new_promise_idx = self.promise_batch_create(account_id_len, account_id_ptr)?;
self.promise_batch_action_function_call(
new_promise_idx,
method_name_len,
method_name_ptr,
arguments_len,
arguments_ptr,
amount_ptr,
gas,
)?;
Ok(new_promise_idx)
}
pub fn promise_then(
&mut self,
promise_idx: u64,
account_id_len: u64,
account_id_ptr: u64,
method_name_len: u64,
method_name_ptr: u64,
arguments_len: u64,
arguments_ptr: u64,
amount_ptr: u64,
gas: u64,
) -> Result<u64> {
let new_promise_idx =
self.promise_batch_then(promise_idx, account_id_len, account_id_ptr)?;
self.promise_batch_action_function_call(
new_promise_idx,
method_name_len,
method_name_ptr,
arguments_len,
arguments_ptr,
amount_ptr,
gas,
)?;
Ok(new_promise_idx)
}
pub fn promise_and(
&mut self,
promise_idx_ptr: u64,
promise_idx_count: u64,
) -> Result<PromiseIndex> {
self.gas_counter.pay_base(base)?;
if self.context.is_view() {
return Err(
HostError::ProhibitedInView { method_name: "promise_and".to_string() }.into()
);
}
self.gas_counter.pay_base(promise_and_base)?;
let memory_len = promise_idx_count
.checked_mul(size_of::<u64>() as u64)
.ok_or(HostError::IntegerOverflow)?;
self.gas_counter.pay_per(promise_and_per_promise, memory_len)?;
let promise_indices = self
.memory
.view(&mut self.gas_counter, MemSlice { ptr: promise_idx_ptr, len: memory_len })?;
let promise_indices = stdx::as_chunks_exact::<{ size_of::<u64>() }, u8>(&promise_indices)
.unwrap()
.into_iter()
.map(|bytes| u64::from_le_bytes(*bytes));
let mut receipt_dependencies = vec![];
for promise_idx in promise_indices {
let promise = self
.promises
.get(promise_idx as usize)
.ok_or(HostError::InvalidPromiseIndex { promise_idx })?;
match &promise {
Promise::Receipt(receipt_idx) => {
receipt_dependencies.push(*receipt_idx);
}
Promise::NotReceipt(receipt_indices) => {
receipt_dependencies.extend(receipt_indices.clone());
}
}
if receipt_dependencies.len() as u64
> self.config.limit_config.max_number_input_data_dependencies
{
return Err(HostError::NumberInputDataDependenciesExceeded {
number_of_input_data_dependencies: receipt_dependencies.len() as u64,
limit: self.config.limit_config.max_number_input_data_dependencies,
}
.into());
}
}
self.checked_push_promise(Promise::NotReceipt(receipt_dependencies))
}
pub fn promise_batch_create(
&mut self,
account_id_len: u64,
account_id_ptr: u64,
) -> Result<u64> {
self.gas_counter.pay_base(base)?;
if self.context.is_view() {
return Err(HostError::ProhibitedInView {
method_name: "promise_batch_create".to_string(),
}
.into());
}
let account_id = self.read_and_parse_account_id(account_id_ptr, account_id_len)?;
let sir = account_id == self.context.current_account_id;
self.pay_gas_for_new_receipt(sir, &[])?;
let new_receipt_idx = self.ext.create_receipt(vec![], account_id)?;
self.checked_push_promise(Promise::Receipt(new_receipt_idx))
}
pub fn promise_batch_then(
&mut self,
promise_idx: u64,
account_id_len: u64,
account_id_ptr: u64,
) -> Result<u64> {
self.gas_counter.pay_base(base)?;
if self.context.is_view() {
return Err(HostError::ProhibitedInView {
method_name: "promise_batch_then".to_string(),
}
.into());
}
let account_id = self.read_and_parse_account_id(account_id_ptr, account_id_len)?;
let promise = self
.promises
.get(promise_idx as usize)
.ok_or(HostError::InvalidPromiseIndex { promise_idx })?;
let receipt_dependencies = match &promise {
Promise::Receipt(receipt_idx) => vec![*receipt_idx],
Promise::NotReceipt(receipt_indices) => receipt_indices.clone(),
};
let sir = account_id == self.context.current_account_id;
let deps: Vec<_> = receipt_dependencies
.iter()
.map(|&receipt_idx| self.ext.get_receipt_receiver(receipt_idx) == &account_id)
.collect();
self.pay_gas_for_new_receipt(sir, &deps)?;
let new_receipt_idx = self.ext.create_receipt(receipt_dependencies, account_id)?;
self.checked_push_promise(Promise::Receipt(new_receipt_idx))
}
fn promise_idx_to_receipt_idx_with_sir(
&self,
promise_idx: u64,
) -> Result<(ReceiptIndex, bool)> {
let promise = self
.promises
.get(promise_idx as usize)
.ok_or(HostError::InvalidPromiseIndex { promise_idx })?;
let receipt_idx = match &promise {
Promise::Receipt(receipt_idx) => Ok(*receipt_idx),
Promise::NotReceipt(_) => Err(HostError::CannotAppendActionToJointPromise),
}?;
let account_id = self.ext.get_receipt_receiver(receipt_idx);
let sir = account_id == &self.context.current_account_id;
Ok((receipt_idx, sir))
}
pub fn promise_batch_action_create_account(&mut self, promise_idx: u64) -> Result<()> {
self.gas_counter.pay_base(base)?;
if self.context.is_view() {
return Err(HostError::ProhibitedInView {
method_name: "promise_batch_action_create_account".to_string(),
}
.into());
}
let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?;
self.pay_action_base(ActionCosts::create_account, sir)?;
self.ext.append_action_create_account(receipt_idx)?;
Ok(())
}
pub fn promise_batch_action_deploy_contract(
&mut self,
promise_idx: u64,
code_len: u64,
code_ptr: u64,
) -> Result<()> {
self.gas_counter.pay_base(base)?;
if self.context.is_view() {
return Err(HostError::ProhibitedInView {
method_name: "promise_batch_action_deploy_contract".to_string(),
}
.into());
}
let code = get_memory_or_register!(self, code_ptr, code_len)?;
let code_len = code.len() as u64;
let limit = self.config.limit_config.max_contract_size;
if code_len > limit {
return Err(HostError::ContractSizeExceeded { size: code_len, limit }.into());
}
let code = code.into_owned();
let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?;
self.pay_action_base(ActionCosts::deploy_contract_base, sir)?;
self.pay_action_per_byte(ActionCosts::deploy_contract_byte, code_len, sir)?;
self.ext.append_action_deploy_contract(receipt_idx, code)?;
Ok(())
}
pub fn promise_batch_action_function_call(
&mut self,
promise_idx: u64,
method_name_len: u64,
method_name_ptr: u64,
arguments_len: u64,
arguments_ptr: u64,
amount_ptr: u64,
gas: Gas,
) -> Result<()> {
self.promise_batch_action_function_call_weight(
promise_idx,
method_name_len,
method_name_ptr,
arguments_len,
arguments_ptr,
amount_ptr,
gas,
0,
)
}
pub fn promise_batch_action_function_call_weight(
&mut self,
promise_idx: u64,
method_name_len: u64,
method_name_ptr: u64,
arguments_len: u64,
arguments_ptr: u64,
amount_ptr: u64,
gas: Gas,
gas_weight: u64,
) -> Result<()> {
self.gas_counter.pay_base(base)?;
if self.context.is_view() {
return Err(HostError::ProhibitedInView {
method_name: "promise_batch_action_function_call".to_string(),
}
.into());
}
let amount = self.memory.get_u128(&mut self.gas_counter, amount_ptr)?;
let method_name = get_memory_or_register!(self, method_name_ptr, method_name_len)?;
if method_name.is_empty() {
return Err(HostError::EmptyMethodName.into());
}
let arguments = get_memory_or_register!(self, arguments_ptr, arguments_len)?;
let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?;
let method_name = method_name.into_owned();
let arguments = arguments.into_owned();
let num_bytes = method_name.len() as u64 + arguments.len() as u64;
self.pay_action_base(ActionCosts::function_call_base, sir)?;
self.pay_action_per_byte(ActionCosts::function_call_byte, num_bytes, sir)?;
self.gas_counter.prepay_gas(gas)?;
self.deduct_balance(amount)?;
self.ext.append_action_function_call_weight(
receipt_idx,
method_name,
arguments,
amount,
gas,
GasWeight(gas_weight),
)
}
pub fn promise_batch_action_transfer(
&mut self,
promise_idx: u64,
amount_ptr: u64,
) -> Result<()> {
self.gas_counter.pay_base(base)?;
if self.context.is_view() {
return Err(HostError::ProhibitedInView {
method_name: "promise_batch_action_transfer".to_string(),
}
.into());
}
let amount = self.memory.get_u128(&mut self.gas_counter, amount_ptr)?;
let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?;
let receiver_id = self.ext.get_receipt_receiver(receipt_idx);
let send_fee = transfer_send_fee(
self.fees_config,
sir,
self.config.implicit_account_creation,
self.config.eth_accounts,
receiver_id.get_account_type(),
);
let exec_fee = transfer_exec_fee(
self.fees_config,
self.config.implicit_account_creation,
self.config.eth_accounts,
receiver_id.get_account_type(),
);
let burn_gas = send_fee;
let use_gas = burn_gas.checked_add(exec_fee).ok_or(HostError::IntegerOverflow)?;
self.gas_counter.pay_action_accumulated(burn_gas, use_gas, ActionCosts::transfer)?;
self.deduct_balance(amount)?;
self.ext.append_action_transfer(receipt_idx, amount)?;
Ok(())
}
pub fn promise_batch_action_stake(
&mut self,
promise_idx: u64,
amount_ptr: u64,
public_key_len: u64,
public_key_ptr: u64,
) -> Result<()> {
self.gas_counter.pay_base(base)?;
if self.context.is_view() {
return Err(HostError::ProhibitedInView {
method_name: "promise_batch_action_stake".to_string(),
}
.into());
}
let amount = self.memory.get_u128(&mut self.gas_counter, amount_ptr)?;
let public_key = self.get_public_key(public_key_ptr, public_key_len)?;
let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?;
self.pay_action_base(ActionCosts::pledge, sir)?;
self.ext.append_action_pledge(receipt_idx, amount, public_key.decode()?);
Ok(())
}
pub fn promise_batch_action_add_key_with_full_access(
&mut self,
promise_idx: u64,
public_key_len: u64,
public_key_ptr: u64,
nonce: u64,
) -> Result<()> {
self.gas_counter.pay_base(base)?;
if self.context.is_view() {
return Err(HostError::ProhibitedInView {
method_name: "promise_batch_action_add_key_with_full_access".to_string(),
}
.into());
}
let public_key = self.get_public_key(public_key_ptr, public_key_len)?;
let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?;
self.pay_action_base(ActionCosts::add_full_access_key, sir)?;
self.ext.append_action_add_key_with_full_access(receipt_idx, public_key.decode()?, nonce);
Ok(())
}
pub fn promise_batch_action_add_key_with_function_call(
&mut self,
promise_idx: u64,
public_key_len: u64,
public_key_ptr: u64,
nonce: u64,
allowance_ptr: u64,
receiver_id_len: u64,
receiver_id_ptr: u64,
method_names_len: u64,
method_names_ptr: u64,
) -> Result<()> {
self.gas_counter.pay_base(base)?;
if self.context.is_view() {
return Err(HostError::ProhibitedInView {
method_name: "promise_batch_action_add_key_with_function_call".to_string(),
}
.into());
}
let public_key = self.get_public_key(public_key_ptr, public_key_len)?;
let allowance = self.memory.get_u128(&mut self.gas_counter, allowance_ptr)?;
let allowance = if allowance > 0 { Some(allowance) } else { None };
let receiver_id = self.read_and_parse_account_id(receiver_id_ptr, receiver_id_len)?;
let raw_method_names = get_memory_or_register!(self, method_names_ptr, method_names_len)?;
let method_names = split_method_names(&raw_method_names)?;
let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?;
let num_bytes = method_names.iter().map(|v| v.len() as u64 + 1).sum::<u64>();
self.pay_action_base(ActionCosts::add_function_call_key_base, sir)?;
self.pay_action_per_byte(ActionCosts::add_function_call_key_byte, num_bytes, sir)?;
self.ext.append_action_add_key_with_function_call(
receipt_idx,
public_key.decode()?,
nonce,
allowance,
receiver_id,
method_names,
)?;
Ok(())
}
pub fn promise_batch_action_delete_key(
&mut self,
promise_idx: u64,
public_key_len: u64,
public_key_ptr: u64,
) -> Result<()> {
self.gas_counter.pay_base(base)?;
if self.context.is_view() {
return Err(HostError::ProhibitedInView {
method_name: "promise_batch_action_delete_key".to_string(),
}
.into());
}
let public_key = self.get_public_key(public_key_ptr, public_key_len)?;
let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?;
self.pay_action_base(ActionCosts::delete_key, sir)?;
self.ext.append_action_delete_key(receipt_idx, public_key.decode()?);
Ok(())
}
pub fn promise_batch_action_delete_account(
&mut self,
promise_idx: u64,
beneficiary_id_len: u64,
beneficiary_id_ptr: u64,
) -> Result<()> {
self.gas_counter.pay_base(base)?;
if self.context.is_view() {
return Err(HostError::ProhibitedInView {
method_name: "promise_batch_action_delete_account".to_string(),
}
.into());
}
let beneficiary_id =
self.read_and_parse_account_id(beneficiary_id_ptr, beneficiary_id_len)?;
let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?;
self.pay_action_base(ActionCosts::delete_account, sir)?;
self.ext.append_action_delete_account(receipt_idx, beneficiary_id)?;
Ok(())
}
pub fn promise_results_count(&mut self) -> Result<u64> {
self.gas_counter.pay_base(base)?;
if self.context.is_view() {
return Err(HostError::ProhibitedInView {
method_name: "promise_results_count".to_string(),
}
.into());
}
Ok(self.promise_results.len() as _)
}
pub fn promise_result(&mut self, result_idx: u64, register_id: u64) -> Result<u64> {
self.gas_counter.pay_base(base)?;
if self.context.is_view() {
return Err(
HostError::ProhibitedInView { method_name: "promise_result".to_string() }.into()
);
}
match self
.promise_results
.get(result_idx as usize)
.ok_or(HostError::InvalidPromiseResultIndex { result_idx })?
{
PromiseResult::NotReady => Ok(0),
PromiseResult::Successful(data) => {
self.registers.set(
&mut self.gas_counter,
&self.config.limit_config,
register_id,
data.as_slice(),
)?;
Ok(1)
}
PromiseResult::Failed => Ok(2),
}
}
pub fn promise_return(&mut self, promise_idx: u64) -> Result<()> {
self.gas_counter.pay_base(base)?;
self.gas_counter.pay_base(promise_return)?;
if self.context.is_view() {
return Err(
HostError::ProhibitedInView { method_name: "promise_return".to_string() }.into()
);
}
match self
.promises
.get(promise_idx as usize)
.ok_or(HostError::InvalidPromiseIndex { promise_idx })?
{
Promise::Receipt(receipt_idx) => {
self.return_data = ReturnData::ReceiptIndex(*receipt_idx);
Ok(())
}
Promise::NotReceipt(_) => Err(HostError::CannotReturnJointPromise.into()),
}
}
pub fn value_return(&mut self, value_len: u64, value_ptr: u64) -> Result<()> {
self.gas_counter.pay_base(base)?;
let return_val = get_memory_or_register!(self, value_ptr, value_len)?;
let mut burn_gas: Gas = 0;
let num_bytes = return_val.len() as u64;
if num_bytes > self.config.limit_config.max_length_returned_data {
return Err(HostError::ReturnedValueLengthExceeded {
length: num_bytes,
limit: self.config.limit_config.max_length_returned_data,
}
.into());
}
for data_receiver in &self.context.output_data_receivers {
let sir = data_receiver == &self.context.current_account_id;
burn_gas = burn_gas
.checked_add(
self.fees_config
.fee(ActionCosts::new_data_receipt_byte)
.send_fee(sir)
.checked_add(
self.fees_config.fee(ActionCosts::new_data_receipt_byte).exec_fee(),
)
.ok_or(HostError::IntegerOverflow)?
.checked_mul(num_bytes)
.ok_or(HostError::IntegerOverflow)?,
)
.ok_or(HostError::IntegerOverflow)?;
}
self.gas_counter.pay_action_accumulated(
burn_gas,
burn_gas,
ActionCosts::new_data_receipt_byte,
)?;
self.return_data = ReturnData::Value(return_val.into_owned());
Ok(())
}
pub fn panic(&mut self) -> Result<()> {
self.gas_counter.pay_base(base)?;
Err(HostError::GuestPanic { panic_msg: "explicit guest panic".to_string() }.into())
}
pub fn panic_utf8(&mut self, len: u64, ptr: u64) -> Result<()> {
self.gas_counter.pay_base(base)?;
Err(HostError::GuestPanic { panic_msg: self.get_utf8_string(len, ptr)? }.into())
}
pub fn log_utf8(&mut self, len: u64, ptr: u64) -> Result<()> {
self.gas_counter.pay_base(base)?;
self.check_can_add_a_log_message()?;
let message = self.get_utf8_string(len, ptr)?;
self.gas_counter.pay_base(log_base)?;
self.gas_counter.pay_per(log_byte, message.len() as u64)?;
self.checked_push_log(message)
}
pub fn log_utf16(&mut self, len: u64, ptr: u64) -> Result<()> {
self.gas_counter.pay_base(base)?;
self.check_can_add_a_log_message()?;
let message = self.get_utf16_string(len, ptr)?;
self.gas_counter.pay_base(log_base)?;
self.gas_counter.pay_per(log_byte, message.len() as u64)?;
self.checked_push_log(message)
}
pub fn abort(&mut self, msg_ptr: u32, filename_ptr: u32, line: u32, col: u32) -> Result<()> {
self.gas_counter.pay_base(base)?;
if msg_ptr < 4 || filename_ptr < 4 {
return Err(HostError::BadUTF16.into());
}
self.check_can_add_a_log_message()?;
let msg_len = self.memory.get_u32(&mut self.gas_counter, (msg_ptr - 4) as u64)?;
let filename_len = self.memory.get_u32(&mut self.gas_counter, (filename_ptr - 4) as u64)?;
let msg = self.get_utf16_string(msg_len as u64, msg_ptr as u64)?;
let filename = self.get_utf16_string(filename_len as u64, filename_ptr as u64)?;
let message = format!("{}, filename: \"{}\" line: {} col: {}", msg, filename, line, col);
self.gas_counter.pay_base(log_base)?;
self.gas_counter.pay_per(log_byte, message.as_bytes().len() as u64)?;
self.checked_push_log(format!("ABORT: {}", message))?;
Err(HostError::GuestPanic { panic_msg: message }.into())
}
fn read_and_parse_account_id(&mut self, ptr: u64, len: u64) -> Result<AccountId> {
let buf = get_memory_or_register!(self, ptr, len)?;
self.gas_counter.pay_base(utf8_decoding_base)?;
self.gas_counter.pay_per(utf8_decoding_byte, buf.len() as u64)?;
let account_id = String::from_utf8(buf.into_owned())
.map(
#[allow(deprecated)]
AccountId::new_unvalidated,
)
.map_err(|_| HostError::BadUTF8)?;
Ok(account_id)
}
pub fn storage_write(
&mut self,
key_len: u64,
key_ptr: u64,
value_len: u64,
value_ptr: u64,
register_id: u64,
) -> Result<u64> {
self.gas_counter.pay_base(base)?;
if self.context.is_view() {
return Err(
HostError::ProhibitedInView { method_name: "storage_write".to_string() }.into()
);
}
self.gas_counter.pay_base(storage_write_base)?;
let key = get_memory_or_register!(self, key_ptr, key_len)?;
if key.len() as u64 > self.config.limit_config.max_length_storage_key {
return Err(HostError::KeyLengthExceeded {
length: key.len() as u64,
limit: self.config.limit_config.max_length_storage_key,
}
.into());
}
let value = get_memory_or_register!(self, value_ptr, value_len)?;
if value.len() as u64 > self.config.limit_config.max_length_storage_value {
return Err(HostError::ValueLengthExceeded {
length: value.len() as u64,
limit: self.config.limit_config.max_length_storage_value,
}
.into());
}
self.gas_counter.pay_per(storage_write_key_byte, key.len() as u64)?;
self.gas_counter.pay_per(storage_write_value_byte, value.len() as u64)?;
let nodes_before = self.ext.get_trie_nodes_count();
let evicted_ptr = self.ext.storage_get(&key, StorageGetMode::Trie)?;
let evicted =
Self::deref_value(&mut self.gas_counter, storage_write_evicted_byte, evicted_ptr)?;
let nodes_delta = self
.ext
.get_trie_nodes_count()
.checked_sub(&nodes_before)
.ok_or(InconsistentStateError::IntegerOverflow)?;
#[cfg(feature = "io_trace")]
tracing::trace!(
target = "io_tracer",
storage_op = "write",
key = base64(&key),
size = value_len,
evicted_len = evicted.as_ref().map(Vec::len),
tn_mem_reads = nodes_delta.mem_reads,
tn_db_reads = nodes_delta.db_reads,
);
self.gas_counter.add_trie_fees(&nodes_delta)?;
self.ext.storage_set(&key, &value)?;
let storage_config = &self.fees_config.storage_usage_config;
match evicted {
Some(old_value) => {
self.current_storage_usage = self
.current_storage_usage
.checked_sub(old_value.len() as u64)
.ok_or(InconsistentStateError::IntegerOverflow)?;
self.current_storage_usage = self
.current_storage_usage
.checked_add(value.len() as u64)
.ok_or(InconsistentStateError::IntegerOverflow)?;
self.registers.set(
&mut self.gas_counter,
&self.config.limit_config,
register_id,
old_value,
)?;
Ok(1)
}
None => {
self.current_storage_usage = self
.current_storage_usage
.checked_add(
value.len() as u64
+ key.len() as u64
+ storage_config.num_extra_bytes_record,
)
.ok_or(InconsistentStateError::IntegerOverflow)?;
Ok(0)
}
}
}
fn deref_value<'s>(
gas_counter: &mut GasCounter,
cost_per_byte: ExtCosts,
value_ptr: Option<Box<dyn ValuePtr + 's>>,
) -> Result<Option<Vec<u8>>> {
match value_ptr {
Some(value_ptr) => {
gas_counter.pay_per(cost_per_byte, value_ptr.len() as u64)?;
value_ptr.deref().map(Some)
}
None => Ok(None),
}
}
pub fn storage_read(&mut self, key_len: u64, key_ptr: u64, register_id: u64) -> Result<u64> {
self.gas_counter.pay_base(base)?;
self.gas_counter.pay_base(storage_read_base)?;
let key = get_memory_or_register!(self, key_ptr, key_len)?;
if key.len() as u64 > self.config.limit_config.max_length_storage_key {
return Err(HostError::KeyLengthExceeded {
length: key.len() as u64,
limit: self.config.limit_config.max_length_storage_key,
}
.into());
}
self.gas_counter.pay_per(storage_read_key_byte, key.len() as u64)?;
let nodes_before = self.ext.get_trie_nodes_count();
let read = self.ext.storage_get(&key, self.config.storage_get_mode);
let nodes_delta = self
.ext
.get_trie_nodes_count()
.checked_sub(&nodes_before)
.ok_or(InconsistentStateError::IntegerOverflow)?;
self.gas_counter.add_trie_fees(&nodes_delta)?;
let read = Self::deref_value(&mut self.gas_counter, storage_read_value_byte, read?)?;
#[cfg(feature = "io_trace")]
tracing::trace!(
target = "io_tracer",
storage_op = "read",
key = base64(&key),
size = read.as_ref().map(Vec::len),
tn_db_reads = nodes_delta.db_reads,
tn_mem_reads = nodes_delta.mem_reads,
);
match read {
Some(value) => {
self.registers.set(
&mut self.gas_counter,
&self.config.limit_config,
register_id,
value,
)?;
Ok(1)
}
None => Ok(0),
}
}
pub fn storage_remove(&mut self, key_len: u64, key_ptr: u64, register_id: u64) -> Result<u64> {
self.gas_counter.pay_base(base)?;
if self.context.is_view() {
return Err(
HostError::ProhibitedInView { method_name: "storage_remove".to_string() }.into()
);
}
self.gas_counter.pay_base(storage_remove_base)?;
let key = get_memory_or_register!(self, key_ptr, key_len)?;
if key.len() as u64 > self.config.limit_config.max_length_storage_key {
return Err(HostError::KeyLengthExceeded {
length: key.len() as u64,
limit: self.config.limit_config.max_length_storage_key,
}
.into());
}
self.gas_counter.pay_per(storage_remove_key_byte, key.len() as u64)?;
let nodes_before = self.ext.get_trie_nodes_count();
let removed_ptr = self.ext.storage_get(&key, StorageGetMode::Trie)?;
let removed =
Self::deref_value(&mut self.gas_counter, storage_remove_ret_value_byte, removed_ptr)?;
self.ext.storage_remove(&key)?;
let nodes_delta = self
.ext
.get_trie_nodes_count()
.checked_sub(&nodes_before)
.ok_or(InconsistentStateError::IntegerOverflow)?;
#[cfg(feature = "io_trace")]
tracing::trace!(
target = "io_tracer",
storage_op = "remove",
key = base64(&key),
evicted_len = removed.as_ref().map(Vec::len),
tn_mem_reads = nodes_delta.mem_reads,
tn_db_reads = nodes_delta.db_reads,
);
self.gas_counter.add_trie_fees(&nodes_delta)?;
let storage_config = &self.fees_config.storage_usage_config;
match removed {
Some(value) => {
self.current_storage_usage = self
.current_storage_usage
.checked_sub(
value.len() as u64
+ key.len() as u64
+ storage_config.num_extra_bytes_record,
)
.ok_or(InconsistentStateError::IntegerOverflow)?;
self.registers.set(
&mut self.gas_counter,
&self.config.limit_config,
register_id,
value,
)?;
Ok(1)
}
None => Ok(0),
}
}
pub fn storage_has_key(&mut self, key_len: u64, key_ptr: u64) -> Result<u64> {
self.gas_counter.pay_base(base)?;
self.gas_counter.pay_base(storage_has_key_base)?;
let key = get_memory_or_register!(self, key_ptr, key_len)?;
if key.len() as u64 > self.config.limit_config.max_length_storage_key {
return Err(HostError::KeyLengthExceeded {
length: key.len() as u64,
limit: self.config.limit_config.max_length_storage_key,
}
.into());
}
self.gas_counter.pay_per(storage_has_key_byte, key.len() as u64)?;
let nodes_before = self.ext.get_trie_nodes_count();
let res = self.ext.storage_has_key(&key, self.config.storage_get_mode);
let nodes_delta = self
.ext
.get_trie_nodes_count()
.checked_sub(&nodes_before)
.ok_or(InconsistentStateError::IntegerOverflow)?;
#[cfg(feature = "io_trace")]
tracing::trace!(
target = "io_tracer",
storage_op = "exists",
key = base64(&key),
tn_mem_reads = nodes_delta.mem_reads,
tn_db_reads = nodes_delta.db_reads,
);
self.gas_counter.add_trie_fees(&nodes_delta)?;
Ok(res? as u64)
}
#[cfg(feature = "sandbox")]
pub fn sandbox_debug_log(&mut self, len: u64, ptr: u64) -> Result<()> {
let message = self.sandbox_get_utf8_string(len, ptr)?;
tracing::debug!(target: "sandbox", message = &message[..]);
Ok(())
}
pub fn storage_iter_prefix(&mut self, _prefix_len: u64, _prefix_ptr: u64) -> Result<u64> {
Err(VMLogicError::HostError(HostError::Deprecated {
method_name: "storage_iter_prefix".to_string(),
}))
}
pub fn storage_iter_range(
&mut self,
_start_len: u64,
_start_ptr: u64,
_end_len: u64,
_end_ptr: u64,
) -> Result<u64> {
Err(VMLogicError::HostError(HostError::Deprecated {
method_name: "storage_iter_range".to_string(),
}))
}
pub fn storage_iter_next(
&mut self,
_iterator_id: u64,
_key_register_id: u64,
_value_register_id: u64,
) -> Result<u64> {
Err(VMLogicError::HostError(HostError::Deprecated {
method_name: "storage_iter_next".to_string(),
}))
}
pub fn compute_outcome(self) -> VMOutcome {
let burnt_gas = self.gas_counter.burnt_gas();
let used_gas = self.gas_counter.used_gas();
let mut profile = self.gas_counter.profile_data();
profile.compute_wasm_instruction_cost(burnt_gas);
let compute_usage = profile.total_compute_usage(&self.config.ext_costs);
VMOutcome {
balance: self.current_account_balance,
storage_usage: self.current_storage_usage,
return_data: self.return_data,
burnt_gas,
used_gas,
compute_usage,
logs: self.logs,
profile,
aborted: None,
}
}
pub fn add_contract_loading_fee(&mut self, code_len: u64) -> Result<()> {
self.gas_counter.pay_per(contract_loading_bytes, code_len)?;
self.gas_counter.pay_base(contract_loading_base)
}
pub fn gas_counter_pointer(&mut self) -> *mut FastGasCounter {
self.gas_counter.gas_counter_raw_ptr()
}
pub fn process_gas_limit(&mut self) -> HostError {
let new_burn_gas = self.gas_counter.burnt_gas();
let new_used_gas = self.gas_counter.used_gas();
self.gas_counter.process_gas_limit(new_burn_gas, new_used_gas)
}
pub fn pay_action_base(&mut self, action: ActionCosts, sir: bool) -> Result<()> {
let base_fee = self.fees_config.fee(action);
let burn_gas = base_fee.send_fee(sir);
let use_gas =
burn_gas.checked_add(base_fee.exec_fee()).ok_or(HostError::IntegerOverflow)?;
self.gas_counter.pay_action_accumulated(burn_gas, use_gas, action)
}
pub fn pay_action_per_byte(
&mut self,
action: ActionCosts,
num_bytes: u64,
sir: bool,
) -> Result<()> {
let per_byte_fee = self.fees_config.fee(action);
let burn_gas =
num_bytes.checked_mul(per_byte_fee.send_fee(sir)).ok_or(HostError::IntegerOverflow)?;
let use_gas = burn_gas
.checked_add(
num_bytes.checked_mul(per_byte_fee.exec_fee()).ok_or(HostError::IntegerOverflow)?,
)
.ok_or(HostError::IntegerOverflow)?;
self.gas_counter.pay_action_accumulated(burn_gas, use_gas, action)
}
pub fn before_loading_executable(
&mut self,
method_name: &str,
wasm_code_bytes: usize,
) -> std::result::Result<(), super::errors::FunctionCallError> {
if method_name.is_empty() {
let error = super::errors::FunctionCallError::MethodResolveError(
super::errors::MethodResolveError::MethodEmptyName,
);
return Err(error);
}
if self.config.fix_contract_loading_cost {
if self.add_contract_loading_fee(wasm_code_bytes as u64).is_err() {
let error =
super::errors::FunctionCallError::HostError(super::HostError::GasExceeded);
return Err(error);
}
}
Ok(())
}
pub fn after_loading_executable(
&mut self,
wasm_code_bytes: usize,
) -> std::result::Result<(), super::errors::FunctionCallError> {
if !self.config.fix_contract_loading_cost {
if self.add_contract_loading_fee(wasm_code_bytes as u64).is_err() {
return Err(super::errors::FunctionCallError::HostError(
super::HostError::GasExceeded,
));
}
}
Ok(())
}
}
#[derive(PartialEq)]
pub struct VMOutcome {
pub balance: Balance,
pub storage_usage: StorageUsage,
pub return_data: ReturnData,
pub burnt_gas: Gas,
pub used_gas: Gas,
pub compute_usage: Compute,
pub logs: Vec<String>,
pub profile: ProfileDataV3,
pub aborted: Option<FunctionCallError>,
}
impl VMOutcome {
pub fn abort(logic: VMLogic, error: FunctionCallError) -> VMOutcome {
let mut outcome = logic.compute_outcome();
outcome.aborted = Some(error);
outcome
}
pub fn ok(logic: VMLogic) -> VMOutcome {
logic.compute_outcome()
}
pub fn nop_outcome(error: FunctionCallError) -> VMOutcome {
VMOutcome {
balance: 0,
storage_usage: 0,
return_data: ReturnData::None,
burnt_gas: 0,
used_gas: 0,
compute_usage: 0,
logs: Vec::new(),
profile: ProfileDataV3::default(),
aborted: Some(error),
}
}
pub fn abort_but_nop_outcome_in_old_protocol(
logic: VMLogic,
error: FunctionCallError,
) -> VMOutcome {
if logic.config.fix_contract_loading_cost {
Self::abort(logic, error)
} else {
Self::nop_outcome(error)
}
}
}
impl std::fmt::Debug for VMOutcome {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let return_data_str = match &self.return_data {
ReturnData::None => "None".to_string(),
ReturnData::ReceiptIndex(_) => "Receipt".to_string(),
ReturnData::Value(v) => format!("Value [{} bytes]", v.len()),
};
write!(
f,
"VMOutcome: balance {} storage_usage {} return data {} burnt gas {} used gas {}",
self.balance, self.storage_usage, return_data_str, self.burnt_gas, self.used_gas
)?;
if let Some(err) = &self.aborted {
write!(f, " failed with {err}")?;
}
Ok(())
}
}