Skip to content

Latest commit

 

History

History
309 lines (225 loc) · 12.8 KB

cap-0046-02.md

File metadata and controls

309 lines (225 loc) · 12.8 KB

Preamble

CAP: 0046-02 (formerly 0047)
Title: Smart Contract Lifecycle
Working Group:
    Owner: Siddharth Suresh <@sisuresh>
    Authors: Siddharth Suresh <@sisuresh>, Dmytro Kozhevin <@dmkozh>
    Consulted: Graydon Hoare <@graydon>, Jon Jove <@jonjove>, Leigh McCulloch <@leighmcculloch>, Nicolas Barry <@MonsieurNicolas>
Status: Draft
Created: 2022-05-02
Discussion:
Protocol version: TBD

Simple Summary

This proposal defines the structure of smart contracts on Stellar and specifies how users can create them.

Motivation and Goals Alignment

See the Soroban overview CAP.

Abstract

Users need a way to manage smart contracts on the network. This CAP allows users to deploy the smart contracts to the network and specifies the supported contract code kinds.

This CAP also lets the validators turn off all smart contract functionality if some unexpected behavior is found in the protocol.

Specification

XDR

See the XDR diffs in the Soroban overview CAP, specifically those referring to HOST_FUNCTION_TYPE_CREATE_CONTRACT and HOST_FUNCTION_TYPE_INSTALL_CONTRACT_CODE.

Semantics

Contract structure

This defines the terms we use in the following sections without going into their design and implementation details.

Contract source

Contract source can be thought of as a 'class' of a contract. Multiple contracts can share the same source, but have their own state. Thanks to that sharing capability, we can reduce the amount of duplication in ledger and only store unique contract sources.

This CAP defines two possible kinds of contract sources:

  • WASM source: a blob of WASM code that is stored in a separate ledger entry and is deduplicated based on contents. This is installed to ledger by the users.
  • Built-in contract: this is a 'source' compiled into host directly that has a protocol-defined interface and behavior.

Contract instance

Contract instance can be thought of as an instance of the contract 'class'. Contract instance consists of:

  • Identifier: SHA-256 hash of a pre-image payload
  • Source reference: a pointer to the WASM source or a tag of a built-in contract

A contract instance may own an arbitrary amount of ledger entries attributed to its identifier. Contracts that share the same source in no way may influence each other; from the perspective of a contract invoker there is no difference between calling the contracts with the same or different source references (besides the possible contract-defined behavior differences).

Contract identifier preimage types

This CAP defines the following supported contract identifier preimage kinds. The use cases for every identifier type are described in the following sections. Every preimage is a part of the HashIDPreimage union and has a unique tag associated with it in order to ensure that there are no collisions with other hashes in the protocol.

  • ENVELOPE_TYPE_CONTRACT_ID_FROM_ED25519: built from an ed25519 public key and the user-specified uint256 salt.
  • ENVELOPE_TYPE_CONTRACT_ID_FROM_SOURCE_ACCOUNT: built from a Stellar account identifier and the user-specified uint256 salt.
  • ENVELOPE_TYPE_CONTRACT_ID_FROM_ASSET: built from a Stellar Asset structure.
  • ENVELOPE_TYPE_CONTRACT_ID_FROM_CONTRACT: built from another contract identifier and contract-specified uint256 salt.

Every preimage must also include a networkID (a hash of the network passphrase) which ensures that every network has unique set of contract identifiers.

Installing WASM sources using InvokeHostFunctionOp

WASM contract sources can be installed to the network without instantiating a contract via InvokeHostFunctionOp(defined in CAP-0046-04) with HOST_FUNCTION_TYPE_INSTALL_CONTRACT_CODE host function type.

This function accepts InstallContractCodeArgs struct that contains the WASM contract code.

Installed contracts are stored in ContractCodeEntry ledger entries. These entries are keyed by the hash of InstallContractCodeArgs used to install them.

Contract installation host function will compute the hash of InstallContractCodeArgs and check if such a contract code already exists. If the entry exists, the operation will immediately succeed. If it doesn't, the new ContractCodeEntry will be created.

Host does not perform any validation on the installed contract code, besides checking its size.

Max contract size setting

The maximum WASM contract size will be introduced as a ConfigSettingEntry(see CAP-0046-09 for details on config entries).

It is set during the protocol version upgrade using a new ConfigSettingEntry, with configSettingID == CONFIG_SETTING_CONTRACT_MAX_SIZE_BYTES, and contractMaxSizeBytes == 16384. The valid values for contractMaxSizeBytes are[0, 256000] (inclusive).

No contract-driven WASM installation

This CAP does not specify a way to install WASM sources from within a contract.

This is done to encourage efficient code reuse and deduplication: if the contract was allowed to install the WASM code, then we'd need to store it twice (in the installer contract and in the source entry).

Instantiating contracts using InvokeHostFunctionOp

Contracts can be instantiated via InvokeHostFunctionOp with HOST_FUNCTION_TYPE_CREATE_CONTRACT host function type.

The function accepts CreateContractArgs struct that defines the input for building the contract identifier preimage (contractID field) and the contract source reference (source field).

All the preimage types besides ENVELOPE_TYPE_CONTRACT_ID_FROM_CONTRACT can be built from the contractID field.

The source and identifier arguments are normally independent of each other with an exception: identifiers that are built from CONTRACT_ID_FROM_ASSET may only be used in conjunction with built-in token contract source. This handles the special case of instantiating token contracts corresponding to the classic Stellar assets (see more details in CAP-0046-06).

