Skip to main content

Overview

Contracts are invoked through a pair of host functions call and try_call:

  • try_call(contract, function, args) calls function exported from contract, passing args and returning an Error on a recoverable error. A non-recoverable error traps instead.
  • call(contract, function, args) just calls try_call with its arguments and traps on Error, essentially propagating the error.

In both cases contract is a Binary host object containing the contract ID, function is a Symbol holding the name of an exported function to call, and args is a Vector of values to pass as arguments.

These host functions can be invoked in two separate ways:

  • From outside the host, such as when a user submits a transaction that calls a contract.
  • From within the host, when one contract calls another.

Both cases follow the same logic:

  • The contract's Wasm bytecode is retrieved from a CONTRACT_DATA ledger entry in the host's storage system.
  • A Wasm VM is instantiated for the duration of the invocation.
  • The function is looked up and invoked, with arguments passed from caller to callee.

When a call occurs from outside the host, any arguments will typically be provided in serialized XDR form accompanying the transaction, and will be deserialized and converted to host objects automatically before invoking the contract.

When a call occurs from inside the host, the caller and callee contracts share the same host and the caller can pass references to host objects directly to the callee without any need to serialize or deserialize them.

Since host objects are immutable, there is limited risk to passing a shared reference from one contract to another: the callee cannot modify the object in a way that would surprise the caller, only create new objects.

Recoverable and Non-Recoverable Errors

try_call returns an Error value only for a recoverable error. A non-recoverable error traps the caller instead: the invocation aborts and the transaction fails, and the calling contract cannot handle the error. A contract does not choose which errors are recoverable. The host decides, and the rule is the same for every contract.

Two groups of errors are non-recoverable:

  • Resource limit errors, which carry the error code ExceededLimit and the error type Budget or Storage. A Budget error means the transaction ran out of CPU instructions or memory. A Storage error means the contract accessed a ledger entry that the transaction footprint does not list.
  • Internal errors, which carry the error code InternalError and any error type other than Contract. The host found a fault in its own logic. This should not happen in practice.

Both groups break a precondition of the invocation, so the host cannot let the contract continue. A budget error recurs on a retry, because the transaction is still out of budget. A footprint violation recurs too, because a transaction cannot add an entry to its own footprint while it runs.

The footprint case is the one to watch, because it is not obvious. A contract that derives a storage key from volatile state, such as a random value or the ledger sequence, can read an entry that simulateTransaction never saw. The footprint then misses that entry and the call traps. Debugging Contract Errors lists the diagnostic events for this case and the other errors of the apply step.

Every other error is recoverable. try_call passes a contract error code through unchanged. It reports every other recoverable host error as an error of type Context with the code InvalidAction, and the granular code stays visible in the diagnostic events.