use crate::config::Config;
use crate::context::VMContext;
use crate::dependencies::{External, MemoryLike};
use crate::gas_counter::GasCounter;
use crate::types::{
AccountId, Balance, Gas, IteratorIndex, PromiseIndex, PromiseResult, ReceiptIndex, ReturnData,
StorageUsage,
};
use crate::{HostError, HostErrorOrStorageError};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::mem::size_of;
type Result<T> = ::std::result::Result<T, HostErrorOrStorageError>;
pub struct VMLogic<'a> {
ext: &'a mut dyn External,
context: VMContext,
config: &'a Config,
promise_results: &'a [PromiseResult],
memory: &'a mut dyn MemoryLike,
current_account_balance: Balance,
current_storage_usage: StorageUsage,
gas_counter: GasCounter,
return_data: ReturnData,
logs: Vec<String>,
registers: HashMap<u64, Vec<u8>>,
valid_iterators: HashSet<IteratorIndex>,
invalid_iterators: HashSet<IteratorIndex>,
promises: Vec<Promise>,
receipt_to_account: HashMap<ReceiptIndex, AccountId>,
}
#[derive(Debug)]
struct Promise {
promise_to_receipt: PromiseToReceipts,
}
#[derive(Debug)]
enum PromiseToReceipts {
Receipt(ReceiptIndex),
NotReceipt(Vec<ReceiptIndex>),
}
impl<'a> VMLogic<'a> {
pub fn new(
ext: &'a mut dyn External,
context: VMContext,
config: &'a Config,
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 gas_counter =
GasCounter::new(config.max_gas_burnt, context.prepaid_gas, context.is_view);
Self {
ext,
context,
config,
promise_results,
memory,
current_account_balance,
current_storage_usage,
gas_counter,
return_data: ReturnData::None,
logs: vec![],
registers: HashMap::new(),
valid_iterators: HashSet::new(),
invalid_iterators: HashSet::new(),
promises: vec![],
receipt_to_account: HashMap::new(),
}
}
fn try_fit_mem(memory: &dyn MemoryLike, offset: u64, len: u64) -> Result<()> {
if memory.fits_memory(offset, len) {
Ok(())
} else {
Err(HostError::MemoryAccessViolation.into())
}
}
fn memory_get_into(memory: &dyn MemoryLike, offset: u64, buf: &mut [u8]) -> Result<()> {
Self::try_fit_mem(memory, offset, buf.len() as u64)?;
memory.read_memory(offset, buf);
Ok(())
}
fn memory_get(memory: &dyn MemoryLike, offset: u64, len: u64) -> Result<Vec<u8>> {
Self::try_fit_mem(memory, offset, len)?;
let mut buf = vec![0; len as usize];
memory.read_memory(offset, &mut buf);
Ok(buf)
}
fn memory_set(memory: &mut dyn MemoryLike, offset: u64, buf: &[u8]) -> Result<()> {
Self::try_fit_mem(memory, offset, buf.len() as _)?;
memory.write_memory(offset, buf);
Ok(())
}
#[allow(dead_code)]
fn memory_set_u128(memory: &mut dyn MemoryLike, offset: u64, value: u128) -> Result<()> {
let data: [u8; size_of::<u128>()] = value.to_le_bytes();
Self::memory_set(memory, offset, &data)
}
fn memory_get_u128(memory: &dyn MemoryLike, offset: u64) -> Result<u128> {
let mut array = [0u8; size_of::<u128>()];
Self::memory_get_into(memory, offset, &mut array)?;
Ok(u128::from_le_bytes(array))
}
fn memory_get_array_u64(
memory: &dyn MemoryLike,
offset: u64,
num_elements: u64,
) -> Result<Vec<u64>> {
let memory_len = num_elements
.checked_mul(size_of::<u64>() as u64)
.ok_or(HostError::MemoryAccessViolation)?;
let data = Self::memory_get(memory, offset, memory_len)?;
Ok(data
.chunks(size_of::<u64>())
.map(|buf| {
assert_eq!(buf.len(), size_of::<u64>());
let mut array = [0u8; size_of::<u64>()];
array.copy_from_slice(buf);
u64::from_le_bytes(array)
})
.collect())
}
fn read_memory_u32(memory: &dyn MemoryLike, ptr: u64) -> Result<u32> {
let mut slice = [0u8; size_of::<u32>()];
let buf = Self::memory_get(memory, ptr, size_of::<u32>() as u64)?;
slice.copy_from_slice(&buf);
Ok(u32::from_le_bytes(slice))
}
fn get_from_memory_or_register(
memory: &dyn MemoryLike,
registers: &HashMap<u64, Vec<u8>>,
offset: u64,
len: u64,
) -> Result<Vec<u8>> {
if len != std::u64::MAX {
Self::memory_get(memory, offset, len)
} else {
registers.get(&offset).ok_or(HostError::InvalidRegisterId.into()).map(|v| v.clone())
}
}
fn get_utf8_string(&mut self, len: u64, ptr: u64) -> Result<String> {
self.gas_counter.pay_base(self.config.runtime_fees.ext_costs.log_base)?;
let mut buf;
let max_len = self.config.max_log_len;
if len != std::u64::MAX {
if len > max_len {
return Err(HostError::BadUTF8.into());
}
self.gas_counter.pay_per_byte(self.config.runtime_fees.ext_costs.log_per_byte, len)?;
buf = Self::memory_get(self.memory, ptr, len)?;
} else {
buf = vec![];
for i in 0..=max_len {
self.gas_counter
.pay_per_byte(self.config.runtime_fees.ext_costs.log_per_byte, 1)?;
Self::try_fit_mem(self.memory, ptr + i, 1)?;
let el = self.memory.read_memory_u8(ptr + i);
if el == 0 {
break;
}
if i == max_len {
return Err(HostError::BadUTF8.into());
}
buf.push(el);
}
}
String::from_utf8(buf).map_err(|_| HostError::BadUTF8.into())
}
fn get_utf16_string(&mut self, len: u64, ptr: u64) -> Result<String> {
self.gas_counter.pay_base(self.config.runtime_fees.ext_costs.log_base)?;
let mut u16_buffer = Vec::new();
let max_len = self.config.max_log_len;
if len != std::u64::MAX {
let input = Self::memory_get(self.memory, ptr, len)?;
if len % 2 != 0 || len > max_len {
return Err(HostError::BadUTF16.into());
}
self.gas_counter.pay_per_byte(self.config.runtime_fees.ext_costs.log_per_byte, len)?;
for i in 0..((len / 2) as usize) {
u16_buffer
.push(u16::from_le_bytes([input[i as usize * 2], input[i as usize * 2 + 1]]));
}
} else {
let limit = max_len / size_of::<u16>() as u64;
for i in 0..=limit {
self.gas_counter.pay_per_byte(
self.config.runtime_fees.ext_costs.log_per_byte,
size_of::<u16>() as u64,
)?;
let start = ptr + i * size_of::<u16>() as u64;
Self::try_fit_mem(self.memory, start, size_of::<u16>() as u64)?;
let lo = self.memory.read_memory_u8(start);
let hi = self.memory.read_memory_u8(start + 1);
if (lo, hi) == (0, 0) {
break;
}
if i == limit {
return Err(HostError::BadUTF16.into());
}
u16_buffer.push(u16::from_le_bytes([lo, hi]));
}
}
String::from_utf16(&u16_buffer).map_err(|_| HostError::BadUTF16.into())
}
pub fn read_register(&mut self, register_id: u64, ptr: u64) -> Result<()> {
let Self { registers, memory, config, .. } = self;
self.gas_counter.pay_base(config.runtime_fees.ext_costs.read_register_base)?;
let register = registers.get(®ister_id).ok_or(HostError::InvalidRegisterId)?;
self.gas_counter.pay_per_byte(
config.runtime_fees.ext_costs.read_register_byte,
register.len() as u64,
)?;
Self::memory_set(*memory, ptr, register)
}
pub fn register_len(&mut self, register_id: u64) -> Result<u64> {
Ok(self.registers.get(®ister_id).map(|r| r.len() as _).unwrap_or(std::u64::MAX))
}
pub fn write_register(&mut self, register_id: u64, data: &[u8]) -> Result<()> {
let Self { registers, config, gas_counter, .. } = self;
Self::internal_write_register(registers, gas_counter, config, register_id, data)
}
fn internal_write_register(
registers: &mut HashMap<u64, Vec<u8>>,
gas_counter: &mut GasCounter,
config: &Config,
register_id: u64,
data: &[u8],
) -> Result<()> {
gas_counter.pay_base(config.runtime_fees.ext_costs.write_register_base)?;
gas_counter
.pay_per_byte(config.runtime_fees.ext_costs.write_register_byte, data.len() as u64)?;
if data.len() as u64 > config.max_register_size
|| registers.len() as u64 == config.max_number_registers
{
return Err(HostError::MemoryAccessViolation.into());
}
let register = registers.entry(register_id).or_insert_with(Vec::new);
register.clear();
register.reserve(data.len());
register.extend_from_slice(data);
let usage: usize =
registers.values().map(|v| size_of::<u64>() + v.len() * size_of::<u8>()).sum();
if usage as u64 > config.registers_memory_limit {
Err(HostError::MemoryAccessViolation.into())
} else {
Ok(())
}
}
pub fn current_account_id(&mut self, register_id: u64) -> Result<()> {
let Self { context, registers, gas_counter, config, .. } = self;
gas_counter.pay_base(config.runtime_fees.ext_costs.current_account_id)?;
let data = context.current_account_id.as_bytes();
gas_counter.pay_per_byte(
config.runtime_fees.ext_costs.signer_account_id_byte,
data.len() as u64,
)?;
Self::internal_write_register(registers, gas_counter, config, register_id, data)
}
pub fn signer_account_id(&mut self, register_id: u64) -> Result<()> {
let Self { context, registers, gas_counter, config, .. } = self;
gas_counter.pay_base(config.runtime_fees.ext_costs.signer_account_id)?;
if context.is_view {
return Err(HostError::ProhibitedInView("signer_account_id".to_string()).into());
}
let data = context.signer_account_id.as_bytes();
gas_counter.pay_per_byte(
config.runtime_fees.ext_costs.signer_account_id_byte,
data.len() as u64,
)?;
Self::internal_write_register(registers, gas_counter, config, register_id, data)
}
pub fn signer_account_pk(&mut self, register_id: u64) -> Result<()> {
let Self { context, registers, gas_counter, config, .. } = self;
gas_counter.pay_base(config.runtime_fees.ext_costs.signer_account_id)?;
if context.is_view {
return Err(HostError::ProhibitedInView("signer_account_pk".to_string()).into());
}
let data = context.signer_account_pk.as_slice();
gas_counter.pay_per_byte(
config.runtime_fees.ext_costs.signer_account_pk_byte,
data.len() as u64,
)?;
Self::internal_write_register(registers, gas_counter, config, register_id, data)
}
pub fn predecessor_account_id(&mut self, register_id: u64) -> Result<()> {
let Self { context, registers, gas_counter, config, .. } = self;
gas_counter.pay_base(config.runtime_fees.ext_costs.predecessor_account_id)?;
if context.is_view {
return Err(HostError::ProhibitedInView("predecessor_account_id".to_string()).into());
}
let data = context.predecessor_account_id.as_bytes();
gas_counter.pay_per_byte(
config.runtime_fees.ext_costs.predecessor_account_id_byte,
data.len() as u64,
)?;
Self::internal_write_register(registers, gas_counter, config, register_id, data)
}
pub fn input(&mut self, register_id: u64) -> Result<()> {
let Self { context, registers, gas_counter, config, .. } = self;
gas_counter.pay_base(config.runtime_fees.ext_costs.input_base)?;
gas_counter.pay_per_byte(
config.runtime_fees.ext_costs.input_per_byte,
context.input.len() as u64,
)?;
Self::internal_write_register(registers, gas_counter, config, register_id, &context.input)
}
pub fn block_index(&mut self) -> Result<u64> {
self.gas_counter.pay_base(self.config.runtime_fees.ext_costs.block_index)?;
Ok(self.context.block_index)
}
pub fn block_timestamp(&mut self) -> Result<u64> {
self.gas_counter.pay_base(self.config.runtime_fees.ext_costs.block_timestamp)?;
Ok(self.context.block_timestamp)
}
pub fn storage_usage(&mut self) -> Result<StorageUsage> {
self.gas_counter.pay_base(self.config.runtime_fees.ext_costs.storage_usage)?;
Ok(self.current_storage_usage)
}
pub fn account_balance(&mut self, balance_ptr: u64) -> Result<()> {
self.gas_counter.pay_base(self.config.runtime_fees.ext_costs.account_balance)?;
Self::memory_set(self.memory, balance_ptr, &self.current_account_balance.to_le_bytes())
}
pub fn attached_deposit(&mut self, balance_ptr: u64) -> Result<()> {
self.gas_counter.pay_base(self.config.runtime_fees.ext_costs.attached_deposit)?;
if self.context.is_view {
return Err(HostError::ProhibitedInView("attached_deposit".to_string()).into());
}
Self::memory_set(self.memory, balance_ptr, &self.context.attached_deposit.to_le_bytes())
}
pub fn prepaid_gas(&mut self) -> Result<Gas> {
self.gas_counter.pay_base(self.config.runtime_fees.ext_costs.prepaid_gas)?;
if self.context.is_view {
return Err(HostError::ProhibitedInView("prepaid_gas".to_string()).into());
}
Ok(self.context.prepaid_gas)
}
pub fn used_gas(&mut self) -> Result<Gas> {
self.gas_counter.pay_base(self.config.runtime_fees.ext_costs.used_gas)?;
if self.context.is_view {
return Err(HostError::ProhibitedInView("used_gas".to_string()).into());
}
Ok(self.gas_counter.used_gas())
}
pub fn random_seed(&mut self, register_id: u64) -> Result<()> {
let Self { context, registers, gas_counter, config, .. } = self;
gas_counter.pay_base(config.runtime_fees.ext_costs.random_seed_base)?;
gas_counter.pay_per_byte(
config.runtime_fees.ext_costs.random_seed_per_byte,
context.random_seed.len() as u64,
)?;
Self::internal_write_register(
registers,
gas_counter,
config,
register_id,
&context.random_seed,
)
}
pub fn sha256(&mut self, value_len: u64, value_ptr: u64, register_id: u64) -> Result<()> {
let Self { memory, registers, gas_counter, config, ext, .. } = self;
gas_counter.pay_base(config.runtime_fees.ext_costs.sha256)?;
let value = Self::get_from_memory_or_register(*memory, registers, value_ptr, value_len)?;
gas_counter.pay_per_byte(config.runtime_fees.ext_costs.sha256_byte, value.len() as u64)?;
let value_hash = ext.sha256(&value)?;
Self::internal_write_register(registers, gas_counter, config, register_id, &value_hash)
}
pub fn gas(&mut self, gas_amount: u32) -> Result<()> {
self.gas_counter.deduct_gas(Gas::from(gas_amount), Gas::from(gas_amount))
}
fn pay_gas_for_new_receipt(&mut self, sir: bool, data_dependencies: &[bool]) -> Result<()> {
let runtime_fees_cfg = &self.config.runtime_fees;
let mut burn_gas = runtime_fees_cfg.action_receipt_creation_config.send_fee(sir);
let mut use_gas = runtime_fees_cfg.action_receipt_creation_config.exec_fee();
for dep in data_dependencies {
burn_gas = burn_gas
.checked_add(runtime_fees_cfg.data_receipt_creation_config.base_cost.send_fee(*dep))
.ok_or(HostError::IntegerOverflow)?
.checked_add(runtime_fees_cfg.data_receipt_creation_config.base_cost.exec_fee())
.ok_or(HostError::IntegerOverflow)?;
}
use_gas = use_gas.checked_add(burn_gas).ok_or(HostError::IntegerOverflow)?;
self.gas_counter.deduct_gas(burn_gas, use_gas)
}
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> {
if self.context.is_view {
return Err(HostError::ProhibitedInView("promise_and".to_string()).into());
}
self.gas_counter.pay_base(self.config.runtime_fees.ext_costs.promise_and_base)?;
self.gas_counter.pay_per_byte(
self.config.runtime_fees.ext_costs.promise_and_per_promise,
promise_idx_count * size_of::<u64>() as u64,
)?;
let promise_indices =
Self::memory_get_array_u64(self.memory, promise_idx_ptr, promise_idx_count)?;
let mut receipt_dependencies = vec![];
for promise_idx in &promise_indices {
let promise =
self.promises.get(*promise_idx as usize).ok_or(HostError::InvalidPromiseIndex)?;
match &promise.promise_to_receipt {
PromiseToReceipts::Receipt(receipt_idx) => {
receipt_dependencies.push(*receipt_idx);
}
PromiseToReceipts::NotReceipt(receipt_indices) => {
receipt_dependencies.extend(receipt_indices.clone());
}
}
}
let new_promise_idx = self.promises.len() as PromiseIndex;
self.promises.push(Promise {
promise_to_receipt: PromiseToReceipts::NotReceipt(receipt_dependencies),
});
Ok(new_promise_idx)
}
pub fn promise_batch_create(
&mut self,
account_id_len: u64,
account_id_ptr: u64,
) -> Result<u64> {
if self.context.is_view {
return Err(HostError::ProhibitedInView("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.clone())?;
self.receipt_to_account.insert(new_receipt_idx, account_id);
let promise_idx = self.promises.len() as PromiseIndex;
self.promises
.push(Promise { promise_to_receipt: PromiseToReceipts::Receipt(new_receipt_idx) });
Ok(promise_idx)
}
pub fn promise_batch_then(
&mut self,
promise_idx: u64,
account_id_len: u64,
account_id_ptr: u64,
) -> Result<u64> {
if self.context.is_view {
return Err(HostError::ProhibitedInView("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)?;
let receipt_dependencies = match &promise.promise_to_receipt {
PromiseToReceipts::Receipt(receipt_idx) => vec![*receipt_idx],
PromiseToReceipts::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.receipt_to_account
.get(receipt_idx)
.expect("promises and receipt_to_account should be consistent.")
== &account_id
})
.collect();
self.pay_gas_for_new_receipt(sir, &deps)?;
let new_receipt_idx = self.ext.create_receipt(receipt_dependencies, account_id.clone())?;
self.receipt_to_account.insert(new_receipt_idx, account_id);
let new_promise_idx = self.promises.len() as PromiseIndex;
self.promises
.push(Promise { promise_to_receipt: PromiseToReceipts::Receipt(new_receipt_idx) });
Ok(new_promise_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)?;
let receipt_idx = match &promise.promise_to_receipt {
PromiseToReceipts::Receipt(receipt_idx) => Ok(*receipt_idx),
PromiseToReceipts::NotReceipt(_) => Err(HostError::CannotAppendActionToJointPromise),
}?;
let account_id = self
.receipt_to_account
.get(&receipt_idx)
.expect("promises and receipt_to_account should be consistent.");
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<()> {
if self.context.is_view {
return Err(HostError::ProhibitedInView(
"promise_batch_action_create_account".to_string(),
)
.into());
}
let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?;
self.gas_counter.pay_action_base(
&self.config.runtime_fees.action_creation_config.create_account_cost,
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<()> {
if self.context.is_view {
return Err(HostError::ProhibitedInView(
"promise_batch_action_deploy_contract".to_string(),
)
.into());
}
let code =
Self::get_from_memory_or_register(self.memory, &self.registers, code_ptr, code_len)?;
let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?;
let num_bytes = code.len() as u64;
self.gas_counter.pay_action_base(
&self.config.runtime_fees.action_creation_config.deploy_contract_cost,
sir,
)?;
self.gas_counter.pay_action_per_byte(
&self.config.runtime_fees.action_creation_config.deploy_contract_cost_per_byte,
num_bytes,
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<()> {
if self.context.is_view {
return Err(HostError::ProhibitedInView(
"promise_batch_action_function_call".to_string(),
)
.into());
}
let amount = Self::memory_get_u128(self.memory, amount_ptr)?;
let method_name = Self::get_from_memory_or_register(
self.memory,
&self.registers,
method_name_ptr,
method_name_len,
)?;
if method_name.is_empty() {
return Err(HostError::EmptyMethodName.into());
}
let arguments = Self::get_from_memory_or_register(
self.memory,
&self.registers,
arguments_ptr,
arguments_len,
)?;
let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?;
let num_bytes = (method_name.len() + arguments.len()) as u64;
self.gas_counter.pay_action_base(
&self.config.runtime_fees.action_creation_config.function_call_cost,
sir,
)?;
self.gas_counter.pay_action_per_byte(
&self.config.runtime_fees.action_creation_config.function_call_cost_per_byte,
num_bytes,
sir,
)?;
self.gas_counter.deduct_gas(0, gas)?;
self.deduct_balance(amount)?;
self.ext.append_action_function_call(receipt_idx, method_name, arguments, amount, gas)?;
Ok(())
}
pub fn promise_batch_action_transfer(
&mut self,
promise_idx: u64,
amount_ptr: u64,
) -> Result<()> {
if self.context.is_view {
return Err(
HostError::ProhibitedInView("promise_batch_action_transfer".to_string()).into()
);
}
let amount = Self::memory_get_u128(self.memory, amount_ptr)?;
let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?;
self.gas_counter
.pay_action_base(&self.config.runtime_fees.action_creation_config.transfer_cost, sir)?;
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<()> {
if self.context.is_view {
return Err(
HostError::ProhibitedInView("promise_batch_action_stake".to_string()).into()
);
}
let amount = Self::memory_get_u128(self.memory, amount_ptr)?;
let public_key = Self::get_from_memory_or_register(
self.memory,
&self.registers,
public_key_ptr,
public_key_len,
)?;
let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?;
self.gas_counter
.pay_action_base(&self.config.runtime_fees.action_creation_config.stake_cost, sir)?;
self.deduct_balance(amount)?;
self.ext.append_action_stake(receipt_idx, amount, public_key)?;
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<()> {
if self.context.is_view {
return Err(HostError::ProhibitedInView(
"promise_batch_action_add_key_with_full_access".to_string(),
)
.into());
}
let public_key = Self::get_from_memory_or_register(
self.memory,
&self.registers,
public_key_ptr,
public_key_len,
)?;
let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?;
self.gas_counter.pay_action_base(
&self.config.runtime_fees.action_creation_config.add_key_cost.full_access_cost,
sir,
)?;
self.ext.append_action_add_key_with_full_access(receipt_idx, public_key, 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<()> {
if self.context.is_view {
return Err(HostError::ProhibitedInView(
"promise_batch_action_add_key_with_function_call".to_string(),
)
.into());
}
let public_key = Self::get_from_memory_or_register(
self.memory,
&self.registers,
public_key_ptr,
public_key_len,
)?;
let allowance = Self::memory_get_u128(self.memory, 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 method_names = Self::get_from_memory_or_register(
self.memory,
&self.registers,
method_names_ptr,
method_names_len,
)?;
let method_names =
method_names
.split(|c| *c == b',')
.map(|v| {
if v.is_empty() {
Err(HostError::EmptyMethodName.into())
} else {
Ok(v.to_vec())
}
})
.collect::<Result<Vec<_>>>()?;
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.gas_counter.pay_action_base(
&self.config.runtime_fees.action_creation_config.add_key_cost.function_call_cost,
sir,
)?;
self.gas_counter.pay_action_per_byte(
&self
.config
.runtime_fees
.action_creation_config
.add_key_cost
.function_call_cost_per_byte,
num_bytes,
sir,
)?;
self.ext.append_action_add_key_with_function_call(
receipt_idx,
public_key,
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<()> {
if self.context.is_view {
return Err(
HostError::ProhibitedInView("promise_batch_action_delete_key".to_string()).into()
);
}
let public_key = Self::get_from_memory_or_register(
self.memory,
&self.registers,
public_key_ptr,
public_key_len,
)?;
let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?;
self.gas_counter.pay_action_base(
&self.config.runtime_fees.action_creation_config.delete_key_cost,
sir,
)?;
self.ext.append_action_delete_key(receipt_idx, public_key)?;
Ok(())
}
pub fn promise_batch_action_delete_account(
&mut self,
promise_idx: u64,
beneficiary_id_len: u64,
beneficiary_id_ptr: u64,
) -> Result<()> {
if self.context.is_view {
return Err(HostError::ProhibitedInView(
"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.gas_counter.pay_action_base(
&self.config.runtime_fees.action_creation_config.delete_account_cost,
sir,
)?;
self.ext.append_action_delete_account(receipt_idx, beneficiary_id)?;
Ok(())
}
pub fn promise_results_count(&self) -> Result<u64> {
if self.context.is_view {
return Err(HostError::ProhibitedInView("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> {
let Self { promise_results, registers, gas_counter, config, context, .. } = self;
if context.is_view {
return Err(HostError::ProhibitedInView("promise_result".to_string()).into());
}
gas_counter.pay_base(config.runtime_fees.ext_costs.promise_result_base)?;
match promise_results
.get(result_idx as usize)
.ok_or(HostError::InvalidPromiseResultIndex)?
{
PromiseResult::NotReady => Ok(0),
PromiseResult::Successful(data) => {
gas_counter.pay_per_byte(
config.runtime_fees.ext_costs.promise_result_byte,
data.len() as u64,
)?;
Self::internal_write_register(registers, gas_counter, config, register_id, data)?;
Ok(1)
}
PromiseResult::Failed => Ok(2),
}
}
pub fn promise_return(&mut self, promise_idx: u64) -> Result<()> {
if self.context.is_view {
return Err(HostError::ProhibitedInView("promise_return".to_string()).into());
}
self.gas_counter.pay_base(self.config.runtime_fees.ext_costs.promise_return)?;
match self
.promises
.get(promise_idx as usize)
.ok_or(HostError::InvalidPromiseIndex)?
.promise_to_receipt
{
PromiseToReceipts::Receipt(receipt_idx) => {
self.return_data = ReturnData::ReceiptIndex(receipt_idx);
Ok(())
}
PromiseToReceipts::NotReceipt(_) => Err(HostError::CannotReturnJointPromise.into()),
}
}
pub fn value_return(&mut self, value_len: u64, value_ptr: u64) -> Result<()> {
let return_val =
Self::get_from_memory_or_register(self.memory, &self.registers, value_ptr, value_len)?;
let mut burn_gas: Gas = 0;
let num_bytes = return_val.len() as u64;
let data_cfg = &self.config.runtime_fees.data_receipt_creation_config;
for data_receiver in &self.context.output_data_receivers {
let sir = data_receiver == &self.context.current_account_id;
burn_gas = burn_gas
.checked_add(
data_cfg
.cost_per_byte
.send_fee(sir)
.checked_add(data_cfg.cost_per_byte.exec_fee())
.ok_or(HostError::IntegerOverflow)?
.checked_mul(num_bytes)
.ok_or(HostError::IntegerOverflow)?,
)
.ok_or(HostError::IntegerOverflow)?;
}
self.gas_counter.deduct_gas(burn_gas, burn_gas)?;
self.return_data = ReturnData::Value(return_val);
Ok(())
}
pub fn panic(&self) -> Result<()> {
Err(HostError::GuestPanic("explicit guest panic".to_string()).into())
}
pub fn panic_utf8(&mut self, len: u64, ptr: u64) -> Result<()> {
Err(HostError::GuestPanic(self.get_utf8_string(len, ptr)?).into())
}
pub fn log_utf8(&mut self, len: u64, ptr: u64) -> Result<()> {
let message = format!("LOG: {}", self.get_utf8_string(len, ptr)?);
self.logs.push(message);
Ok(())
}
pub fn log_utf16(&mut self, len: u64, ptr: u64) -> Result<()> {
let message = format!("LOG: {}", self.get_utf16_string(len, ptr)?);
self.logs.push(message);
Ok(())
}
pub fn abort(&mut self, msg_ptr: u32, filename_ptr: u32, line: u32, col: u32) -> Result<()> {
if msg_ptr < 4 || filename_ptr < 4 {
return Err(HostError::BadUTF16.into());
}
let msg_len = Self::read_memory_u32(self.memory, (msg_ptr - 4) as u64)?;
let filename_len = Self::read_memory_u32(self.memory, (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.logs.push(format!("ABORT: {}", message));
Err(HostError::GuestPanic(message).into())
}
pub fn read_and_parse_account_id(&self, ptr: u64, len: u64) -> Result<AccountId> {
let buf = Self::get_from_memory_or_register(self.memory, &self.registers, ptr, len)?;
let account_id = AccountId::from_utf8(buf).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> {
let Self {
memory,
registers,
gas_counter,
config,
valid_iterators,
invalid_iterators,
ext,
..
} = self;
gas_counter.pay_base(config.runtime_fees.ext_costs.storage_write_base)?;
for invalidated_iter_idx in valid_iterators.drain() {
ext.storage_iter_drop(invalidated_iter_idx)?;
invalid_iterators.insert(invalidated_iter_idx);
}
let key = Self::get_from_memory_or_register(*memory, registers, key_ptr, key_len)?;
let value = Self::get_from_memory_or_register(*memory, registers, value_ptr, value_len)?;
gas_counter.pay_per_byte(
config.runtime_fees.ext_costs.storage_write_value_byte,
key.len() as u64,
)?;
gas_counter.pay_per_byte(
config.runtime_fees.ext_costs.storage_write_value_byte,
value.len() as u64,
)?;
let evicted = self.ext.storage_set(&key, &value)?;
let storage_config = &config.runtime_fees.storage_usage_config;
match evicted {
Some(old_value) => {
self.current_storage_usage -=
(old_value.len() as u64) * storage_config.value_cost_per_byte;
self.current_storage_usage +=
value.len() as u64 * storage_config.value_cost_per_byte;
Self::internal_write_register(
registers,
gas_counter,
config,
register_id,
&old_value,
)?;
Ok(1)
}
None => {
self.current_storage_usage +=
value.len() as u64 * storage_config.value_cost_per_byte;
self.current_storage_usage += key.len() as u64 * storage_config.key_cost_per_byte;
self.current_storage_usage += storage_config.data_record_cost;
Ok(0)
}
}
}
pub fn storage_read(&mut self, key_len: u64, key_ptr: u64, register_id: u64) -> Result<u64> {
let Self { ext, memory, registers, gas_counter, config, .. } = self;
gas_counter.pay_base(config.runtime_fees.ext_costs.storage_read_base)?;
let key = Self::get_from_memory_or_register(*memory, registers, key_ptr, key_len)?;
gas_counter
.pay_per_byte(config.runtime_fees.ext_costs.storage_read_key_byte, key.len() as u64)?;
let read = ext.storage_get(&key)?;
match read {
Some(value) => {
gas_counter.pay_per_byte(
config.runtime_fees.ext_costs.storage_read_key_byte,
value.len() as u64,
)?;
Self::internal_write_register(registers, gas_counter, 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> {
let Self {
ext,
memory,
registers,
gas_counter,
config,
valid_iterators,
invalid_iterators,
..
} = self;
gas_counter.pay_base(config.runtime_fees.ext_costs.storage_remove_base)?;
for invalidated_iter_idx in valid_iterators.drain() {
ext.storage_iter_drop(invalidated_iter_idx)?;
invalid_iterators.insert(invalidated_iter_idx);
}
let key = Self::get_from_memory_or_register(*memory, registers, key_ptr, key_len)?;
gas_counter.pay_per_byte(
config.runtime_fees.ext_costs.storage_remove_key_byte,
key.len() as u64,
)?;
let removed = ext.storage_remove(&key)?;
let storage_config = &config.runtime_fees.storage_usage_config;
match removed {
Some(value) => {
gas_counter.pay_per_byte(
config.runtime_fees.ext_costs.storage_remove_ret_value_byte,
value.len() as u64,
)?;
self.current_storage_usage -=
(value.len() as u64) * storage_config.value_cost_per_byte;
self.current_storage_usage -= key.len() as u64 * storage_config.key_cost_per_byte;
self.current_storage_usage -= storage_config.data_record_cost;
Self::internal_write_register(registers, gas_counter, 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(self.config.runtime_fees.ext_costs.storage_has_key_base)?;
let key =
Self::get_from_memory_or_register(self.memory, &self.registers, key_ptr, key_len)?;
self.gas_counter.pay_per_byte(
self.config.runtime_fees.ext_costs.storage_has_key_byte,
key.len() as u64,
)?;
let res = self.ext.storage_has_key(&key)?;
Ok(res as u64)
}
pub fn storage_iter_prefix(&mut self, prefix_len: u64, prefix_ptr: u64) -> Result<u64> {
self.gas_counter
.pay_base(self.config.runtime_fees.ext_costs.storage_iter_create_prefix_base)?;
let prefix = Self::get_from_memory_or_register(
self.memory,
&self.registers,
prefix_ptr,
prefix_len,
)?;
self.gas_counter.pay_per_byte(
self.config.runtime_fees.ext_costs.storage_iter_create_key_byte,
prefix.len() as u64,
)?;
let iterator_index = self.ext.storage_iter(&prefix)?;
self.valid_iterators.insert(iterator_index);
Ok(iterator_index)
}
pub fn storage_iter_range(
&mut self,
start_len: u64,
start_ptr: u64,
end_len: u64,
end_ptr: u64,
) -> Result<u64> {
self.gas_counter
.pay_base(self.config.runtime_fees.ext_costs.storage_iter_create_range_base)?;
let start_key =
Self::get_from_memory_or_register(self.memory, &self.registers, start_ptr, start_len)?;
let end_key =
Self::get_from_memory_or_register(self.memory, &self.registers, end_ptr, end_len)?;
self.gas_counter.pay_per_byte(
self.config.runtime_fees.ext_costs.storage_iter_create_key_byte,
start_key.len() as u64,
)?;
self.gas_counter.pay_per_byte(
self.config.runtime_fees.ext_costs.storage_iter_create_key_byte,
end_key.len() as u64,
)?;
let iterator_index = self.ext.storage_iter_range(&start_key, &end_key)?;
self.valid_iterators.insert(iterator_index);
Ok(iterator_index)
}
pub fn storage_iter_next(
&mut self,
iterator_id: u64,
key_register_id: u64,
value_register_id: u64,
) -> Result<u64> {
let Self {
ext, registers, gas_counter, config, valid_iterators, invalid_iterators, ..
} = self;
gas_counter.pay_base(config.runtime_fees.ext_costs.storage_iter_next_base)?;
if invalid_iterators.contains(&iterator_id) {
return Err(HostError::IteratorWasInvalidated.into());
} else if !valid_iterators.contains(&iterator_id) {
return Err(HostError::InvalidIteratorIndex.into());
}
let value = ext.storage_iter_next(iterator_id)?;
match value {
Some((key, value)) => {
gas_counter.pay_per_byte(
config.runtime_fees.ext_costs.storage_iter_next_key_byte,
key.len() as u64,
)?;
gas_counter.pay_per_byte(
config.runtime_fees.ext_costs.storage_iter_next_value_byte,
value.len() as u64,
)?;
Self::internal_write_register(
registers,
gas_counter,
config,
key_register_id,
&key,
)?;
Self::internal_write_register(
registers,
gas_counter,
config,
value_register_id,
&value,
)?;
Ok(1)
}
None => Ok(0),
}
}
pub fn outcome(self) -> VMOutcome {
VMOutcome {
balance: self.current_account_balance,
storage_usage: self.current_storage_usage,
return_data: self.return_data,
burnt_gas: self.gas_counter.burnt_gas(),
used_gas: self.gas_counter.used_gas(),
logs: self.logs,
}
}
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct VMOutcome {
pub balance: Balance,
pub storage_usage: StorageUsage,
pub return_data: ReturnData,
pub burnt_gas: Gas,
pub used_gas: Gas,
pub logs: Vec<String>,
}