diff --git a/core/cap-0046-06.md b/core/cap-0046-06.md index bf40f88ce..b218d04d1 100644 --- a/core/cap-0046-06.md +++ b/core/cap-0046-06.md @@ -1,6 +1,6 @@ ``` CAP: 0046-06 (formerly 0054) -Title: Smart Contract Standardized Asset +Title: Smart Contract Standardized Asset (Stellar Asset Contract) Working Group: Owner: Jonathan Jove <@jonjove> Authors: Jonathan Jove <@jonjove>, Siddharth Suresh <@sisuresh>, @@ -52,11 +52,8 @@ This CAP is aligned with the following Stellar Network Goals: ## Abstract -This proposal introduces a native contract implementation for tokens which is -suitable for both smart tokens and wrapped classic tokens. The interface tries -to follow an ERC-20 model, although functions perform their own authorization -more like EIP-2612 `permit`. Identifiers in these tokens can be contracts, -ed25519 public keys, or Stellar accounts. +This proposal introduces a native contract implementation for classic tokens. +The interface tries to follow an ERC-20 model. ## Specification @@ -67,8 +64,7 @@ new envelope types. ### Semantics: Data Format -The following types are used throughout this proposal. The comment above each -type describes the way in which the type is serialized as an `SCVal`. +The following types are used throughtout this proposal. ```rust /******************************************************************************\ @@ -77,51 +73,33 @@ type describes the way in which the type is serialized as an `SCVal`. * \******************************************************************************/ -pub struct Ed25519Signature { - pub public_key: BytesN<32>, - pub signature: BytesN<64>, +pub struct AllowanceDataKey { + pub from: Address, + pub spender: Address, } -pub struct AccountSignatures { - pub account_id: AccountId, - pub signatures: Vec, +pub struct AllowanceValue { + pub amount: i128, + pub expiration_ledger: u32, } -pub enum Signature { - Invoker, - Ed25519(Ed25519Signature), - Account(AccountSignatures), +pub struct BalanceValue { + pub amount: i128, + pub authorized: bool, + pub clawback: bool, } -pub enum Identifier { - Contract(BytesN<32>), - Ed25519(BytesN<32>), - Account(AccountId), +/// Keys for the persistent data associated with token users. +pub enum DataKey { + Allowance(AllowanceDataKey), + Balance(Address), } -// Byte arrays and strings can be passed to contracts and stores using the Bytes type. -// BytesN is fixed size, while Bytes is variable -pub struct Bytes(EnvVal); -pub struct BytesN(EnvVal); -``` - -### Semantics: Signatures - -Signatures for this contract are over the following `SignaturePayload` type - -```rust - -pub struct SignaturePayloadV0 { - pub network: Bytes, - pub contract: BytesN<32>, - pub name: Symbol, - pub args: Vec, -} - -pub enum SignaturePayload { - V0(SignaturePayloadV0), +/// Keys for token instance data. +pub enum InstanceDataKey { + Admin, + AssetInfo, } - ``` ### Semantics: Initialization @@ -130,14 +108,19 @@ pub enum SignaturePayload { ```rust /******************************************************************************\ * -* Initialization Interface +* Initialization * \******************************************************************************/ -// Initializes a token contract that does not wrap an asset on the classic side. -// Should be used on a contract created with create_token_from_contract. -// Sets admin, decimals, name, symbol, and the clawback flag. -fn init(admin: Identifier, metadata: TokenMetadata, can_clawback: bool) -> Result<(), Error>; +/// init_asset can create a contract that can interact with a classic asset +/// (Native, AlphaNum4, or AlphaNum12). It will fail if the contractID +/// of this contract does not match the expected contractID for this asset +/// returned by Host::get_asset_contract_id_hash. This function should only be +/// called internally by the host. +/// +/// No admin will be set for the Native token, so any function that checks the admin +/// (clawback, set_auth, mint, set_admin, admin) will always fail +fn init_asset(e: &Host, asset_bytes: Bytes) -> Result<(), HostError>; ``` ### Semantics: Descriptive Interface @@ -155,16 +138,14 @@ token. fn decimals() -> Result; // Get the name for this token -fn name() -> Result; +fn name() -> Result; // Get the symbol for this token -fn symbol() -> Result; +fn symbol() -> Result; ``` ### Semantics: Token Interface -TODO: Specify u128 in CAP-0046 - The token interface provides capabilities analogous to those of ERC-20 tokens. ```rust @@ -173,25 +154,127 @@ The token interface provides capabilities analogous to those of ERC-20 tokens. * Token Interface * \******************************************************************************/ -// Get the allowance for "spender" to transfer from "from" -fn allowance(from: Identifier, spender: Identifier) -> Result; - -// Verify from and nonce. Then increase the allowance by "amount" for "spender" to transfer from "from". -// If an allowance does not exist, set allowance to "amount". -fn increase_allowance(from: Signature, nonce: BigInt, spender: Identifier, amount: u128) -> Result<(), Error>; - -// Verify from and nonce. Then decrease the allowance by "amount" for "spender" to transfer from "from". -// If amount is greater than the existing allowance, set the allowance to 0. -fn decrease_allowance(from: Signature, nonce: BigInt, spender: Identifier, amount: u128) -> Result<(), Error>; - -// Get the balance of "id" -fn balance_of(id: Identifier) -> Result; - -// Verify from and nonce. Then transfer "amount" from "from" to "to" -fn xfer(from: Signature, nonce: BigInt, to: Identifier, amount: u128) -> Result<(), Error>; - -// Verify spender and nonce. Then transfer "amount" from "from" to "to", consuming the allowance of "spender" -fn xfer_from(spender: Signature, nonce: BigInt, from: Identifier, to: Identifier, amount: u128) -> Result<(), Error>; +/// Returns the allowance for `spender` to transfer from `from`. +/// +/// # Arguments +/// +/// * `from` - The address holding the balance of tokens to be drawn from. +/// * `spender` - The address spending the tokens held by `from`. +fn allowance(e: &Host, from: Address, spender: Address) -> Result; + +/// Set the allowance by `amount` for `spender` to transfer/burn from +/// `from`. +/// +/// # Arguments +/// +/// * `from` - The address holding the balance of tokens to be drawn from. +/// * `spender` - The address being authorized to spend the tokens held by +/// `from`. +/// * `amount` - The tokens to be made availabe to `spender`. +/// * `expiration_ledger` - The ledger number where this allowance expires. Cannot +/// be less than the current ledger number unless the amount is being set to 0. +/// An expired entry (where expiration_ledger < the current ledger number) +/// should be treated as a 0 amount allowance. +/// +/// # Events +/// +/// Emits an event with topics `["approve", from: Address, +/// spender: Address], data = [amount: i128, expiration_ledger: u32]` +fn approve( + e: &Host, + from: Address, + spender: Address, + amount: i128, + expiration_ledger: u32, + ) -> Result<(), HostError>; + +/// Returns the balance of `id`. +/// +/// # Arguments +/// +/// * `id` - The address for which a balance is being queried. If the +/// address has no existing balance, returns 0. +fn balance(env: Env, id: Address) -> i128; + +/// Returns the spendable balance of `id`. +/// +/// # Arguments +/// +/// * `id` - The address for which a spendable balance is being queried. +/// 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_balance(env: Env, id: Address) -> i128; + +/// Returns true if `id` is authorized to use its balance. +/// +/// # Arguments +/// +/// * `id` - The address for which token authorization is being checked. +fn authorized(env: Env, id: Address) -> bool; + +/// Transfer `amount` from `from` to `to`. +/// +/// # Arguments +/// +/// * `from` - The address holding the balance of tokens which will be +/// withdrawn from. +/// * `to` - The address which will receive the transferred tokens. +/// * `amount` - The amount of tokens to be transferred. +/// +/// # Events +/// +/// Emits an event with topics `["transfer", from: Address, to: Address], +/// data = [amount: i128]` +fn transfer(env: Env, from: Address, to: Address, amount: i128); + +/// Transfer `amount` from `from` to `to`, consuming the allowance of +/// `spender`. Authorized by spender (`spender.require_auth()`). +/// +/// # Arguments +/// +/// * `spender` - The address authorizing the transfer, and having its +/// allowance consumed during the transfer. +/// * `from` - The address holding the balance of tokens which will be +/// withdrawn from. +/// * `to` - The address which will receive the transferred tokens. +/// * `amount` - The amount of tokens to be transferred. +/// +/// # Events +/// +/// Emits an event with topics `["transfer", from: Address, to: Address], +/// data = [amount: i128]` +fn transfer_from(env: Env, spender: Address, from: Address, to: Address, amount: i128); + +/// Burn `amount` from `from`. +/// +/// # Arguments +/// +/// * `from` - The address holding the balance of tokens which will be +/// burned from. +/// * `amount` - The amount of tokens to be burned. +/// +/// # Events +/// +/// Emits an event with topics `["burn", from: Address], data = [amount: +/// i128]` +fn burn(env: Env, from: Address, amount: i128); + +/// Burn `amount` from `from`, consuming the allowance of `spender`. +/// +/// # Arguments +/// +/// * `spender` - The address authorizing the burn, and having its allowance +/// consumed during the burn. +/// * `from` - The address holding the balance of tokens which will be +/// burned from. +/// * `amount` - The amount of tokens to be burned. +/// +/// # Events +/// +/// Emits an event with topics `["burn", from: Address], data = [amount: +/// i128]` +fn burn_from(env: Env, spender: Address, from: Address, amount: i128); ``` ### Semantics: Admin Interface @@ -205,98 +288,130 @@ compliance functionality. * Admin Interface * \******************************************************************************/ -// If "admin" is the administrator and clawback is not disabled, clawback "amount" from "from". -// The "amount" is burned. This function can be used on deauthorized balances. -fn clawback(admin: Signature, nonce: BigInt, from: Identifier, amount: u128) -> Result<(), Error>; - -// If "admin" is the administrator, clear the authorized flag for "id" -fn deauthorize(admin: Signature, nonce: BigInt, id: Identifier) -> Result<(), Error>; - -// If "admin" is the administrator, set the authorized flag for "id". Without authorization, -// "id" will not be able to use it's balance. -fn authorize(admin: Signature, nonce: BigInt, id: Identifier) -> Result<(), Error>; - -// Returns true if the authorized flag is set -fn is_authorized(id: Identifier) -> Result; - -// If "admin" is the administrator, mint "amount" to "to" -fn mint(admin: Signature, nonce: BigInt, to: Identifier, amount: u128) -> Result<(), Error>; - -// If "admin" is the administrator, set the administrator to "id" -fn set_admin(admin: Signature, nonce: BigInt, new_admin: Identifier) -> Result<(), Error>; - -// If "admin" is the administrator, disable the clawback function for this contract -fn disable_clawback(admin: Signature, nonce: BigInt) -> Result<(), Error>; - -// Returns the current admin. If admin is not set, returns a zero ed25519 key -fn admin() -> Result; -``` - -### Semantics: Classic Wrapper Interface - -The wrapper interface is only provided for classic assets, allowing assets to -flow between classic and smart. - -```rust -/******************************************************************************\ -* -* Wrapper Interface -* -\******************************************************************************/ -// Move "amount" from "id" on classic to "id" on smart -fn import(id: Signature, nonce: BigInt, amount: i64) -> Result<(), Error>; - -// Move "amount" from "id" on smart to "id" on classic -fn export(id: Signature, nonce: BigInt, amount: i64) -> Result<(), Error>; +/// Sets the administrator to the specified address `new_admin`. +/// +/// # Arguments +/// +/// * `new_admin` - The address which will henceforth be the administrator +/// of this token contract. +/// +/// # Events +/// +/// Emits an event with topics `["set_admin", admin: Address], data = +/// [new_admin: Address]` +fn set_admin(env: Env, new_admin: Address); + +/// Returns the admin of the contract. +/// +/// # Panics +/// +/// If the admin is not set. +fn admin(env: Env) -> Address; + +/// Sets whether the account is authorized to use its balance. If +/// `authorized` is true, `id` should be able to use its balance. +/// +/// # Arguments +/// +/// * `id` - The address being (de-)authorized. +/// * `authorize` - Whether or not `id` can use its balance. +/// +/// # Events +/// +/// Emits an event with topics `["set_authorized", id: Address], data = +/// [authorize: bool]` +fn set_authorized(env: Env, id: Address, authorize: bool); + +/// Mints `amount` to `to`. +/// +/// # Arguments +/// +/// * `to` - The address which will receive the minted tokens. +/// * `amount` - The amount of tokens to be minted. +/// +/// # Events +/// +/// Emits an event with topics `["mint", admin: Address, to: Address], data +/// = [amount: i128]` +fn mint(env: Env, to: Address, amount: i128); + +/// Clawback `amount` from `from` account. `amount` is burned in the +/// clawback process. +/// +/// # Arguments +/// +/// * `from` - The address holding the balance from which the clawback will +/// take tokens. +/// * `amount` - The amount of tokens to be clawed back. +/// +/// # Events +/// +/// Emits an event with topics `["clawback", admin: Address, to: Address], +/// data = [amount: i128]` +fn clawback(env: Env, from: Address, amount: i128); ``` ### Semantics: Deployment -#### Deploying a contract that allow wrapping Stellar classic assets +#### Deploying a contract that allows interacting with Stellar classic assets -In order to guarantee uniqueness of wrapper contracts, `create_token_from_asset` -produces a deterministic contract identifier that does not depend on the creator -or any salt. This is achieved by introducing -`ENVELOPE_TYPE_CONTRACT_ID_FROM_ASSET` in -[CAP-0046-02](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0046-02.md#contract-identifier-preimage-types). -The wrapper contracts should be initialized atomically with contract creation so -the right asset is passed in. `create_token_from_asset` should call the `init_asset` -function specified below after the contract is created. +The Stellar Asset Contract can be deployed by using `InvokeHostFunctionOp`, with +a `HostFunction` of type `HOST_FUNCTION_TYPE_CREATE_CONTRACT`. The +`CreateContractArgs` under that will contain a `ContractExecutable` of type +`CONTRACT_EXECUTABLE_TOKEN`, and a `ContractIDPreimage` of type +`CONTRACT_ID_PREIMAGE_FROM_ASSET` which contains the `Asset` being +deployed. -Note that both functions below are not exposed for contracts to call. `create_token_from_asset` -can only be called through `InvokeHostFunctionOp` specified in [CAP-0046-04](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0046-04.md). +In order to guarantee uniqueness of contracts that allow interacting with a +classic Stellar asset, the contractID for any specific Stellar asset is +deterministic because the `ContractIDPreimage` is of type +`CONTRACT_ID_PREIMAGE_FROM_ASSET` which contains only an `Asset`. +The deployment and initilization of these contracts should be atomic, and the host +accomplishes this by calling `init_asset` during deployment of the Stellar Asset Contract. + +Contracts can also deploy the Stellar Asset COntract by calling the `create_asset_contract` host function. ```rust -// Creates a contract that wraps a classic Stellar asset. The asset parameter should be -// the binary representation of the XDR Asset being wrapped. This will also call init_asset on the -// newly created contract to prevent the predetermined contract id for this asset from being taken. -fn create_token_from_asset(asset: Object) -> Result; +// Creates the instance of Stellar Asset contract corresponding to the provided asset. `asset` +// is `stellar::Asset` XDR serialized to bytes format. Returns the address of the created contract. +fn create_asset_contract(asset: Object) -> Result; // This is a built-in token contract function, but only -// intended to be called by create_token_from_asset. -// init_asset will initialize a contract for a wrapped classic asset +// intended to be called by the host when deploying the Stellar Asset Contract. +// init_asset will initialize a contract that can interact with a classic asset // (Native, AlphaNum4, or AlphaNum12). It will fail if the contractID // of this contract does not match the expected contractID for this asset -// returned by Host::get_contract_id_from_asset. This function should only be -// called by the create_token_from_asset host function for this reason. +// returned by Host::get_asset_contract_id_hash. // // No admin will be set for the Native token, so any function that checks the admin // (clawback, disable_clawback, deauthorize, authorize, mint, set_admin) will always fail fn init_asset(asset_bytes: Bytes) -> Result<(), Error>; ``` -#### Deploying a token contract that only operates on Soroban -```rust -// From CAP-0046-02 - Instantiates a contract with the source referring to the built-in token. -// Returns the newly created contractID. -fn create_token_from_contract(salt: Object) -> Result; -``` - -Uniqueness does not apply to non-wrapper -token contracts, so this function uses the creating contract and a salt. +#### Balances +The Stellar Asset Contract handles balances for both `ScAddress::Account` and +`ScAddress::Contract`. For the `Account` scenario, it uses the classic trustline +for the account specified, so if the trustline doesn't exist, any function that +uses it will error. For the `Contract` scenario, the contract will create a +`ContractDataEntry` to hold the balance, along with an `authorized` and +`clawback` flag to mimic classic trustlines and allow for the same issuer +controls. #### Authorized flag -Balances will be authorized by default. +Contract balances will be authorized by default unless the issuer account has +`AUTH_REQUIRED_FLAG` set. + +#### Auth Revocable flag +Authorization cannot be revoked with `set_authorized` if the issuer does not +have `AUTH_REVOCABLE_FLAG` set. + +#### Clawback +Account balances can only be clawed back if the trustline has +`TRUSTLINE_CLAWBACK_ENABLED_FLAG` set. When a contract balance is created, it +will be clawback enabled if the issuer account has `AUTH_CLAWBACK_ENABLED_FLAG` +set. Note that the clawback enabled flag for contract balances cannot be +modified, which is a little different from trustlines where the issuer can +choose to disable it. ## Design Rationale @@ -314,91 +429,46 @@ as a dedicated fee-lane for payments. If payments have arbitrary pre / post hooks then any arbitrarily expensive program could get embedded into the payment lane, significantly reducing the feasibility of such a concept. -### Several Authorization Mechanisms - -This proposal supports several signature mechanisms to acknowledge the reality -that account-based multisig is a fundamental aspect of the Stellar ecosystem. -I anticipate many pure contract users will favor a single Ed25519 key because -fees will be lower, so we provide this option as well. Contracts cannot sign, so -they need a separate authorization mechanism too (achieved with -`get_invoking_contract`). - -### Message Allows Caller to Be Specified - -This is an extremely useful design point that makes it possible to couple -multiple signed messages. Consider the interface described in "Ecosystem -Support: Wrap-Then-Do Contract". This interface can receive multiple signed -messages. But an attacker could observe this on the network, then submit the -signed messages separately. By adding the caller to the signed message, it -allows the caller to perform additional consistency checks. For example, the -calling contract could receive a signature over a message containing the -coupled signed messages. - ### Admin Interface -Unlike the existing Stellar protocol, which uses simple flags for compliance -controls, this proposal delegates all decisions regarding administrative -functionality to an administrator. The administrator can be a contract, a simple -Ed25519 key, or an existing Stellar account. - -This design decision does not break compatibility with Stellar assets. If an -existing holder does not want to accept the new terms, they simply elect not to -wrap their asset. The ability to do this without breaking compatibility is one -of the big advantages of using wrappers. - -If people want an analog to the existing compliance semantics, we can provide a -reference administrator contract that implements them. - -### Classic Wrapper Interface - -The wrapper interface is only provided for classic assets. Smart assets do not -have a wrapper interface because those administered by contracts would lose -control over the wrapped assets. Smart asset issuers can always deploy their own -wrapper interface should they need it. +The admin is initally set to the issuer (except for the XLM contract, where no +admin is set) and can be updated to a contract or a different Stellar account. +The admin has controls similar to the issuer on classic as you can see in the +admin interface. ### No Total Supply -Total supply is confusing for classic assets because they can exist in wrapped -and unwrapped form. Total supply is also high contention when minting and -burning. Tokens that need to track total supply can do so by having the -administrator contract update it when calling `mint` and `clawback`. +Total supply is not available because it isn't supported in Stellar Classic, so +there's no way to reasonably track the supply between trustlines and contract +balances. ### Deployment Unlike earlier variants of this proposal (see CAP-0048 and CAP-0049), this -proposal does not implicitly deploy a wrapper for every classic asset. However, -anyone can deploy the wrapper for any asset. The main advantage to this approach -is that we no longer need to special case the identifiers for these wrappers. -We still desire that these wrappers are unique, so they will be constructed +proposal does not implicitly deploy a contract for every classic asset. However, +anyone can deploy the contract for any asset. The main advantage to this approach +is that we no longer need to special case the identifiers for these contract. +We still desire that these contracts are unique, so they will be constructed differently even though they have the same format. -Uniqueness is not a concern for smart tokens, so their identifiers are -constructed using the typical creator and salt approach. - -### Clawback +### No native contract for Soroban only assets -The ability to call the `clawback` function is set by the entity that calls -`init` for non-classic tokens. For classic tokens, `clawback` will be enabled -only if `AUTH_CLAWBACK_ENABLED_FLAG` is set on the issuer. If the issuer account -is missing, `clawback` will be disabled on initialization because anyone could -create the missing issuer account without any flags. Instead of checking for -`AUTH_CLAWBACK_ENABLED_FLAG`, we could just have `clawback` enabled by default, -and allow the issuer to clear it. I don't think this is a great idea because I -think we'll see most issuers not clear the flag even if they don't require -clawback, making users hesitant to use classic assets on Soroban. +We are only adding a contract to handle Classic stellar assets. For Soroban-only +assets, you'd implement a contract that follows the same interface, but will +need to deploy WASM. This will allow token contracts in the ecosystem to +materialize naturally. We still have the option to nativize popular contracts in +the future. -Note that `clawback` can be disabled using `disable_clawback`, but it can not be -enabled. This matches the clawback behavior on Stellar classic. +### The Token Interface accepts i128s, and Contract Balances are i128s -### Balances are u128s - -The options considered were u63 (contained in an ScVal), u64, u128, u256, and an -arbitrary precision big integer. Big integer is unnecessary since u256 is more -than enough to represent any reasonable balance. This is made more obvious when -looking at other chains and what they use (Solana uses u64, Near uses u128, and -ERC-20 uses u256). Note that a u63 is more efficient than u64 in Soroban because -the u63 can be contained in a 64 bit `ScVal`, while u64 will need be to an -`ScObject`, which only exists in the host and needs to be referenced by the guest. +The options considered were u63 (contained in an ScVal), u64, u128, i128, u256, +and an arbitrary precision big integer. Big integer is unnecessary since u256 is +more than enough to represent any reasonable balance. This is made more obvious +when looking at other chains and what they use (Solana uses u64, Near uses u128, +and ERC-20 uses u256). Note that a u63 is more efficient than u64 in Soroban +because the u63 can be contained in a 64 bit `ScVal`, while u64 will need be to +an `ScObject`, which only exists in the host and needs to be referenced by the +guest. Now the question is what should it be instead? u63 is the most efficient with u64 close behind, but not the most flexible if you use more than a couple @@ -411,84 +481,15 @@ example, the US Treasury has a cash balance of $636 billion. This barely works in the Stellar case, but isn’t far off from reaching the max. It works in the variable decimals case if you give up some decimals. There are other currency and asset examples that exceed these limits as well. Instead of forcing issuers -to compromise on decimals, the best option is to use u128 balances. +to compromise on decimals, the best option is to use u128 balances. We chose to +use i128 instead though to allow contracts to handle negative amounts if they +wanted to. -u128 gives users more flexibility than Stellar classic without compromising too +i128 gives users more flexibilty than Stellar classic without compromising too much in terms of performance. u256 gives us even more flexibility and easy -compatibility with ERC-20, but u128 has a max value that should work with almost +compatibility with ERC-20, but i128 has a max value that should work with almost every use case even with 18 decimal places -(340,282,366,920,938,463,463.374607431768211456). The performance implications -of using u128 instead of u63 or u64 should be measured. - -### No approve function -This proposal does not include an `approve` function like the one specified in EIP-20. This was done to mitigate the allowance vulnerability specified in this [google doc](https://docs.google.com/document/d/1YLPtQxZu1UAvO9cZ1O2RPXBbT0mooh4DYKjA_jp-RLM/edit). TLDR: If user A specified an allowance of 10 for B, then tries to reduce it to 5, B could spend the allowance before the reduction is accepted on the ledger, and then have an additional 5 to spend. Instead of reducing the allowance to 5, the user actually increased it to 15! - -Instead of `approve`, this proposal specifies two functions to deal with allowances - `increase_allowance` and `decrease_allowance`. - -### No explicit functions to burn -This proposal does not include a `burn` or `burnFrom` function that allows a -user to burn a balance that it holds. This functionality can be imitated by just -sending the balance to the zero ed25519 identifier. The alternative would be to -implement `burn` and `burnFrom` similar to what the [OpenZeppelinERC-20](https://docs.openzeppelin.com/contracts/2.x/api/token/erc20#ERC20Burnable) -implementation does. - -### Ecosystem Support: Wrap-Then-Do Contract - -One concern about the use of wrapped assets is that it will degrade the user -experience by requiring users of classic assets to submit additional -transactions: first wrap, then do. This can be avoided relatively easily by -implementing a generic wrap-then-do contract. A wrap-then-do contract might have -the following functions - -```rust -fn do_then_unwrap(sig: KeyedAccountAuthorization, token: u256, - id: AccountAuthorization, amount: u128, contract: u256, - symbol: SCSymbool, parameters: SCVec) { - // check that "sig" contains medium threshold signatures over - // Message::V0(MessageV0 { - // nonce: nonce_of(Identifier::Account(sig.publicKey)), - // thisContract: get_contract_id(), - // symbol: "do_then_unwrap", - // parameters: (token, id, amount, contract, symbol, symbol, - // parameters), - // caller: Caller::Transaction, - // }) - // otherwise trap - - let keyedID = KeyedAccountAuthorization { - publicKey: sig.publicKey, - authorization: id; - }; - call(contract, symbol, parameters); - call(token, "unwrap", (keyedID, amount)); -} - -// signatures in "id" should be over messages that specify this contract as the -// "caller" -// signatures in elements of "parameters" should be over messages that specify -// this contract as the "caller" -fn wrap_then_do(sig: KeyedAccountAuthorization, token: u256, - id: AccountAuthorization, amount: u128, contract: u256, - symbol: SCSymbool, parameters: SCVec) { - // check that "sig" contains medium threshold signatures over - // Message::V0(MessageV0 { - // nonce: nonce_of(Identifier::Account(sig.publicKey)), - // thisContract: get_contract_id(), - // symbol: "wrap_then_do", - // parameters: (token, id, amount, contract, symbol, symbol, - // parameters), - // caller: Caller::Transaction, - // }) - // otherwise trap - - let keyedID = KeyedAccountAuthorization { - publicKey: sig.publicKey, - authorization: id; - }; - call(token, "wrap", (keyedID, amount)); - call(contract, symbol, parameters); -} -``` +(170,141,183,460,469,231,731.687303715884105727). ### Possible Improvement: Contract Extensibility