Skip to main content
Version: Next

Contracts

Accounts store contracts. A contract can also just be an interface.

An account exposes its inbox through the contracts field, which has the type Account.Contracts.

Account.Contracts

access(all)
struct Contracts {

/// The names of all contracts deployed in the account.
access(all)
let names: [String]

/// Returns the deployed contract for the contract/contract interface with the given name in the account, if any.
///
/// Returns nil if no contract/contract interface with the given name exists in the account.
access(all)
view fun get(name: String): DeployedContract?

/// Returns a reference of the given type to the contract with the given name in the account, if any.
///
/// Returns nil if no contract with the given name exists in the account,
/// or if the contract does not conform to the given type.
access(all)
view fun borrow<T: &Any>(name: String): T?

/// Adds the given contract to the account.
///
/// The `code` parameter is the UTF-8 encoded representation of the source code.
/// The code must contain exactly one contract or contract interface,
/// which must have the same name as the `name` parameter.
///
/// All additional arguments that are given are passed further to the initializer
/// of the contract that is being deployed.
///
/// The function fails if a contract/contract interface with the given name already exists in the account,
/// if the given code does not declare exactly one contract or contract interface,
/// or if the given name does not match the name of the contract/contract interface declaration in the code.
///
/// Returns the deployed contract.
access(Contracts | AddContract)
fun add(
name: String,
code: [UInt8]
): DeployedContract

/// Updates the code for the contract/contract interface in the account.
///
/// The `code` parameter is the UTF-8 encoded representation of the source code.
/// The code must contain exactly one contract or contract interface,
/// which must have the same name as the `name` parameter.
///
/// Does **not** run the initializer of the contract/contract interface again.
/// The contract instance in the world state stays as is.
///
/// Fails if no contract/contract interface with the given name exists in the account,
/// if the given code does not declare exactly one contract or contract interface,
/// or if the given name does not match the name of the contract/contract interface declaration in the code.
///
/// Returns the deployed contract for the updated contract.
access(Contracts | UpdateContract)
fun update(name: String, code: [UInt8]): DeployedContract

/// Removes the contract/contract interface from the account which has the given name, if any.
///
/// Returns the removed deployed contract, if any.
///
/// Returns nil if no contract/contract interface with the given name exists in the account.
access(Contracts | RemoveContract)
fun remove(name: String): DeployedContract?
}

entitlement Contracts

entitlement AddContract
entitlement UpdateContract
entitlement RemoveContract

Deployed contract

Accounts store "deployed contracts," that is, the code of the contract:

access(all)
struct DeployedContract {
/// The address of the account where the contract is deployed at.
access(all)
let address: Address

/// The name of the contract.
access(all)
let name: String

/// The code of the contract.
access(all)
let code: [UInt8]

/// Returns an array of `Type` objects representing all the public type declarations in this contract
/// (e.g. structs, resources, enums).
///
/// For example, given a contract
/// ```
/// contract Foo {
/// access(all) struct Bar {...}
/// access(all) resource Qux {...}
/// }
/// ```
/// then `.publicTypes()` will return an array equivalent to the expression `[Type<Bar>(), Type<Qux>()]`
access(all)
fun publicTypes(): [Type]
}

Note that this is type only provides information about a deployed contract, it is not the contract instance, the result of importing a contract.

Getting a deployed contract

The function contracts.get retrieves a deployed contract:

access(all)
view fun get(name: String): DeployedContract?

The function returns the deployed contract with the given name, if any. If no contract with the given name exists in the account, the function returns nil.

For example, assuming that an account has a contract named Test deployed to it, the contract can be retrieved as follows:

let account = getAccount(0x1)
let contract = account.contracts.get(name: "Test")

Borrowing a deployed contract

Contracts can be "borrowed" to effectively perform a dynamic import dependent on a specific execution path.

This is in contrast to a typical import statement, for example import T from 0x1, which statically imports a contract.

The contracts.borrow function obtains a reference to a contract instance:

access(all)
view fun borrow<T: &Any>(name: String): T?

The functions returns a reference to the contract instance stored with that name on the account, if it exists, and if it has the provided type T. If no contract with the given name exists in the account, the function returns nil.

For example, assuming that a contract named Test which conforms to the TestInterface interface is deployed to an account, a reference to the contract instance can obtained be as follows:

let account = getAccount(0x1)
let contract: &TestInterface = account.contracts.borrow<&TestInterface>(name: "Test")

This is similar to the import statement

import Test from 0x1

Deploying a new contract

The contracts.add function deploys a new contract to an account:

access(Contracts | AddContract)
fun add(
name: String,
code: [UInt8],
... contractInitializerArguments
): DeployedContract

Calling the add function requires access to an account via a reference which is authorized with the coarse-grained Contracts entitlement (auth(Contracts) &Account), or the fine-grained AddContract entitlement (auth(AddContract) &Account).

The code parameter is the UTF-8 encoded representation of the source code. The code must contain exactly one contract or contract interface, which must have the same name as the name parameter.

The add function passes all extra arguments of the call (contractInitializerArguments) to the initializer of the contract.

If a contract with the given name already exists in the account, if the given code does not declare exactly one contract or contract interface, or if the given name does not match the name of the contract declaration in the code, then the function aborts the program.

When the deployment succeeded, the function returns the deployed contract.

For example, assuming the following contract code should be deployed:

access(all)
contract Test {
access(all)
let message: String

init(message: String) {
self.message = message
}
}

The contract can be deployed as follows:

transaction(code: String) {
prepare(signer: auth(AddContract) &Account) {
signer.contracts.add(
name: "Test",
code: code.utf8,
message: "I'm a new contract in an existing account"
)
}
}

Updating a deployed contract

The contracts.update function updates the code of an existing contract:

access(Contracts | UpdateContract)
fun update(name: String, code: [UInt8]): DeployedContract

Calling the update function requires access to an account via a reference which is authorized with the coarse-grained Contracts entitlement (auth(Contracts) &Account), or the fine-grained UpdateContract entitlement (auth(UpdateContract) &Account).

The code parameter is the UTF-8 encoded representation of the source code. The code must contain exactly one contract or contract interface, which must have the same name as the name parameter.

If no contract with the given name exists in the account, if the given code does not declare exactly one contract or contract interface, or if the given name does not match the name of the contract declaration in the code, then the function aborts the program.

When the update succeeded, the function returns the deployed contract.

info

The update function does not run the initializer of the contract again.

Updating a contract does not change the contract instance and its existing stored data. A contract update only changes the code a contract.

Is only possible to update contracts in ways that keep data consistency. Certain restrictions apply.

For example, assuming that a contract named Test is already deployed to the account, and it should be updated with the following contract code:

access(all) contract Test {
access(all) let message: String

init(message: String) {
self.message = message
}
}

The contract can be updated as follows:

transaction(code: String) {
prepare(signer: auth(UpdateContract) &Account) {
signer.contracts.update(
name: "Test",
code: code
)
}
}

Removing a deployed contract

The contracts.remove function removes a deployed contract from an account:

access(Contracts | RemoveContract)
fun remove(name: String): DeployedContract?

Calling the remove function requires access to an account via a reference which is authorized with the coarse-grained Contracts entitlement (auth(Contracts) &Account), or the fine-grained RemoveContract entitlement (auth(RemoveContract) &Account).

The function removes the contract from the account which has the given name and returns it. If no contract with the given name exists in the account, the function returns nil.

For example, assuming that a contract named Test is deployed to an account, the contract can be removed as follows:

transaction(code: String) {
prepare(signer: auth(RemoveContract) &Account) {
signer.contracts.remove(name: "Test",)
}
}