Skip to main content

Environment Concepts

The contract environment is an interface that defines the facilities -- objects, functions, data sources, etc. -- available to contracts.

Host and Guest

As an interface, the environment has two sides, which we refer to as the host environment and the guest environment. Code in the host environment implements the environment interface; code in the guest environment uses the environment interface.

The host environment is provided by a known set of Rust crates, compiled once into stellar-core (or the SDK). Multiple contracts interact with the same host environment, and the host environment has access to any facilities of its enclosing operating system: files, networking, memory, etc.

In contrast, a new guest environment is established for each invocation of each smart contract. Each contract sees a single environment interface, and can only call functions provided by the environment interface. In other words, the guest environment is a sandbox for executing arbitrary code within safe parameters.

WebAssembly

The on-chain guest environment is isolated inside a WebAssembly (Wasm) virtual machine ("VM"). This means that deployed contract code is compiled to Wasm bytecode rather than native machine code. The host environment includes an interpreter for the VM, and a new short-lived VM is instantiated for each call to a contract, running the bytecode for the contract and then exiting.

The use of a VM helps provide security against any potential guest-code misbehavior, to both host and other guest environments, as well as ensuring portability of guest code between hosts running on different types of hardware.

When developing and testing contract code off-chain, it is possible to compile contract code to native machine code rather than Wasm bytecode, and to run tests and debug contracts against a local copy of the host environment by linking directly to it, rather than executing within a VM. This configuration runs much faster and provides much better debugging information, but is only possible locally, off-chain. On-chain deployed contracts are always Wasm.

WebAssembly is a relatively low-level VM, which means that it does not provide a very rich set of standard or "built-in" operations. In contrast to VMs like the JVM, it has no garbage collector (not even a memory allocator), no IO facilities, no standard data structures like lists, arrays, maps or strings, no concepts of objects or types at all besides basic machine types like 32 and 64-bit integers.

As a result, programs compiled to Wasm bytecode often face a dilemma: if they want rich standard functionality, they must often include a copy of all the "support code" for that functionality within themselves. But if they do, they dramatically increase their code size, which incurs costs and limits performance. Moreover, including such support code limits their ability to interoperate with other programs that may include different, incompatible support code.

The way out of this dilemma is for the environment itself to provide support code for rich standard functionality, in the form of host objects and functions that guest code can use by reference. Each contract refers to the same functionality implemented in the host, ensuring much smaller code size, higher performance, and greater interoperability between contracts. This is what Soroban does.

Host objects and functions

Shared, standard functionality available to all contract guest code is provided through the environment interface in terms of host objects and host functions.

The environment supports a small number of types of host objects covering data structures like vectors, maps, binary blobs, addresses, strings and big integers. Host objects are all immutable, are allocated and reside within the host environment, and are only available in the guest environment by reference. Guest code refers to host objects by integer-valued handles.

There is also a slightly larger set of host functions that act on host objects: creating, modifying, inspecting and manipulating them. Some host functions allow copying blocks of binary data into and out of the VM memory of the guest, and some host functions perform cryptographic operations on host objects.

There are also host functions for interacting with select components of the host environment beyond the host object repertoire, such as reading and writing ledger entries, emitting events, calling other contracts, and accessing information about the transaction context in which guest code is executing.

Serialization

Host objects can be passed (by handle) directly to storage routines or between collaborating contracts. No serialization or deserialization code needs to exist in the guest: the host knows how to serialize and deserialize all of its object types and does so transparently whenever necessary.

Values and types

All host functions can accept as arguments and return values from, at most, the limited Wasm VM repertoire of machine-level types. To simplify matters, Soroban further limits all host functions to passing and returning values from within a single specialized form of 64-bit integers called "value" or "the value type". Through careful bit-packing, the value type can encode any of several separate types more meaningful to users than just "integers".

Specifically, the value type can directly encode small integers (up to 56 bits), but also boolean true and false, signed or unsigned 32-bit integers, typed host object handles, typed error codes, small symbols (up to 9 latin-alphanumeric characters), or a unique void value. Individual bits in a value are allocated to tagging and switching between these cases dynamically, and host functions or objects that require specific cases may reject values of other cases.

Since the value type can contain a handle to a host object, any container object that can contain the value type can in fact hold any object. Therefore the host map and vector types -- container data structures -- are defined merely as containers for the value type, where the specific case of each value may vary from container to container or even among the elements of a container. In this way, the host container types are more like the containers of dynamic-typed languages like JavaScript or Python. The SDK also provides static, uniformly-typed wrappers when this is desired, prohibiting values outside the designated case from being added to the container.