From 601f640fc96663e681cd011277d1799b12893f9c Mon Sep 17 00:00:00 2001 From: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> Date: Fri, 29 Jul 2022 10:36:30 -0700 Subject: [PATCH] Move Learn and Under-the-Hood to Guides (#39) --- docs/guides/_category_.json | 4 + docs/guides/contract-lifecycle.mdx | 38 +++ docs/guides/debugging.mdx | 42 ++++ docs/guides/environment-concepts.mdx | 47 ++++ docs/guides/events.mdx | 11 + docs/guides/gas-and-metering.mdx | 12 + docs/guides/high-level-overview.mdx | 30 +++ docs/guides/interacting-with-contracts-2.mdx | 44 ++++ docs/guides/interacting-with-contracts.mdx | 100 ++++++++ docs/guides/persisting-data.mdx | 22 ++ docs/guides/rust-dialect.mdx | 56 +++++ .../standard-assets-and-authorization.mdx | 234 ++++++++++++++++++ 12 files changed, 640 insertions(+) create mode 100644 docs/guides/_category_.json create mode 100644 docs/guides/contract-lifecycle.mdx create mode 100644 docs/guides/debugging.mdx create mode 100644 docs/guides/environment-concepts.mdx create mode 100644 docs/guides/events.mdx create mode 100644 docs/guides/gas-and-metering.mdx create mode 100644 docs/guides/high-level-overview.mdx create mode 100644 docs/guides/interacting-with-contracts-2.mdx create mode 100644 docs/guides/interacting-with-contracts.mdx create mode 100644 docs/guides/persisting-data.mdx create mode 100644 docs/guides/rust-dialect.mdx create mode 100644 docs/guides/standard-assets-and-authorization.mdx diff --git a/docs/guides/_category_.json b/docs/guides/_category_.json new file mode 100644 index 000000000..1ba237c2d --- /dev/null +++ b/docs/guides/_category_.json @@ -0,0 +1,4 @@ +{ + "position": 5, + "label": "Guides" +} diff --git a/docs/guides/contract-lifecycle.mdx b/docs/guides/contract-lifecycle.mdx new file mode 100644 index 000000000..b35189a57 --- /dev/null +++ b/docs/guides/contract-lifecycle.mdx @@ -0,0 +1,38 @@ +--- +sidebar_position: 3 +title: Contract Lifecycle +--- + +## Development +Contract development can be done on a local computer with as little as 3 necessary components: an IDE, a copy of the Rust toolchain, and a copy of the Soroban SDK. + +The SDK contains a full working copy of the host environment, as well as a "mock" version of the ledger for persistent storage of contract data. It is therefore possible (and encouraged) to edit, compile, test and debug contracts directly against this "local" copy of the host, entirely offline and without even accessing a test network. + +To make the local development process even more convenient and fast, the contract being developed can (and should) be compiled as native code and linked directly to the local host, rather than compiled to WASM and run in a local VM sandbox. Both configurations are possible, but the native configuration is fastest and provides the richest testing and debugging experience. + +The SDK-provided local contract host also contains a local web server that serves the necessary HTTP API endpoint used for client applications to interact with a contract. This can be used for local development of applications, again without needing to deploy contracts to any test or live network. +## Deployment +Once a contract has been tested and debugged locally, it can be deployed. To do this it must be compiled to WASM code, and then included by value in a transaction sent to the intended deployment network. + +The SDK provides a command-line utility that invokes the Rust compiler with the correct settings for producing a WASM bundle for deployment, but developers can also build this themselves. + +Before submitting to the network, developers should inspect the resulting WASM binary emitted by the Rust compiler to ensure that it contains only the intended code and data, and is as small as possible. The SDK command-line utility contains diagnostic commands to assist with this process. + +The SDK command-line utility can also build and submit the transaction deploying a WASM contract to the network. Deployment requires sufficient network credentials to sign a transaction performing the deployment and pay its fees. Contracts should be deployed to test networks and thoroughly tested there being deployed to the live network. +## Execution +Deployed contracts live on chain in a CONTRACT_DATA ledger entry. They are executed within a VM sandbox managed by a host environment inside stellar-core. Each transaction that leads to a contract execution is run in a separate host environment, and each contract called by such a transaction (either directly or indirectly from another contract) is executed in a separate guest WASM VM contained within the transaction’s host environment. + +Execution is initiated by a host function called "call". The "call" host function can itself be invoked two different ways: either by someone submitting a transaction to the network that invokes "call" directly, or indirectly by some other contract invoking "call". + +In either case, the "call" host function is provided with the ID of a contract to invoke, the name of a function in the contract, and a vector of argument values to pass. The "call" host function then sets up a VM sandbox for the called contract, loads and instantiates its WASM bytecode, and invokes the named function, passing the provided arguments. + +Each contract execution continues until the contract either completes successfully or traps with an error condition. If execution completes successfully, all ledger entries modified during execution will be written back to the ledger atomically. If execution traps, all modified ledger entries will be discarded and the contract will have no effect on the ledger. + +A variety of conditions in either the guest or host environments can cause a contract to trap. If a host function is called with invalid arguments, for example, the host will trap. Similarly if the contract performs an erroneous WASM bytecode such as a division by zero or access to memory out of bounds, the WASM VM will trap. Also if the contract uses more resources than its enclosing transaction has paid for, the contract will trap. +## Monitoring +Contracts can be monitored in two main ways: by observing events emitted during their execution, and by examining the ledger entries written by them. + +TBD: expand this section. + +## Upgrading contracts +TBD: depends on decisions pending in CAPs. diff --git a/docs/guides/debugging.mdx b/docs/guides/debugging.mdx new file mode 100644 index 000000000..5fc2b5ed6 --- /dev/null +++ b/docs/guides/debugging.mdx @@ -0,0 +1,42 @@ +--- +sidebar_position: 9 +title: Debugging +--- + +Soroban contracts are as much as possible regular Rust programs and can be debugged using the same tools you'd usually use. + +The debugging facilities available differ significantly depending on whether a contract is compiled natively for local testing, or compiled into WASM for deployment. + +Deciding between these two modes and making the most of them while debugging requires a **careful understanding** of which code is compiled-in to deployed contracts and which code is only available for local testing. + +## Local-testing mode + +It is possible (and encouraged during development) to compile Soroban contracts **natively** (eg. as x86-64 or AArch64 code) and link against the host environment **directly**, such that the contract is not a guest running in a WASM virtual machine at all: it is simply one native library calling another -- its host -- and both host and contract are linked together as a single program, together with a test harness. + +This configuration is referred to as **"local-testing mode"** and since it eliminates the WASM virtual machine from the debugging experience it has many advantages: + + - Tests run much faster since there is no VM interpreting them, and will execute in parallel by default on multiple threads. + - Tests can use numerous standard testing and debugging techniques: + - [The standard Rust `#[test]` harness](https://doc.rust-lang.org/reference/attributes/testing.html), including push-button IDE support for running and re-running single tests. + - [Standard IDE-supported debuggers](https://code.visualstudio.com/docs/languages/rust#_debugging), including IDE support for setting breakpoints and inspecting values in both contract and host. + - Lightweight debugging via [standard logging](https://docs.rs/log/latest/log/) or [tracing](https://docs.rs/tracing/latest/tracing/). + - Systematic testing such as [fuzzing](https://crates.io/crates/cargo-fuzz), [property-testing](https://crates.io/crates/proptest), or even [model checking](https://crates.io/crates/kani-verifier) or [formal verification](https://github.com/xldenis/creusot). + - The simplest of all debugging approaches, [printing to standard error](https://doc.rust-lang.org/std/macro.eprintln.html). + +Local-testing mode is the **default** configuration when compiling code targeting your local computer's CPU and operating system, which is what cargo will do if you set up a new Rust project and don't specify a target. + +## WASM mode + +If on the other hand you wish to compile for deployment, you must tell cargo to build for the WASM target. + +Building for WASM will _disable_ many of the debugging facilities described above, typically for one of three reasons: + + - The WASM VM simply can't (or the VM we've chosen doesn't) provide them. + - The WASM VM _could_ provide them but doing so would violate constraints of the [contract Rust dialect](rust-dialect.mdx). + - The WASM VM _could_ provide them but doing so would make the resulting WASM code impractically large. + +While we encourage most testing to happen in local-testing mode, some problems will obviously only arise in deployment and some debugging facilities thus remain available even there: + + - A "sandbox" host with a mock-ledger that can read and write `CONTRACT_DATA` ledger entries to the local filesystem. + - A general logging system that allows contracts to log values of the [shared host/guest "value" type](environment-concepts.mdx), even in production. + - User-extensible `Status` codes that can be returned from any contract call to indicate problems. diff --git a/docs/guides/environment-concepts.mdx b/docs/guides/environment-concepts.mdx new file mode 100644 index 000000000..fee24f854 --- /dev/null +++ b/docs/guides/environment-concepts.mdx @@ -0,0 +1,47 @@ +--- +sidebar_position: 2 +title: 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](debugging-contracts.mdx) 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, and arbitrary precision numbers. 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 63-bit unsigned integers (equivalent to positive signed 64-bit numbers), but also boolean true and false, signed or unsigned 32-bit integers, typed host object handles, typed error codes, small symbols (up to 10 latin-alphanumeric characters), small bitsets (up to 60 bits), 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. + +![](/diagrams/environment-concepts.png) diff --git a/docs/guides/events.mdx b/docs/guides/events.mdx new file mode 100644 index 000000000..02916f069 --- /dev/null +++ b/docs/guides/events.mdx @@ -0,0 +1,11 @@ +--- +sidebar_position: 6 +title: Events +--- + +Events are the mechanism that applications off-chain can use to monitor changes +and events in contracts on-chain. + +:::danger +Soroban events are under active-development and are not yet available. +::: diff --git a/docs/guides/gas-and-metering.mdx b/docs/guides/gas-and-metering.mdx new file mode 100644 index 000000000..9966da277 --- /dev/null +++ b/docs/guides/gas-and-metering.mdx @@ -0,0 +1,12 @@ +--- +sidebar_position: 8 +title: Gas and metering +--- + +Gas and metering are the mechanisms that will measure the cost of running a +contract and bill that cost to the account executing the contract. + +:::danger +Soroban gas and metering are under active-development and are not yet available. +::: + diff --git a/docs/guides/high-level-overview.mdx b/docs/guides/high-level-overview.mdx new file mode 100644 index 000000000..f43e0a979 --- /dev/null +++ b/docs/guides/high-level-overview.mdx @@ -0,0 +1,30 @@ +--- +sidebar_position: 1 +title: High level overview +--- + +## Rust language +Contracts are small programs written in the [Rust programming language](https://www.rust-lang.org). In order to write contracts, [install a Rust toolchain](https://www.rust-lang.org/tools/install), configure your [editor to support Rust programs](https://www.rust-lang.org/tools), and [learn at least some basic Rust concepts](https://www.rust-lang.org/learn). + +Contracts can be compiled to native code for local (off-chain) testing, but must be compiled as WebAssembly ("WASM") for deployment. The on-chain host environment only allows uploading WASM contracts, and runs them within a WASM "sandbox" virtual machine ("VM"). + +In practice several special characteristics of the contract execution environment -- resource constraints, security considerations, nonstandard IO and invocation primitives, the requirement for strict determinism -- will mean that contracts can only use a very narrow subset of the full Rust language, and must use specialized libraries for most tasks. See the [rust dialect](rust-dialect.mdx) section for details. + +In particular both the Rust standard library and most 3rd party libraries ("crates" in Rust parlance) will not be available for direct off-the-shelf use in contracts, due to the constraints listed above. Some crates will be possible to adapt to use in contracts, others may be incorporated into the host environment as host objects or functions. + +In the future it is possible that other source languages may be supported. The execution environment is somewhat language-agnostic, and some very early experiments suggest that other languages may be possible. However, at this time only Rust is supported. + +## SDK +Contracts are developed with the help of a Software Development Kit or SDK. The SDK consists of both a Rust crate and a command-line tool. + +The SDK crate acts as a substitute for the Rust standard library -- providing data structures and utility functions for contracts -- as well as providing access to smart-contract-specific functionality from the contract environment, like cryptographic hashing and signature verification, access to on-chain persistent storage, and location and invocation of secondary contracts via stable identifiers. + +### Local testing mode +The SDK command-line tool provides a developer-focused front-end for compiling, testing, inspecting, versioning and deploying contracts. + +The SDK also includes a _complete implementation of the contract host environment_ -- identical to the one that runs on-chain -- such that contracts can be **run locally** on a developer's workstation, and can be [tested and debugged](debugging-contracts.mdx) directly with a local debugger within a standard IDE, as well as a native test harness for fast-feedback unit testing and high-speed fuzzing or property testing. + +## Host Environment +The host environment is a set of Rust crates that are compiled into both the SDK command-line tool and stellar-core, the transaction processor at the heart of the stellar network. It comprises a set of host objects and functions, an interface to on-chain storage and contract invocation, a resource-accounting and fee-charging system, and a WASM interpreter. + +Most contract developers will not frequently need to interact with the host environment directly -- SDK functions wrap most of its facilities and provide richer and more ergonomic types and functions -- but it is helpful to understand its structure in order to understand the conceptual model the SDK is presenting. It is also likely that some parts of the host environment will be visible when testing or debugging contracts compiled natively on a local workstation. diff --git a/docs/guides/interacting-with-contracts-2.mdx b/docs/guides/interacting-with-contracts-2.mdx new file mode 100644 index 000000000..d0f9c5bf0 --- /dev/null +++ b/docs/guides/interacting-with-contracts-2.mdx @@ -0,0 +1,44 @@ +--- +sidebar_position: 4 +title: Interacting with contracts 2 +--- + +## 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](../learn/persisting-data) 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. diff --git a/docs/guides/interacting-with-contracts.mdx b/docs/guides/interacting-with-contracts.mdx new file mode 100644 index 000000000..96ea92af6 --- /dev/null +++ b/docs/guides/interacting-with-contracts.mdx @@ -0,0 +1,100 @@ +--- +sidebar_position: 4 +title: Interacting with contracts +--- + +## Three types of interactions + +### Function call + +A function call is the simplest and least expensive kind of contract +interaction. A function call does exactly what you would expect a contract call +to do in any other software development context: the contract transfers control +and data to another part of the _same_ contract. Because a function call always +transfers control to the same contract, they do not change the values returned +by `get_current_contract` and `get_invoking_contract`. A function call is the +only way to access the private methods of a contract, although it can also call +be used to access the public methods of a contract. + +To perform a function call, simply make a Rust function call. + +### Contract invocation + +A contract invocation is a more powerful and more expensive kind of contract +interaction. A contract invocation is similar to starting a new process because +the code that runs will be in a separate address space, meaning that they do +not share any data other than what was passed in the invocation. While a +contract invocation typically transfers control to a _different_ contract, it +is possible to transfer control to the currently running contract. Regardless +of whether the contract that receives control is a different contract or the +currently running contract, the value returned by `get_invoking_contract` will +be the previous value of `get_current_contract`. A contract invocation can only +access the public methods of a contract. + +If a contract contains a public function `f`, then invoking `f` can be done by +making a Rust function call to `f::invoke`. + +Some contracts, such as the token contract, only export the contract invocation +functions. In doing so, they are able to assign those functions friendly names. +For example, +[initialize](https://github.com/stellar/soroban-token-contract/blob/42380647bb817bf01c739c19286f18be881e0e41/src/contract.rs#L55-L57) + +```rust +#[contractimpl(export_if = "export")] +impl TokenTrait for Token { + fn initialize(e: Env, admin: Identifier, decimal: u32, name: Binary, symbol: Binary) { +``` + +is [exported](https://github.com/stellar/soroban-token-contract/blob/42380647bb817bf01c739c19286f18be881e0e41/src/lib.rs#L26) +as + +```rust +pub use crate::contract::initialize::invoke as initialize; +``` + +This function is then easily [called by the liquidity pool contract](https://github.com/stellar/soroban-examples/blob/4060d3bd5ee7846020b68ee583665a4d4cf4b315/liquidity_pool/src/lib.rs#L164-L171). + +### Operation + +An operation is the ultimate entrypoint of every contract interaction. An +operation transfers control and external data to a contract, allowing execution +to begin. + +## Interacting with contracts in tests + +[Debugging contracts](../under-the-hood/debugging-contracts.mdx) explains that +it is much more convenient to debug using native code than WASM. Given that you +are testing native code, it is tempting to interact with your contract directly +using function calls. If you attempt this approach, you will find that it +doesn't always work. Function call interactions do not set the environment into +the correct state for contract execution, so functions involving contract data +and determining the current or invoking contract will not work. + +When writing tests, it is important to always interact with contracts through +contract invocation. In a production setting, contract invocation will execute +WASM bytecode loaded from the ledger. So how does this work if you are testing +native code? You must register your contract with the environment so it knows +what functions are available and how to call them. While this sounds complex, +the `contractimpl` procedural macro automatically generates almost all of the +code to do this. All you have to do is write a small [stub](https://github.com/stellar/soroban-token-contract/blob/42380647bb817bf01c739c19286f18be881e0e41/src/testutils.rs#L12-L15) +to actually call the generated code, such as + +```rust +pub fn register_test_contract(e: &Env, contract_id: &[u8; 32]) { + let contract_id = FixedBinary::from_array(e, *contract_id); + e.register_contract(&contract_id, crate::contract::Token {}); +} +``` + +Some contracts, such as the token contract, also provide a [friendlier interface](https://github.com/stellar/soroban-token-contract/blob/42380647bb817bf01c739c19286f18be881e0e41/src/testutils.rs#L26-L191) +to facilitate testing. There are many ways these interfaces might make testing +easier, but one common one is to allow automatic message signing by passing a +[ed25519_dalek::Keypair](https://docs.rs/ed25519-dalek/latest/ed25519_dalek/struct.Keypair.html). + +Note that everything described in this section is only available if the +`testutils` feature is enabled. + +### Example + +This machinery can also be used to test multiple contracts together. For +example, the single offer contract test case [creates a token](https://github.com/stellar/soroban-examples/blob/56fef787395b5aed7cd7b19772cca28e21b3feb5/single_offer/src/test.rs#L22). diff --git a/docs/guides/persisting-data.mdx b/docs/guides/persisting-data.mdx new file mode 100644 index 000000000..263b804ad --- /dev/null +++ b/docs/guides/persisting-data.mdx @@ -0,0 +1,22 @@ +--- +sidebar_position: 5 +title: Persisting data +--- + +## Ledger entries +Contracts can access ledger entries of type `CONTRACT_DATA`. Host functions are provided to probe, read, write, and delete `CONTRACT_DATA` ledger entries. + +Each `CONTRACT_DATA` ledger entry is keyed in the ledger by the contract ID that owns it, as well as a single user-chosen value, of the standard value type. This means that the user-chosen key may be a simple value such as a symbol, number or binary blob, or it may be a more complex structured value like a vector or map with multiple sub-values. + +Each `CONTRACT_DATA` ledger entry also holds (in addition to its key) a single value associated with the key. Again, this value may be simple like a symbol or number, or may be complex like a vector or map with many sub-values. + +No serialization or deserialization is required in contract code when accessing `CONTRACT_DATA` ledger entries: the host automatically serializes and deserializes any ledger entries accessed, exchanging them with the contract as deserialized values. If a contract wishes to use a custom serialization format, it can store a binary-valued `CONTRACT_DATA` ledger entry and provide its own code to serialize and deserialize, but Soroban has been designed with the intent to minimize the need for contracts to ever do this. + +## Access Control +Contracts are only allowed to read and write `CONTRACT_DATA` ledger entries owned by the contract: those keyed by the same contract ID as the contract performing the read or write. Attempting to access other `CONTRACT_DATA` ledger entries will cause a transaction to fail. + +## Granularity +A `CONTRACT_DATA` ledger entry is read or written from the ledger in its entirety; there is no way to read or write "only a part" of a `CONTRACT_DATA` ledger entry. There is also a fixed overhead cost to accessing any `CONTRACT_DATA` ledger entry. Contracts are therefore responsible for dividing logically "large" data structures into "pieces" with an appropriate size granularity, to use for reading and writing. If pieces are too large there may be unnecessary costs paid for reading and writing unused data, as well as unnecessary contention in parallel execution; but if pieces are too small there may be unnecessary costs paid for the fixed overhead of each entry. + +## Footprints and parallel contention +Contracts are only allowed to access ledger entries specified in the footprint of their transaction. Transactions with overlapping footprints are said to contend, and will only execute sequentially with respect to one another, on a single thread. Transactions with non-overlapping footprints may execute in parallel. This means that a finer granularity of `CONTRACT_DATA` ledger entries may reduce artificial contention among transactions using a contract, and thereby increase parallelism. diff --git a/docs/guides/rust-dialect.mdx b/docs/guides/rust-dialect.mdx new file mode 100644 index 000000000..06604cecc --- /dev/null +++ b/docs/guides/rust-dialect.mdx @@ -0,0 +1,56 @@ +--- +sidebar_position: 8 +title: Contract Rust dialect +--- + +Contract development occurs in the Rust programming language, but several features of the Rust language are either unavailable in the deployment guest environment, or not recommended because their use would incur unacceptable costs at runtime. + +For this reason it makes sense to consider code written for contracts to be a _dialect_ or special variant of the Rust programming language, with certain unusual constraints and priorities, such as determinism and code size. + +These constraints and priorities are _similar_ to those encountered when writing Rust code for "embedded systems", and the tools, libraries and techniques used in the "contract dialect" are frequently borrowed from the [Rust embedded systems community](https://docs.rust-embedded.org/book/index.html), and by default contracts are recommended to be built with the [`#[no_std]` mode](https://docs.rust-embedded.org/book/intro/no-std.html) that excludes the Rust standard library entirely, relying on the smaller underlying `core` library instead. + +Note: these constraints and priorities are **not enforced when building in local-testing mode**, and in fact local contract tests will _frequently_ use facilities -- to generate test input, inspect test output, and guide testing -- that are not supported in the deployment guest environment. Developers **must understand** the difference between code that is compiled-in to WASM modules for deployment and code that is conditionally compiled for testing. See [debugging contracts](debugging-contracts.mdx) for more details. + +The "contract dialect" has the following characteristics: + +## No floating point +Floating-point arithmetic in the guest is completely prohibited. Floating-point operations in WASM have a few nondeterministic or platform-specific aspects: mainly NaN bit patterns, as well as floating-point environment settings such as rounding mode. + +While it is theoretically possible to force all floating-point code into deterministic behaviour across WASM implementations, doing so on some WASM implementations may be difficult, costly, or error-prone. To avoid the complexity, all floating-point code is rejected at instantiation time. + +This restriction may be revisited in a future version. + +## Limited (ideally zero) dynamic memory allocation +Dynamic memory allocation within the guest is **strongly** discouraged, but not completely prohibited. + +The host object and host function repertoire has been designed to relieve the guest from having to perform dynamic allocation within its own linear memory; instead, the guest is expected and intended to allocate dynamic structures _within host objects_ and interact with them using lightweight handles. + +Using host objects instead of data structures in guest memory carries numerous benefits: much higher performance, much smaller code size, interoperability between contracts, shared host support for serialization, debugging and data structure introspection. + +The guest does, however, has a small linear memory available to it in cases where dynamic memory allocation is necessary. Using this memory carries costs: the guest must include in its code a full copy of a memory allocator, and must pay the runtime cost of executing the allocator's code inside the VM. + +This restriction is due to the limited ability of WASM to support code-sharing: there is no standard way for the WASM sandbox to provide shared "standard library" code within a guest, such as a memory allocator, nor does the host have adequate insight into the contents of the guest's memory to provide an allocator itself. Every contract that wishes to use dynamic allocation must therefore carry its own copy of an allocator. + +Many instances where dynamic memory allocation might _seem_ to be required can also be addressed just as well with a library such as [heapless](https://docs.rs/heapless/latest/heapless/). This library (and others of its kind) provide data structures with familiar APIs that _appear_ dynamic, but are actually implemented in terms of a single stack or static allocation, with a fixed maximum size established at construction: attempts to grow the dynamic size beyond the maximum size simply fail. In the context of a contract, this can sometimes be perferable behaviour, and avoids the question of dynamic allocation entirely. + +## Non-standard I/O +All standard I/O facilities and access to the operating system that a typical Rust program would expect to perform using the Rust standard library is prohibited; programs that try to import such functions from the host through (for example) the WASI interface will fail to instantiate, since they refer to functions not provided by the host. + +No operating system, nor any simulation thereof, is present in the contract sandbox. Again, the repertoire of host objects and host functions is intended to replace and largely obviate the need for such facilities from the standard library. + +This restriction arises from the fact that contracts need to run with _stronger_ guarantees than those made by typical operating-system APIs. Specifically contracts must perform I/O with all-or-nothing, transactional semantics (relative to their successful execution or failure) as well as serializable consistency. This eliminating most APIs that would relate to typical file I/O. Furthermore contracts must be isolated from all sources of nondeterminism such as networking or process control, which eliminating most of the remaining APIs. Once files, networking and process control are gone, there simply isn't enough left in the standard operating system I/O facililties to bother trying to provide them. + +## No multithreading +Multithreading is not available. As with I/O functions, attempting to import any APIs from the host related to multithreading will fail at instantiation time. + +This restriction is similarly based on the need for contracts to run in an environment with strong determinism and serializable consistency guarantees. + +## Immediate panic +The Rust `panic!()` facility for unrecoverable errors will trap the WASM virtual machine immediately, halting execution at the instruction that traps rather than unwinding. This means that `Drop` code in Rust types will not run during a panic. This behaviour is similar to the `panic = "abort"` profile that Rust code can (and often is) compiled with. + +This is not a hard restriction enforced by the host, but a soft configuration made through a mixture of SDK functions and flags used when compiling, in the interest of minimizing code size and limiting execution costs. It can be bypassed with some effort if unwinding and `Drop` code is desired, at the cost of greatly increased code size. + +## Pure-functional collections +Host objects have significantly different semantics than typical Rust data structures, especially those implementing _collections_ such as maps and vectors. + +In particular: host objects are **immutable**, and often **share substructure**. They therefore resemble the data structures from pure-functional programming more closely than the typical imperative model used in many Rust programs. diff --git a/docs/guides/standard-assets-and-authorization.mdx b/docs/guides/standard-assets-and-authorization.mdx new file mode 100644 index 000000000..1096d9a7a --- /dev/null +++ b/docs/guides/standard-assets-and-authorization.mdx @@ -0,0 +1,234 @@ +--- +sidebar_position: 7 +title: Standard assets and authorization +--- + +The authorization model of Soroban is closely related to its "standard asset" contract; the two are best understood together. + +# Authorization + +Authorization is the process of judging which operations "should" or "should not" be allowed to occur; it is about judging _permission_. + +Authorization differs from _authentication_, which is the narrower problem of judging whether a person "is who they say they are", or whether a message claiming to come from a person "really" came from them. + +Authorization often uses cryptographic authentication (via signatures) to support its judgments, but is a broader, more general process. + +## Basic authorization: writing to `CONTRACT_DATA` + +The basic authorization rule is simple: each contract has an identifier, each `CONTRACT_DATA` ledger entry carries the identifier of a contract, and only the contract with the same identifier as a `CONTRACT_DATA` ledger entry can write to it. + +## Complex authorization: multiple users and the impracticality of general rules + +Authorization is complicated by the fact that contracts do not exist or execute in isolation: they record data related to multiple users and run transactions that change multiple users' data simultaneously. Each user might reasonably want (and be permitted) to modify some parts of a contract's data, but not other parts. + +We might expect the system to have a rich set of built-in authorization rules, to allow modeling individual users, their data, and the sets of data each user of a contract can or cannot modify under a variety of transactions and signatures. This is however impractical in general: each contract has complex and unforeseeable conditions governing access to its data. + +As an example, consider a contract holding accounts for users, and exposing a "transfer" operation. If a contract has ledger entries representing account balances for users A and B, then a transfer from A to B probably requires modifying the ledger entries associated with both A and B. But which transfers should be permitted? One possible rule would be to allow transfers from A to B only when A has sufficient funds and A signed the transaction; then B could not initiate the transfer nor could A overspend their balance. But that is not the only possible rule: reasonable contracts might allow "overdraft" on A's account, or could permit A to delegate to B the right to withdraw some of A's funds when necessary. + +In general, the conditions in which data associated with each user should or should-not be modified can be quite complex, and vary on a contract-by-contract basis. Indeed, one way of thinking of contracts is as _primarily_ a set of rules for authorizing changes to data. It is therefore not possible (or at least not practical) to capture all plausible authorization patterns with structured, declarative authorization rules "outside the contracts". The contracts _are_ the rules. + +Instead, we focus on facilities provided _to_ contracts to _support them_ in making authorization decisions for themselves, in their own code. + +## Tools for authorization: identities, messages and authorizations + +Several mechanisms are provided to each contract to make authorization decisions. The mechanisms fall into two categories: + + - Common data structures and functions in the SDK and "standard asset" contract. + - Host functions that assist in validating aspects of these data structures. + +### Common data structures and functions + +The common data structures involved in authorization model key concepts used in authorization judgments. They are provided (and used) by the "standard asset" contract, and form a base set of functionality that should be sufficient for expressing many authorization patterns in other contracts, as well as interacting with instances of the standard asset. + +These concepts are: + + - **Identities**: these are the parties that can initiate actions subject to authorization judgments. Identities may be one of three types: + - **Single-key users**, represented by a single Ed25519 public key + - **Account users**, represented by a reference to an existing account on the Stellar network (which stores, in an account ledger entry, a list of weighted authorized signing keys) + - **Contracts**, represented by a contract ID (not a public key) + - **Messages**: these encode a request from an identity to perform some action, such that the message can have an authorization claim made about it, and an authorization judgment applied to it. Messages include a nonce, a "domain" code number indicating the action to take, and a set of general parameters to that action. + - **Authorizations**: these are statements made _about messages_. Each authorization encodes the claim that the action described by the message is authorized to occur, on the authority of some identity. Authorizations may have three forms, corresponding to the three forms of identity: single-key, account, and contract. + +Contracts decide when a message is authorized with two separate steps: + + - Validate the provided authorization, by some mixture of checking signatures or examining the invocation context. + - Evaluate the operation requested by the message to see if it fits the contract's unique rules for that operation. + +The first step often requires host-function support. The second step is always contract-specific, and cannot be provided by the platform in general. + +### Authorization-validation host functions + +Three families of host function are provided to validate authorizations: + + - **Cryptographic validation** functions. These help validate the "single-key user" form of authorization. Specifically these functions compute SHA256 hashes and verify Ed25519 signatures. + - **Account authorization** functions. These help validate the "account user" form of authorization. Specifically these functions allow looking up the signing thresholds and weights for signing keys, as stored in existing account ledger entries in the Stellar blockchain. + - **Invoking contract** function. This helps validate the "contract" form of authorization. Specifically this function returns the contract ID of the contract that invoked the currently-executing contract. If no contract invoked the currently-executing contract, this function traps. + +This last function deserves further explanation. A contract can be invoked either from a transaction (originating outside of the host) or from a cross-contract call made by some _other_ contract. When some contract X decides to invoke some other contract Y, it is generally sufficient to consider the _fact that X called Y_ as evidence that X authorizes whatever request it is making to Y with that call. After all, if X did not wish to authorize such an invocation, it would not make it! So the only information necessary to evaluate a "contract" authorization is the identity of the (directly) invoking contract. + +# Standard Token Contract + +Tokens are a vital part of blockchains, and contracts that implement token +functionality are inevitable on any smart contract platform. Instead of +expecting a standard to emerge from the ecosystem, we provide our own token +contract implementation. This has a couple advantages. First, we can special +case this contract and run it natively instead of running in a WASM VM, reducing +the cost of using the contract. Second, we can use this native contract to allow +"classic" Stellar assets to interoperate with smart tokens. The current +iteration of the standard token contract doesn't run natively or interoperate +with "classic" Stellar assets, but these improvements will be made in the +future. + +The standard token contract is similar to the widely used ERC-20 token standard, +which should make it easier for existing smart contract developers to get +started on Stellar. + +Note that this standard token contract does not prevent the ecosystem from +developing other token contracts if the one we provide is missing functionality +they require. + +## Token contract authorization semantics + +[Public types](https://github.com/stellar/soroban-token-contract/blob/main/src/public_types.rs) referenced below for the token contract + +### Authorization parameter + +Used by `mint`, `burn`, `freeze`, `unfreeze`, and `set_admin`. These functions +assume the `Authorization` is for the admin `Identifier`, which is why there are +no keys in `Authorization`. + +### KeyedAuthorization parameter + +Used by `allowance`, `approve`, `xfer`, and `xfer_from`. `KeyedAuthorization` is +for contract functions where the user is interacting with a balance or allowance in the token +contract. The user's identifier is specified as a key for +`KeyedAuthorization::Ed25519` and `KeyedAuthorization::Account`, while +`KeyedAuthorization::Contract` implies that the invoking contract's address is +the user. + +### Signatures + +For non-Contract authorization types, both `KeyedAuthorization` and +`Authorization` will include one of more signatures. Those signatures are +derived by signing the `Message` enum, which has one value at the moment, `V0`. +`Message::V0` requires the current nonce for the Identifier providing +authorization, a +[domain](https://github.com/stellar/soroban-token-contract/blob/main/src/cryptography.rs), +and the function parameters. + +### Nonce + +The token contract stores a nonce per `Identifier`. This nonce is required on +any function that requires authorization (The Signatures section above explain +how it’s used). The current nonce for an `Identifier` can be retrieved using the +nonce contract function. + +## Contract Interface + +```rust +// Admin interface + +// Sets the administrator to "admin". Also sets some metadata +fn initialize(e: Env, admin: Identifier, decimal: u32, name: Binary, symbol: Binary); + +// If "admin" is the administrator, burn "amount" from "from" +fn burn(e: Env, admin: Authorization, from: Identifier, amount: BigInt); + +// If "admin" is the administrator, mint "amount" to "to" +fn mint(e: Env, admin: Authorization, to: Identifier, amount: BigInt); + +// If "admin" is the administrator, set the administrator to "id" +fn set_admin(e: Env, admin: Authorization, new_admin: Identifier); + +// If "admin" is the administrator, freeze "id" +fn freeze(e: Env, admin: Authorization, id: Identifier); + +// If "admin" is the administrator, unfreeze "id" +fn unfreeze(e: Env, admin: Authorization, id: Identifier); + +// Token Interface + +// Get the allowance for "spender" to transfer from "from" +fn allowance(e: Env, from: Identifier, spender: Identifier) -> BigInt; + +// Set the allowance to "amount" for "spender" to transfer from "from" +fn approve(e: Env, from: KeyedAuthorization, spender: Identifier, amount: BigInt); + +// Get the balance of "id" +fn balance(e: Env, id: Identifier) -> BigInt; + +// Transfer "amount" from "from" to "to" +fn xfer(e: Env, from: KeyedAuthorization, to: Identifier, amount: BigInt); + +// Transfer "amount" from "from" to "to", consuming the allowance of "spender" +fn xfer_from(e: Env, spender: KeyedAuthorization, from: Identifier, to: Identifier, amount: BigInt); + +// Returns true if "id" is frozen +fn is_frozen(e: Env, id: Identifier) -> bool; + +// Returns the current nonce for "id" +fn nonce(e: Env, id: Identifier) -> BigInt; + +// Descriptive Interface + +// Get the number of decimals used to represent amounts of this token +fn decimals(e: Env) -> u32; + +// Get the name for this token +fn name(e: Env) -> Binary; + +// Get the symbol for this token +fn symbol(e: Env) -> Binary; +``` + +## Running the token contract with testutils machinery +This section has more information about the testutils machinery (TODO: link to interacting-with-contracts.mdx testutils section) + +This [test +case](https://github.com/stellar/soroban-token-contract/blob/37e2cd3580f8e28beea9e8f9194b9cf8547472dd/tests/test.rs#L20) +shows how the token contract should be used. Note that the actual call into the +contract is in the +[Token](https://github.com/stellar/soroban-token-contract/blob/37e2cd3580f8e28beea9e8f9194b9cf8547472dd/src/testutils.rs#L26) +wrapper test class. + +## Signing messages + +The two +[examples](https://github.com/stellar//soroban-token-contract/blob/37e2cd3580f8e28beea9e8f9194b9cf8547472dd/src/testutils.rs#L55) +from the test `Token` wrapper class demonstrate how you sign a contract function that expects an `Authorization` and a `KeyedAuthorization` type. + +### Authorization +The admin must match the `Identifier` that was used to `initialize` the contract. +```rust +pub fn mint(&self, admin: &Keypair, to: &Identifier, amount: &BigInt) { + let mut args: Vec = Vec::new(&self.env); + args.push(to.clone().into_env_val(&self.env)); + args.push(amount.clone().into_env_val(&self.env)); + let msg = Message::V0(MessageV0 { + nonce: self.nonce(&to_ed25519(&self.env, admin)), + domain: Domain::Mint as u32, + parameters: args, + }); + let auth = Authorization::Ed25519(admin.sign(msg).unwrap().into_val(&self.env)); + mint(&self.env, &self.contract_id, &auth, to, amount) +} +``` + +### KeyedAuthorization +```rust + pub fn approve(&self, from: &Keypair, spender: &Identifier, amount: &BigInt) { + let mut args: Vec = Vec::new(&self.env); + args.push(spender.clone().into_env_val(&self.env)); + args.push(amount.clone().into_env_val(&self.env)); + let msg = Message::V0(MessageV0 { + nonce: self.nonce(&to_ed25519(&self.env, from)), + domain: Domain::Approve as u32, + parameters: args, + }); + let auth = KeyedAuthorization::Ed25519(KeyedEd25519Authorization { + public_key: from.public.to_bytes().into_val(&self.env), + signature: from.sign(msg).unwrap().into_val(&self.env), + }); + approve(&self.env, &self.contract_id, &auth, spender, amount) +} +``` \ No newline at end of file