Skip to main content

Invoking and Creating Contracts with Stellar Transactions

Stellar supports invoking and deploying contracts with a new Operation named InvokeHostFunctionOp. The soroban-cli abstracts these details away from the user, but SDKs do not yet and if you're building a dapp you'll probably find yourself building the XDR transaction to submit to the network.

The InvokeHostFunctionOp can be used to perform the following Soroban operations:

  • Invoke contract functions.
  • Install WASM of the new contracts.
  • Deploy new contracts using the installed WASM or built-in implementations (this currently includes only the token contract).

InvokeHostFunctionOp

The XDR of HostFunction and InvokeHostFunctionOp below can be found here.

union HostFunction switch (HostFunctionType type)
{
case HOST_FUNCTION_TYPE_INVOKE_CONTRACT:
SCVec invokeArgs;
case HOST_FUNCTION_TYPE_CREATE_CONTRACT:
CreateContractArgs createContractArgs;
case HOST_FUNCTION_TYPE_INSTALL_CONTRACT_CODE:
InstallContractCodeArgs installContractCodeArgs;
};

struct InvokeHostFunctionOp
{
// The host function to invoke
HostFunction function;
// The footprint for this invocation
LedgerFootprint footprint;
// Per-address authorizations for this host fn
// Currently only supported for INVOKE_CONTRACT function
ContractAuth auth<>;
};

Function

The function in InvokeHostFunctionOp will be executed by the Soroban host environment. The function arguments are passed as function-dependent XDR. The options are:

  1. HOST_FUNCTION_TYPE_INVOKE_CONTRACT
    • This will call the call_n host function, invoking a contract function.
    • invokeArgs is expected to contain the contract id, contract function name, and the parameters to the contract function being invoked.
  2. HOST_FUNCTION_TYPE_INSTALL_CONTRACT_CODE
    • This will install the contract WASM using the provided code blob:
    struct InstallContractCodeArgs
    {
    opaque code<SCVAL_LIMIT>;
    };
  3. HOST_FUNCTION_TYPE_CREATE_CONTRACT
    • This will create a contract instance in the network using the specified source and build a 32-byte contract identifer based on contractID value.
     struct CreateContractArgs
    {
    ContractID contractID;
    SCContractCode source;
    };
    • source can be either a reference to the hash of the installed WASM (to be more precise, a SHA-256 hash of the InstallContractCodeArgs from the operation above) or just specify that a built-in contract has to be used:
      union SCContractCode switch (SCContractCodeType type)
      {
      case SCCONTRACT_CODE_WASM_REF:
      Hash wasm_id;
      case SCCONTRACT_CODE_TOKEN:
      void;
      };
  • contractID is defined as following:
    union ContractID switch (ContractIDType type)
    {
    case CONTRACT_ID_FROM_SOURCE_ACCOUNT:
    uint256 salt;
    case CONTRACT_ID_FROM_ED25519_PUBLIC_KEY:
    struct
    {
    uint256 key;
    Signature signature;
    uint256 salt;
    } fromEd25519PublicKey;
    case CONTRACT_ID_FROM_ASSET:
    Asset asset;
    };
  • The parameters of this value define which the hash preimage that is then hashed with SHA-256 to get the ID of the created contract.
  • CONTRACT_ID_FROM_SOURCE_ACCOUNT specifies that the contract ID will be created using the Stellar source account and the provided salt. The contract ID preimage used is ENVELOPE_TYPE_CONTRACT_ID_FROM_SOURCE_ACCOUNT.
  • CONTRACT_ID_FROM_ASSET specifies that the contract will be created using the Stellar asset. This is only supported when source == SCCONTRACT_CODE_TOKEN. Note, that the asset doesn't need to exist when this is applied, however the issuer of the asset will be the initial token administrator. The contract ID preimage used is ENVELOPE_TYPE_CONTRACT_ID_FROM_ASSET.
  • CONTRACT_ID_FROM_ED25519_PUBLIC_KEY specified that the contract will be created using a public ED25519 key. Since this operation is not tied to any on-chain entity, this also has to contain an ED25519 signature of SHA256 hash of ENVELOPE_TYPE_CREATE_CONTRACT_ARGS XDR preimage. The contract ID preimage used is ENVELOPE_TYPE_CONTRACT_ID_FROM_ED25519.

Footprint

The footprint must contain the LedgerKeys that will be read and/or written. More information about the footprint can be found in the advanced section of interacting with contracts.

Authorization Data

This is currently only supported for HOST_FUNCTION_TYPE_INVOKE_CONTRACT.

Soroban authorization framework provides a standardized way for passing authorization data to the contract invocations via ContractAuth structures.

struct AddressWithNonce
{
SCAddress address;
uint64 nonce;
};

struct ContractAuth
{
AddressWithNonce* addressWithNonce; // not present for invoker
AuthorizedInvocation rootInvocation;
SCVec signatureArgs;
};

ContractAuth contains a tree of invocations authorized by an Address and the corresponding signature (in Address-dependent format).

In order to build a ContractAuth the following is required:

  • Address that authorizes the invocations and the corresponding nonce.
    • When addressWithNonce is missing, then it means that the transaction source account (or operation source when present) authorizes these invocations. signatureArgs have to be skipped as well in this case.
    • nonce has to correspond to the contractID of the rootInvocation. It is stored in the ledger entry with CONTRACT_DATA key with contractID from rootInvocation and SCVal key with the following value: ScVal::Object(Some(ScObject::NonceKey(address))) (when not present, nonce is equal to 0).
  • rootInvocation structure corresponding to the authorized invocation tree
  • signatureArgs - signature (or multiple signatures) that sign the 32-byte payload that is a SHA-256 hash of ENVELOPE_TYPE_CONTRACT_AUTH preimage (XDR). The signature structure is defined by the Address (see below for the Stellar account signatures). This should be empty if addressWithNonce is not present (as transaction/operation signature is used instead).

AuthorizedInvocation defines a node in the authorized invocation tree:

struct AuthorizedInvocation
{
Hash contractID;
SCSymbol functionName;
SCVec args;
AuthorizedInvocation subInvocations<>;
};

AuthorizedInvocation needs to include a contract, contract function, arguments of require_auth/require_auth_for_args call and all the authorized sub-contract invocations originating from the current node. Building AuthorizedInvocation trees may be simplified by using the recording auth mode in Soroban preflight (see the docs for more details).

Stellar Account Signatures

signatureArgs format is user-defined for the custom accounts, but it is protocol-defined for the Stellar accounts.

The signatures for the Stellar account are a vector of the following Soroban structures in the Soroban SDK format:

#[contracttype]
pub struct AccountEd25519Signature {
pub public_key: BytesN<32>,
pub signature: BytesN<64>,
}