Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CAP-0054 Smart Contract Standardized Asset: Initial draft #1224

Merged
merged 2 commits into from
Jun 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
| [CAP-0051](cap-0051.md) | Smart Contract Host Functons | Jay Geng | Draft |
| [CAP-0052](cap-0052.md) | Smart Contract Interactions (Minimal) | Jonathan Jove | Draft |
| [CAP-0053](cap-0053.md) | Smart Contract Data | Graydon Hoare | Draft |
| [CAP-0054](cap-0054.md) | Smart Contract Standardized Asset | Jonathan Jove | Draft |

### Rejected Proposals
| Number | Title | Author | Status |
Expand Down
386 changes: 386 additions & 0 deletions core/cap-0054.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,386 @@
```
CAP: 0054
Title: Smart Contract Standardized Asset
Working Group:
Owner: Jonathan Jove <@jonjove>
Authors:
Consulted: Nicolas Barry <@monsieurnicolas>, Leigh McCulloch <@leighmcculloch>, Tomer Weller <@tomerweller>
Status: Draft
Created: 2022-05-31
Discussion: TBD
Protocol version: TBD
```

## Simple Summary

Allow contracts to interoperate with Stellar assets.

## Motivation

A fungible asset is a fundamental concept on blockchains. Fungible assets can
conceptually be divided into two pieces: standard functionality enabling push
and pull transfers, and varying functionality around asset administration. Most
blockchain ecosystems have very little innovation in the space of fungible
assets, with developers often relying on open source implementations such as
OpenZeppelin.

Rather than rely on an open source implementation, developers should have access
to a native contract which fulfils typical needs. This does not prevent
developers from implementing their own fungible asset if the contract does not
meet their needs. But the efficiency gained from a native implementation should
reduce fees sufficiently to encourage most developers to choose the native
implementation when it is suitable.

The native implementation should serve as a compatibility layer for classic
assets. This ensures that any contract which can interoperate with new smart
assets can also interoperate with classic assets, and vice versa.

The advantage of a native implementation is that the semantics are known to the
protocol. This allows additional enhancements like a dedicated fee lane for
simple asset transfers, or the potential to use the asset in a high-throughput
parallel exchange like SPEEDEX.

### Goals Alignment

This CAP is aligned with the following Stellar Network Goals:

- The Stellar Network should make it easy for developers of Stellar projects to
create highly usable products
- The Stellar Network should enable cross-border payments, i.e. payments via
exchange of assets, throughout the globe, enabling users to make payments
between assets in a manner that is fast, cheap, and highly usable.

## Specification

### Semantics: Deployment

TODO

### Semantics: Data Format

```rust
/******************************************************************************\
*
* Data Structures
*
\******************************************************************************/
// SCO_VEC[
// SCO_BINARY, // publicKey, 32 bytes
// SCO_BINARY, // signature, 64 bytes
// ]
struct KeyedEd25519Signature {
u256 publicKey;
u512 signature;
}

// SCO_VEC[
// SCO_BINARY, // nonce, 32 bytes
// SCO_BINARY, // signature, 64 bytes
// ]
struct Ed25519Authorization {
u256 nonce;
u512 signature;
}

// SCO_VEC[
// SCO_BINARY, // publicKey, 32 bytes
// Ed25519Authorization, // authorization
// ]
struct KeyedEd25519Authorization {
u256 publicKey;
Ed25519Authorization authorization;
}

// SCO_VEC[
// SCO_BINARY, // publicKey, 32 bytes
// SCO_VEC[ // signatures
// KeyedEd25519Signature,
// ...,
// ]
// ]
struct AccountAuthorization {
u256 nonce;
Vec<KeyedEd25519Signature> signatures;
}

// SCO_VEC[
// SCO_BINARY, // publicKey, 32 bytes
// AccountAuthorization, // authorization
// ]
struct KeyedAccountAuthorization {
u256 publicKey;
AccountAuthorization authorization;
}

// SCO_VEC[
// 0, // Authorization::Contract, 4 bytes
// ] | SCO_VEC[
// 1, // Authorization::Ed25519, 4 bytes
// Ed25519Authorization,
// ] | SCO_VEC[
// 2, // Authorization::Account, 4 bytes
// AccountAuthorization,
// ]
enum Authorization {
Contract,
Ed25519(Ed25519Authorization),
Account(AccountAuthorization),
}

// SCO_VEC[
// 0, // KeyedAuthorization::Contract, 4 bytes
// ] | SCO_VEC[
// 1, // KeyedAuthorization::Ed25519, 4 bytes
// KeyedEd25519Authorization,
// ] | SCO_VEC[
// 2, // KeyedAuthorization::Account, 4 bytes
// KeyedAccountAuthorization,
// ]
enum KeyedAuthorization {
Contract,
Ed25519(KeyedEd25519Authorization),
Account(KeyedAccountAuthorization),
}

// SCO_VEC[
// SCV_U32, // {0, 1, 2}
// SCO_BINARY, // 32 bytes
// ]
enum Identifier {
Contract(u256),
Ed25519(u256),
Account(u256),
}
```

