Function Call

In this section we provide an explanation how the FunctionCall action execution works, what are the inputs and what are the outputs. Suppose runtime received the following ActionReceipt:


#![allow(unused)]
fn main() {
ActionReceipt {
     id: "A1",
     signer_id: "alice",
     signer_public_key: "6934...e248",
     receiver_id: "dex",
     predecessor_id: "alice",
     input_data_ids: [],
     output_data_receivers: [],
     actions: [FunctionCall { gas: 100000, deposit: 100000u128, method_name: "exchange", args: "{arg1, arg2, ...}", ... }],
 }
}

input_data_ids to PromiseResult's

ActionReceipt.input_data_ids must be satisfied before execution (see Receipt Matching). Each of ActionReceipt.input_data_ids will be converted to the PromiseResult::Successful(Vec<u8>) if data_id.data is Some(Vec<u8>) otherwise if data_id.data is None promise will be PromiseResult::Failed.

Input

The FunctionCall executes in the receiver_id account environment.

  • a vector of Promise Results which can be accessed by a promise_result import PromisesAPI promise_result)
  • the original Transaction signer_id, signer_public_key data from the ActionReceipt (e.g. method_name, args, predecessor_id, deposit, prepaid_gas (which is gas in FunctionCall))
  • a general blockchain data (e.g. block_index, block_timestamp)
  • read data from the account storage

A full list of the data available for the contract can be found in Context API and Trie

Execution

First of all, runtime does prepare the Wasm binary to be executed:

  • loads the contract code from the receiver_id account storage
  • deserializes and validates the code Wasm binary (see prepare::prepare_contract)
  • injects the gas counting function gas which will charge gas on the beginning of the each code block
  • instantiates Bindings Spec with binary and calls the FunctionCall.method_name exported function

During execution, VM does the following:

  • counts burnt gas on execution
  • counts used gas (which is burnt gas + gas attached to the new created receipts)
  • counts how accounts storage usage increased by the call
  • collects logs produced by the contract
  • sets the return data
  • creates new receipts through PromisesAPI

Output

