Skip to content
This repository has been archived by the owner on Aug 21, 2024. It is now read-only.

Add under the hood #16

Merged
merged 1 commit into from
Jul 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 1 addition & 41 deletions docs/learn/interacting-with-contracts.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,6 @@ sidebar_position: 4
title: Interacting with contracts
---

## Calling contracts

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 a `Status` on any error.
- `call(contract, function, args)` just calls `try_call` with its arguments and traps on `Status`, 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 separapte 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.

## Storage footprint and preflight

As mentioned in the [persisting data](persisting-data.md) section, a contract can only load or store `CONTRACT_DATA` entries that are declared in a _footprint_ associated with its invocation.

A footprint is a set of ledger keys, each marked as either read-only or read-write. Read-only keys are available to the transaction for reading, read-write available for reading, writing or both.

Any transaction submitted by a user has to be accompanied by a footprint. A single footprint encompasses _all_ the data read and written by _all_ contracts transitively invoked by the transaction: not just the initial contract that the transaction calls, but also all contracts it calls, and so on.

Since it can be difficult for a user to always know which ledger entries a given contract call will attempt to read or write -- especially those caused by contracts called by other contracts deep within a transaction -- the host provides an auxiliary **"preflight"** mechanism that executes a transaction against a temporary, possibly slightly-stale _snapshot_ of the ledger. The preflight mechanism is _not_ constrained to only read or write the contents of a footprint; rather it _records_ a footprint describing the transaction's execution, discards the execution's effects, and then returns the recorded footprint it to its caller.

Such a preflight-provided footprint can then be used to accompany a "real" submission of the same transaction to the network for real execution. If the state of the ledger has changed too much between the time of the preflight and the real submission, the footprint may be too stale and no longer accurately identify the _keys_ the transaction needs to read and write, at which point the preflight must be retried to refresh the footprint.

In any event -- whether successful or failing -- the real transaction will execute atomically, deterministically and with serializable consistency semantics. An inaccurate footprint simply causes deterministic transaction failure, not a stale-read anomaly. All effects of such a failed transaction are discarded, as they would be in the presence of any other error.

## Testing contracts with our testutils machinery

We provide some machinery in the Soroban
Expand Down Expand Up @@ -75,4 +35,4 @@ case](https://github.com/stellar/soroban-examples/blob/56fef787395b5aed7cd7b1977
This machinery can also be used to test multiple contracts together. For
example, the single offer contract test case creates a token using the
[token
contract](https://github.com/stellar/soroban-examples/blob/56fef787395b5aed7cd7b19772cca28e21b3feb5/single_offer/src/test.rs#L22).
contract](https://github.com/stellar/soroban-examples/blob/56fef787395b5aed7cd7b19772cca28e21b3feb5/single_offer/src/test.rs#L22).
4 changes: 4 additions & 0 deletions docs/under-the-hood/_category_.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"position": 8,
"label": "Under the Hood"
}
44 changes: 44 additions & 0 deletions docs/under-the-hood/interacting-with-contracts.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
sidebar_position: 1
title: Interacting with contracts
---

## Calling contracts

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 a `Status` on any error.
- `call(contract, function, args)` just calls `try_call` with its arguments and traps on `Status`, 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 separapte 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.

## Storage footprint and preflight

As mentioned in the [persisting data](persisting-data.md) section, a contract can only load or store `CONTRACT_DATA` entries that are declared in a _footprint_ associated with its invocation.

A footprint is a set of ledger keys, each marked as either read-only or read-write. Read-only keys are available to the transaction for reading, read-write available for reading, writing or both.

Any transaction submitted by a user has to be accompanied by a footprint. A single footprint encompasses _all_ the data read and written by _all_ contracts transitively invoked by the transaction: not just the initial contract that the transaction calls, but also all contracts it calls, and so on.

Since it can be difficult for a user to always know which ledger entries a given contract call will attempt to read or write -- especially those caused by contracts called by other contracts deep within a transaction -- the host provides an auxiliary **"preflight"** mechanism that executes a transaction against a temporary, possibly slightly-stale _snapshot_ of the ledger. The preflight mechanism is _not_ constrained to only read or write the contents of a footprint; rather it _records_ a footprint describing the transaction's execution, discards the execution's effects, and then returns the recorded footprint it to its caller.

Such a preflight-provided footprint can then be used to accompany a "real" submission of the same transaction to the network for real execution. If the state of the ledger has changed too much between the time of the preflight and the real submission, the footprint may be too stale and no longer accurately identify the _keys_ the transaction needs to read and write, at which point the preflight must be retried to refresh the footprint.

In any event -- whether successful or failing -- the real transaction will execute atomically, deterministically and with serializable consistency semantics. An inaccurate footprint simply causes deterministic transaction failure, not a stale-read anomaly. All effects of such a failed transaction are discarded, as they would be in the presence of any other error.