The host builds the actual contract identifier by computing SHA-256 of the HashIDPreimage corresponding to the contractID. If the contract identifier already exists, the operation fails.

If the identifier is new, the host will a new ContractDataEntry from CAP-0046-05 with a SCV_STATIC key type, and SCS_LEDGER_KEY_CONTRACT_CODE key value. The value of the entry is SCContractCode that either refers to the WASM code entry or to a built-in contract (according to the value of source field in CreateContractArgs).

ED25519-based contract identifiers

Building a ENVELOPE_TYPE_CONTRACT_ID_FROM_ED25519 preimage based on a public ED25519 key has an a additional signature verification step as to make sure that this key has authorized creating a contract on their behalf.

The owner of the key must sign SHA-256 hash of HashIDPreimage of type ENVELOPE_TYPE_CREATE_CONTRACT_ARGS, that includes the network id, salt, and CreateContractSource args that must match the respective args of HOST_FUNCTION_TYPE_CREATE_CONTRACT invocation.

Installation fused with instantiation

One of the possible types of CreateContractSource is CONTRACT_SOURCE_INSTALLED, that accepts InstallContractCodeArgs. This is a convenience argument that allows to install the code and instantiate a contract using that code in a single operation.

The installation implementation is exactly the same as for the case when HOST_FUNCTION_TYPE_INSTALL_CONTRACT_CODE function is called. The contract will be instantiated with WASM code reference source type that points to the hash of the newly installed contract.

As mentioned in the installation section, if the contract code already exists in the ledger, the operation will still succeed, but no code entry will be created.

Instantiating a contract from a contract

Factory contracts are quite popular already on other networks, so this CAP adds functionality to support them.

The following host functions are provided to instantiate contracts:

// Instantiates a contract with the source referring to the provided wasm_hash.
fn create_contract_from_contract(wasm_hash: Object /* 32-bytes array */,
                                 salt: Object /* 32-bytes array */) -> Object /* 32-bytes array */
// Instantiates a contract with the source referring to the built-in token.
fn create_token_from_contract(salt: Object /* 32-bytes array */) -> Object /* 32-bytes array */

All of these functions return the identifier of the newly created contract.

The identifier of the created contract is generated by hashing the HashIDPreimage with type ENVELOPE_TYPE_CONTRACT_ID_FROM_CONTRACT with the salt provided by the host function call.

Validator override

This proposal adds two new LedgerHeader flags that can disable the create and invoke contract operations using upgrades. The validators can use this mechanism in case unexpected behaviour is seen. We also considered adding a mechanism for validators to opt accounts into smart contracts to allow for a "soft" launch, but the implementation changes to get this to work are not simple. The validators have the LedgerHeader overrides to fall back on, so it's not clear that the complexity of adding a "soft" launch mechanism is worth it.

Design Rationale

There are no built in controls for contracts

Controls like pausing invocation or mutability for all or a subset of a contract should be put into a contract itself. Leaving it to the contract writer is a much more general solution than baking it into the protocol. The downside is this is more error prone and will take more space since the same logic will be implemented multiple times.

ContractDataEntry has no owner associated with it

The entity that creates the ContractDataEntry that contains the contract code is not tied to it in any way. This allows for contract management and authorization to be handled in the contract using whichever custom mechanism the contract creator chooses.

ContractCodeEntry has no owner associated with it

Contract source code entries with the WASM code don't have any ownership. Anyone can install contract sources to the ledger and then anyone can use them. This encourages sharing the contract code and allows contracts that use it to be sure that their implementation can't unexpectedly change.

Contracts cannot be updated and deleted

The contract code reference is stored in a ContractDataEntry, but contract code cannot be updated or deleted in the initial version. The host functions in CAP-0046-05 to update or delete ContractDataEntry should trap if they are used on contract code.

Malicious contracts

The validators do not have a mechanism to ban specific contracts. Any kind of targeted banning mechanism can be worked around quite easily by creating new accounts and contracts.

Maximum WASM contract code size is configurable

The maximum contract size will be set during the protocol upgrade, and can be updated by the validators. This allows to adjust the contract sizes depending on the demand and network load requirements.

ContractIDs are deterministic

Pulling contractIDs from LedgerHeader.idPool would be easier but it would make parallelizing contract creation more difficult in the future. It's also more difficult to determine what the contractID will be since the id pool would be used by offers and other contracts. This CAP uses a Hash instead as the contractID.

With this CAP we provide several ways of building the contractID preimages that can be reproduced off-chain and then used to address the contracts that may or may not exist (for example, some general contracts like tokens or AMMs).

Security Concerns

The security concerns from CAP-0046 (https://github.com/stellar/stellar-protocol/blob/master/core/cap-0046.md#security-concerns) apply here as well.

In addition to those concerns, this CAP does not provide validators with much control over contracts on the network. The only mechanism they have is blocking all contract creations and invocations, which should only be used in drastic situations. This CAP does not define a way for validators to block specific contracts.

Potential Future Work

Mutable contracts support

While the contracts are immutable in this CAP, it's already possible to make them 'mutable' via proxy contracts (for example, contract A forwards its method calls to contract B and ID of contract B is stored in the data of the contract A).

To further support mutation via the proxy pattern we could do the following:

  • Allow contract to modify its SCS_LEDGER_KEY_CONTRACT_CODE entry (as the modification can only happen from within the contract, this would need to be implemented in the first installed version of the contract)
  • Allow specifying a contract ID as the contract's source reference, so that the contract would be guaranteed to have exactly the same implementation as the referred contract without any additional code.