diff --git a/docs/built-in-contracts/token.mdx b/docs/built-in-contracts/token.mdx index 0bcb95e6..c9f54aba 100644 --- a/docs/built-in-contracts/token.mdx +++ b/docs/built-in-contracts/token.mdx @@ -1,49 +1,91 @@ --- sidebar_position: 1 -title: Token Contract +title: Stellar Asset Contract --- -# Token Contract +# Stellar Asset Contract -The token contract is an implementation of [CAP-46-6 Smart Contract Standardized Asset]. +The Stellar Asset Contract is an implementation of [CAP-46-6 Smart Contract Standardized Asset]. [CAP-46-6 Smart Contract Standardized Asset]: https://stellar.org/protocol/cap-46-06 :::caution The token contract is in early development, has not been audited, and is -intended for use in development and testing only at this stage.. Report issues +intended for use in development and testing only at this stage. Report issues [here](https://github.com/stellar/soroban-token-contract/issues/new/choose). ::: ## Overview -Tokens are a vital part of blockchains, and contracts that implement token -functionality are inevitable on any smart contract platform. A built-in token -contract has a number of advantages over token contracts written by the -ecosystem. 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 built-in contract to allow "classic" Stellar assets to interoperate -with Soroban. Note that this standard token contract does not prevent the -ecosystem from developing other token contracts if the standard is missing -functionality they require. +Stellar has numerous assets on its classic network, and being able to use them +in Soroban would give users much more flexibility with how they can use their +assets. For this reason, we introduced the Stellar Asset Contract, or SAC for +short, which will allow users to use their Stellar account and trustline +balances in Soroban. -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. +The SAC implements the [token interface](../common-interfaces/token.mdx), which is +similar to the widely used ERC-20 token standard. This should make it easier for +existing smart contract developers to get started on Stellar. -## Token contract authorization semantics +## Deployment + +For every 'classic' asset exactly one respective Stellar Asset Contract can be +deployed. It can be deployed using the `InvokeHostFunctionOp` with +`HOST_FUNCTION_TYPE_CREATE_CONTRACT` and `CONTRACT_ID_FROM_ASSET` specified +[here](../tutorials/invoking-contracts-with-transactions). The resulting token +will have a deterministic identifier, which will be the sha256 hash of +`HashIDPreimage::ENVELOPE_TYPE_CONTRACT_ID_FROM_ASSET` xdr specified +[here](https://github.com/stellar/stellar-xdr/blob/026c9cd074bdb28ddde8ee52f2a4502d9e518a09/Stellar-transaction.x#L637). + +## Interacting with classic Stellar assets + +The Stellar Asset Contract is the only way to interact with 'classic' Stellar assets in +Soroban. 'Classic' assets include native Stellar token (lumens) and all the +existing trustlines. The issuer of the asset will be the +administrator of the deployed contract. Because the the Native Stellar token +doesn't have an issuer, it will not have an administrator either. It also cannot +be burned. + +After the contract has been deployed, users can use their classic account or +trustline balance. There are some differences depending on if you are using a +classic account identifier vs a non-account identifier like a contract. + +- Using `Identifier::Account` + - The balance must exist in a trustline or an account. This means the contract + will not store the balance in ContractData. If the trustline or account is + missing, any function that tries to interact with that balance will fail. + - Classic trustline semantics will be followed. + - Transfers will only succeed if the corresponding + trustline(s) have the `AUTHORIZED_FLAG` set. + - A trustline balance can only be clawed back using the `clawback` + contract function if the trustline has `TRUSTLINE_CLAWBACK_ENABLED_FLAG` + set. + - The admin can only deauthorize a trustline if the issuer of the asset has + `AUTH_REVOCABLE` set. The deauthorization will fail if the issuer is + missing. Note that when a trustline is deauthorized from Soroban, + `AUTHORIZED_FLAG` is cleared and `AUTHORIZED_TO_MAINTAIN_LIABILITIES_FLAG` is + set to avoid having to pull offers and redeeming pool shares. + - Transfers to the issuer account will burn the token, while transfers from + the issuer account will mint. + - Trustline balances are stored in a 64-bit signed integer. +- Using `Identifier::Ed25519` or `Identifier::Contract` + - The balance and authorization state will be stored in ContractData, as + opposed to a trustline. + - These balances are stored in a 128-bit signed integer. + +## Authorization semantics See the [advanced auth example](../examples/auth-advanced) for an overview of authorization. -### Token operations +### SAC operations The token contract contains three kinds of operations - getters, such as `balance`, which do not change the state of the contract -- unprivileged mutators, such as `approve` and `xfer`, which change the state of +- unprivileged mutators, such as `incr_allow` and `xfer`, which change the state of the contract but do not require special privileges -- privileged mutators, such as `burn` and `set_admin`, which change the state of +- privileged mutators, such as `clawback` and `set_admin`, which change the state of the contract but require special privileges Getters require no authorization because they do not change the state of the @@ -52,8 +94,8 @@ the balance of the specified identity without changing it. Unprivileged mutators require authorization from some identity. The identity which must provide authorization will vary depending on the unprivileged -mutator. For example, a "grantor" can use `approve` to allow a "spender" to spend -the grantor's money up to some limit. So for approve, the grantor must provide +mutator. For example, a "grantor" can use `incr_allow` to allow a "spender" to spend +the grantor's money up to some limit. So for `incr_allow`, the grantor must provide authorization. Similarly, a "sender" can use `xfer` to send money to a "recipient". So for `xfer`, the sender must provide authorization. @@ -128,116 +170,10 @@ token.mint( ); ``` -## Interacting with classic Stellar assets - -Token contract is the only way to interact with 'classic' Stellar assets in -Soroban. 'Classic' assets include native Stellar token (lumens) and all the -existing trustlines. - -For every 'classic' asset exactly one respective token contract can be deployed -via `create_token_from_asset` host function. The resulting token will have a -deterministic identifier. The issuer of the asset will be the administrator of -the deployed contract. Native Stellar token doesn't have an administrator. - -After the contract has been deployed the users can use `import` function to move -part of their existing balance to the contract or `export` to move the token -balance back to their 'classic' balance. Otherwise, the token will behave in -exactly the same way as any other token, i.e. users that don't have a -corresponding trustline or even a Stellar account can still use it (with the -exception of the `import`/`export` functions). - ## Contract Interface -This interface can be found in the [SDK](https://github.com/stellar/rs-soroban-sdk/blob/47895a87b98a2fb8dd882839df7abde03f6c7fc6/soroban-token-spec/src/lib.rs#L27). It -extends the common [token interface](../common-interfaces/token.mdx) with -`import`/`export` functions for classic asset interactions. - -```rust -// The metadata used to initialize token (doesn't apply to contracts representing -// 'classic' Stellar assets). -pub struct TokenMetadata { - pub name: Bytes, - pub symbol: Bytes, - pub decimals: u32, -} - -// Initializes a 'smart-only' token by setting its admin and metadata. -// Tokens that represent 'classic' Stellar assets don't need to call this, as -// their metadata is inherited from the existing assets. -fn init(env: Env, admin: Identifier, metadata: TokenMetadata); - -// Functions that apply on for tokens representing classic assets. - -// Moves the `amount` from classic asset balance to the token balance of `id` -// user. -// `id` must be a classic Stellar account (i.e. an account invoker or signature -// signed by an account). -fn import(env: Env, id: Signature, nonce: i128, amount: i64); - -// Moves the `amount` from token balance to the classic asset balance of `id` -// user. -// `id` must be a classic Stellar account (i.e. an account invoker or signature -// signed by an account). -fn export(env: Env, id: Signature, nonce: i128, amount: i64); - -// Admin interface -- these functions are privileged - -// If "admin" is the administrator, burn "amount" from "from" -fn burn(e: Env, admin: Signature, nonce: i128, from: Identifier, amount: i128); - -// If "admin" is the administrator, mint "amount" to "to" -fn mint(e: Env, admin: Signature, nonce: i128, to: Identifier, amount: i128); - -// If "admin" is the administrator, set the administrator to "id" -fn set_admin(e: Env, admin: Signature, nonce: i128, new_admin: Identifier); - -// If "admin" is the administrator, freeze "id" -fn freeze(e: Env, admin: Signature, nonce: i128, id: Identifier); - -// If "admin" is the administrator, unfreeze "id" -fn unfreeze(e: Env, admin: Signature, nonce: i128, id: Identifier); - -// Token Interface - -// Get the allowance for "spender" to transfer from "from" -fn allowance(e: Env, from: Identifier, spender: Identifier) -> i128; - -// Set the allowance to "amount" for "spender" to transfer from "from" -fn approve(e: Env, from: Signature, nonce: i128, spender: Identifier, amount: i128); - -// Get the balance of "id" -fn balance(e: Env, id: Identifier) -> i128; - -// Transfer "amount" from "from" to "to" -fn xfer(e: Env, from: Signature, nonce: i128, to: Identifier, amount: i128); - -// Transfer "amount" from "from" to "to", consuming the allowance of "spender" -fn xfer_from( - e: Env, - spender: Signature, - nonce: i128, - from: Identifier, - to: Identifier, - amount: i128, -); - -// 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) -> i128; - -// 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) -> Bytes; - -// Get the symbol for this token -fn symbol(e: Env) -> Bytes; -``` +This interface can be found in the [SDK](https://github.com/stellar/rs-soroban-sdk/blob/main/soroban-token-spec/src/lib.rs). It +extends the common [token interface](../common-interfaces/token.mdx). ## Interacting with the token contract in tests diff --git a/docs/common-interfaces/token.mdx b/docs/common-interfaces/token.mdx index 51e26490..1bbccb95 100644 --- a/docs/common-interfaces/token.mdx +++ b/docs/common-interfaces/token.mdx @@ -3,7 +3,7 @@ sidebar_position: 1 title: Token Interface --- -Token contracts, including the built-in token and example token implementations +Token contracts, including the Stellar Asset Contract and example token implementations expose the following common interface. Tokens deployed on Soroban can implement any interface they choose, however, @@ -12,16 +12,13 @@ built to support Soroban's built-in tokens. ```rust pub trait Contract { - /// Sets the administrator to "admin" and metadata. - fn init(env: soroban_sdk::Env, admin: soroban_auth::Identifier, metadata: TokenMetadata); - // -------------------------------------------------------------------------------- // Admin interface – privileged functions. // -------------------------------------------------------------------------------- - /// If "admin" is the administrator, burn "amount" from "from". - /// Emit event with topics = ["burn", from: Identifier, to: Identifier], data = [amount: i128] - fn burn( + /// If "admin" is the administrator, clawback "amount" from "from". "amount" is burned. + /// Emit event with topics = ["clawback", from: Identifier, to: Identifier], data = [amount: i128] + fn clawback( env: soroban_sdk::Env, admin: soroban_auth::Signature, nonce: i128, @@ -48,22 +45,15 @@ pub trait Contract { new_admin: soroban_auth::Identifier, ); - /// If "admin" is the administrator, freeze "id". - /// Emit event with topics = ["freeze", admin: Identifier], data = [id: Identifier] - fn freeze( - env: soroban_sdk::Env, - admin: soroban_auth::Signature, - nonce: i128, - id: soroban_auth::Identifier, - ); - - /// If "admin" is the administrator, unfreeze "id". - /// Emit event with topics = ["unfreeze", admin: Identifier], data = [id: Identifier] - fn unfreeze( + /// If "admin" is the administrator, set the authorize state of "id" to "authorize". + /// If "authorize" is true, "id" should be able to use its balance. + /// Emit event with topics = ["set_auth", admin: Identifier, id: Identifier], data = [authorize: bool] + fn set_auth( env: soroban_sdk::Env, admin: soroban_auth::Signature, nonce: i128, id: soroban_auth::Identifier, + authorize: bool, ); // -------------------------------------------------------------------------------- @@ -77,9 +67,20 @@ pub trait Contract { spender: soroban_auth::Identifier, ) -> i128; - /// Set the allowance to "amount" for "spender" to transfer from "from". - /// Emit event with topics = ["approve", from: Identifier, spender: Identifier], data = [amount: i128] - fn approve( + /// Increase the allowance by "amount" for "spender" to transfer/burn from "from". + /// Emit event with topics = ["incr_allow", from: Identifier, spender: Identifier], data = [amount: i128] + fn incr_allow( + env: soroban_sdk::Env, + from: soroban_auth::Signature, + nonce: i128, + spender: soroban_auth::Identifier, + amount: i128, + ); + + /// Decrease the allowance by "amount" for "spender" to transfer/burn from "from". + /// If "amount" is greater than the current allowance, set the allowance to 0. + /// Emit event with topics = ["decr_allow", from: Identifier, spender: Identifier], data = [amount: i128] + fn decr_allow( env: soroban_sdk::Env, from: soroban_auth::Signature, nonce: i128, @@ -90,6 +91,11 @@ pub trait Contract { /// Get the balance of "id". fn balance(env: soroban_sdk::Env, id: soroban_auth::Identifier) -> i128; + /// Get the spendable balance of "id". This will return the same value as balance() + /// unless this is called on the Stellar Asset Contract, in which case this can + /// be less due to reserves/liabilities. + fn spendable(env: soroban_sdk::Env, id: soroban_auth::Identifier) -> i128; + /// Transfer "amount" from "from" to "to. /// Emit event with topics = ["transfer", from: Identifier, to: Identifier], data = [amount: i128] fn xfer( @@ -111,8 +117,27 @@ pub trait Contract { amount: i128, ); - // Returns true if "id" is frozen. - fn is_frozen(env: soroban_sdk::Env, id: soroban_auth::Identifier) -> bool; + /// Burn "amount" from "from". + /// Emit event with topics = ["burn", from: Identifier], data = [amount: i128] + fn burn( + env: soroban_sdk::Env, + from: soroban_auth::Signature, + nonce: i128, + amount: i128, + ); + + /// Burn "amount" from "from", consuming the allowance of "spender". + /// Emit event with topics = ["burn", from: Identifier], data = [amount: i128] + fn burn_from( + env: soroban_sdk::Env, + spender: soroban_auth::Signature, + nonce: i128, + from: soroban_auth::Identifier, + amount: i128, + ); + + // Returns true if "id" is authorized to use its balance. + fn authorized(env: soroban_sdk::Env, id: soroban_auth::Identifier) -> bool; // Returns the current nonce for "id". fn nonce(env: soroban_sdk::Env, id: soroban_auth::Identifier) -> i128; @@ -133,17 +158,7 @@ pub trait Contract { ``` Most types that a token implementation utilizes are provided by the -[soroban-sdk] and [soroban-auth] crates. The following type is also used during -initialization. - -```rust -#[soroban_sdk::contracttype] -pub struct TokenMetadata { - pub name: soroban_sdk::Bytes, - pub symbol: soroban_sdk::Bytes, - pub decimals: u32, -} -``` +[soroban-sdk] and [soroban-auth] crates. [soroban-sdk]: ../SDKs/rust [soroban-auth]: ../SDKs/rust-auth