### Semantics: Signatures

Signatures for this contract are over the following `Message` type

```rust
// SCO_VEC[
// 0, // Caller::Transaction, 4 bytes
// ] | SCO_VEC[
// 1, // Caller::Contract, 4 bytes
// SCO_BINARY, // 32 bytes
// ]
enum Caller {
Transaction,
Contract(u256),
}

// SCO_VEC[
// SCO_BINARY, // nonce, 32 bytes
// SCO_BINARY, // thisContract, 32 bytes
// SCV_SYMBOL, // symbol
// SCO_VEC[ // parameters
// SCVal,
// ...,
// ],
// Caller, // caller
// ]
struct MessageV0 {
u256 nonce;
u256 thisContract;
SCSymbol symbol;
Vec<SCVal> parameters;
Caller caller;
}

// SCO_VEC[
// 0, // Message::V0, 4 bytes
// MessageV0,
// ]
enum Message {
V0(MessageV0),
}
```

### Semantics: Token Interface

```rust
/******************************************************************************\
*
* Token Interface
*
\******************************************************************************/
// Get the allowance for "spender" to transfer from "from"
fn allowance(spender: Identifier, from: Identifier) -> BigInt;

// Set the allowance to "amount" for "spender" to transfer from "from"
fn approve(from: KeyedAuthorization, spender: Identifier,
amount: BigInt) -> bool;

// Get the balance of "id"
fn balance_of(id: Identifier) -> BigInt;

// Transfer "amount" from "from" to "to"
fn transfer(from: KeyedAuthorization, to: Identifier, amount: BigInt) -> bool;

// Transfer "amount" from "from" to "to", consuming the allowance of "spender"
fn transfer_from(spender: KeyedAuthorization: from: Identifier, to: Identifier,
amount: BigInt) -> bool;
```

### Semantics: Admin Interface

```rust
/******************************************************************************\
*
* Admin Interface
*
\******************************************************************************/
// If "admin" is the administrator, burn "amount" from "from"
fn burn(admin: Authorization, from: Identifier, amount: BigInt) -> bool;

// If "admin" is the administrator, freeze "id"
fn freeze(admin: Authorization, id: Identifier) -> bool;

// If "admin" is the administrator, mint "amount" to "to"
fn mint(admin: Authorization, to: Identifier, amount: BigInt) -> bool;

// If "admin" is the administrator, set the administrator to "id"
fn set_administrator(admin: Authorization, id: Identifier) -> bool;

// If "admin" is the administrator, unfreeze "id"
fn unfreeze(admin: Authorization, id: Identifier) -> bool;
```

### Semantics: Classic Wrapper Interface

```rust
/******************************************************************************\
*
* Wrapper Interface
*
\******************************************************************************/
// Move "amount" from "id" on classic to "id" on smart
fn move_to_smart(id: KeyedAccountAuthorization, amount: BigInt) -> bool;

// Move "amount" from "id" on smart to "id" on classic
fn move_to_classic(id: KeyedAccountAuthorization, amount: BigInt) -> bool;
```

## Design Rationale

### Asset Equivalence

From the perspective of a contract, smart assets and classic assets have exactly
identical semantics. This makes contract design and implementation easier by
reducing the number of edge cases to consider and tests to write.

### No Pre / Post Hooks

The main goal of this proposal is to create a standard asset with predictable
behavior. This preserves the ability to deliver certain future enhancements such
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.

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.

### 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 `burn`.

### 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 wrap_then_do(sig: KeyedAuthorization, token: u256, id:
KeyedAccountAuthorization, amount: BigInt, contract: u256,
symbol: SCSymbol, parameters: SCVec) {
// check sig
call(token, "wrap", (id, amount));
call(contract, symbol, parameters);
}

fn do_then_unwrap(sig: KeyedAuthorization, contract: u256, symbol: SCSymbol,
parameters: SCVec, token: u256, id: KeyedAccountAuthorization,
amount: BigInt) {
// check sig
call(contract, symbol, parameters);
call(token, "unwrap", (id, amount));
}
```

### Possible Improvement: Contract Extensibility

One disadvantage of this design is the fact that token functionality must be
separated into different contracts. For example, a liquidity pool share token
will typically have `mint` and `burn` functions which can be called by any user.
This is not possible because there is no way to extend a contract, so the only
functions available will be those specified above. Instead, the additional
functions will need to be provided in a separate contract.

## Protocol Upgrade Transition

### Backwards Incompatibilities

This proposal is completely backwards compatible. It describes a new interface
to existing behavior that is accessible from smart contracts, but it does not
modify the existing behavior.

### Resource Utilization

This proposal will lead to more ledger entries for tracking token state,
increasing the total ledger size.

## Security Concerns

This proposal does not introduce any security concerns.

## Test Cases

None yet.

## Implementation

None yet.