The output of the FunctionCall:

  • storage updates - changes to the account trie storage which will be applied on a successful call
  • burnt_gas - irreversible amount of gas witch was spent on computations
  • used_gas - includes burnt_gas and gas attached to the new ActionReceipts created during the method execution. In case of failure, created ActionReceipts not going to be sent thus account will pay only for burnt_gas
  • balance - unspent account balance (account balance could be spent on deposits of newly created FunctionCalls or TransferActions to other contracts)
  • storage_usage - storage_usage after ActionReceipt application
  • logs - during contract execution, utf8/16 string log records could be created. Logs are not persistent currently.
  • new_receipts - new ActionReceipts created during the execution. These receipts are going to be sent to the respective receiver_ids (see Receipt Matching explanation)
  • result could be either ReturnData::Value(Vec<u8>) or ReturnData::ReceiptIndex(u64)`

Value Result

If applied ActionReceipt contains output_data_receivers, runtime will create DataReceipt for each of data_id and receiver_id and data equals returned value. Eventually, these DataReceipt will be delivered to the corresponding receivers.

ReceiptIndex Result

Successful result could not return any Value, but generates a bunch of new ActionReceipts instead. One example could be a callback. In this case, we assume the the new Receipt will send its Value Result to the output_data_receivers of the current ActionReceipt.

Errors

As with other actions, errors can be divided into two categories: validation error and execution error.

Validation Error

  • If there is zero gas attached to the function call, a

#![allow(unused)]
fn main() {
/// The attached amount of gas in a FunctionCall action has to be a positive number.
FunctionCallZeroAttachedGas,
}

error will be returned

  • If the length of the method name to be called exceeds max_length_method_name, a genesis parameter whose current value is 256, a

#![allow(unused)]
fn main() {
/// The length of the method name exceeded the limit in a Function Call action.
FunctionCallMethodNameLengthExceeded { length: u64, limit: u64 }
}

error is returned.

  • If the length of the argument to the function call exceeds max_arguments_length, a genesis parameter whose current value is 4194304 (4MB), a

#![allow(unused)]
fn main() {
/// The length of the arguments exceeded the limit in a Function Call action.
FunctionCallArgumentsLengthExceeded { length: u64, limit: u64 }
}

error is returned.

Execution Error

There can be three types of errors returned when applying a function call action: FunctionCallError, ExternalError, and StorageError.

  • FunctionCallError includes everything from around the execution of the wasm binary, from compiling wasm to native to traps occurred while executing the compiled native binary. More specifically, it includes the following errors:

#![allow(unused)]
fn main() {
pub enum FunctionCallError {
    /// Wasm compilation error
    CompilationError(CompilationError),
    /// Wasm binary env link error
    LinkError {
        msg: String,
    },
    /// Import/export resolve error
    MethodResolveError(MethodResolveError),
    /// A trap happened during execution of a binary
    WasmTrap(WasmTrap),
    WasmUnknownError,   
    HostError(HostError),
}
}
  • CompilationError includes errors that can occur during the compilation of wasm binary.
  • LinkError is returned when wasmer runtime is unable to link the wasm module with provided imports.
  • MethodResolveError occurs when the method in the action cannot be found in the contract code.
  • WasmTrap error happens when a trap occurs during the execution of the binary. Traps here include

#![allow(unused)]
fn main() {
pub enum WasmTrap {
    /// An `unreachable` opcode was executed.
    Unreachable,
    /// Call indirect incorrect signature trap.
    IncorrectCallIndirectSignature,
    /// Memory out of bounds trap.
    MemoryOutOfBounds,
    /// Call indirect out of bounds trap.
    CallIndirectOOB,
    /// An arithmetic exception, e.g. divided by zero.
    IllegalArithmetic,
    /// Misaligned atomic access trap.
    MisalignedAtomicAccess,
    /// Breakpoint trap.
    BreakpointTrap,
    /// Stack overflow.
    StackOverflow,
    /// Generic trap.
    GenericTrap,
}
}
  • WasmUnknownError occurs when something inside wasmer goes wrong
  • HostError includes errors that might be returned during the execution of a host function. Those errors are

#![allow(unused)]
fn main() {
pub enum HostError {
    /// String encoding is bad UTF-16 sequence
    BadUTF16,
    /// String encoding is bad UTF-8 sequence
    BadUTF8,
    /// Exceeded the prepaid gas
    GasExceeded,
    /// Exceeded the maximum amount of gas allowed to burn per contract
    GasLimitExceeded,
    /// Exceeded the account balance
    BalanceExceeded,
    /// Tried to call an empty method name
    EmptyMethodName,
    /// Smart contract panicked
    GuestPanic { panic_msg: String },
    /// IntegerOverflow happened during a contract execution
    IntegerOverflow,
    /// `promise_idx` does not correspond to existing promises
    InvalidPromiseIndex { promise_idx: u64 },
    /// Actions can only be appended to non-joint promise.
    CannotAppendActionToJointPromise,
    /// Returning joint promise is currently prohibited
    CannotReturnJointPromise,
    /// Accessed invalid promise result index
    InvalidPromiseResultIndex { result_idx: u64 },
    /// Accessed invalid register id
    InvalidRegisterId { register_id: u64 },
    /// Iterator `iterator_index` was invalidated after its creation by performing a mutable operation on trie
    IteratorWasInvalidated { iterator_index: u64 },
    /// Accessed memory outside the bounds
    MemoryAccessViolation,
    /// VM Logic returned an invalid receipt index
    InvalidReceiptIndex { receipt_index: u64 },
    /// Iterator index `iterator_index` does not exist
    InvalidIteratorIndex { iterator_index: u64 },
    /// VM Logic returned an invalid account id
    InvalidAccountId,
    /// VM Logic returned an invalid method name
    InvalidMethodName,
    /// VM Logic provided an invalid public key
    InvalidPublicKey,
    /// `method_name` is not allowed in view calls
    ProhibitedInView { method_name: String },
    /// The total number of logs will exceed the limit.
    NumberOfLogsExceeded { limit: u64 },
    /// The storage key length exceeded the limit.
    KeyLengthExceeded { length: u64, limit: u64 },
    /// The storage value length exceeded the limit.
    ValueLengthExceeded { length: u64, limit: u64 },
    /// The total log length exceeded the limit.
    TotalLogLengthExceeded { length: u64, limit: u64 },
    /// The maximum number of promises within a FunctionCall exceeded the limit.
    NumberPromisesExceeded { number_of_promises: u64, limit: u64 },
    /// The maximum number of input data dependencies exceeded the limit.
    NumberInputDataDependenciesExceeded { number_of_input_data_dependencies: u64, limit: u64 },
    /// The returned value length exceeded the limit.
    ReturnedValueLengthExceeded { length: u64, limit: u64 },
    /// The contract size for DeployContract action exceeded the limit.
    ContractSizeExceeded { size: u64, limit: u64 },
    /// The host function was deprecated.
    Deprecated { method_name: String },
}
}
  • ExternalError includes errors that occur during the execution inside External, which is an interface between runtime and the rest of the system. The possible errors are:

#![allow(unused)]
fn main() {
pub enum ExternalError {
    /// Unexpected error which is typically related to the node storage corruption.
    /// It's possible the input state is invalid or malicious.
    StorageError(StorageError),
    /// Error when accessing validator information. Happens inside epoch manager.
    ValidatorError(EpochError),
}
}
  • StorageError occurs when state or storage is corrupted.