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

SEP-41: Add a token contract interface proposal #1402

Merged
merged 18 commits into from
Oct 12, 2023
Merged
1 change: 1 addition & 0 deletions ecosystem/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
| [SEP-0038](sep-0038.md) | Anchor RFQ API | Jake Urban and Leigh McCulloch | Standard | Draft |
| [SEP-0039](sep-0039.md) | Interoperability Recommendations for NFTs | SDF, Litemint.io | Informational | Active |
| [SEP-0040](sep-0040.md) | Oracle Consumer Interface | Alex Mootz, OrbitLens, Markus Paulson-Luna | Standard | Draft |
| [SEP-0041](sep-0041.md) | Soroban Token Interface | Jonathan Jove, Siddharth Suresh | Standard | Draft |



Expand Down
239 changes: 239 additions & 0 deletions ecosystem/sep-0041.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
## Preamble

```
SEP: 0041
Title: Soroban Token Interface
Authors: Jonathan Jove <@jonjove>, Siddharth Suresh <@sisuresh>
Track: Standard
Status: Draft
Created: 2023-09-22
Version 0.1.0
Discussion: https://discord.com/channels/897514728459468821/1159937045322547250
```

## Simple Summary

This proposal defines a standard contract interface for tokens. The interface is
a subset of the Stellar Asset contract, and compatible with its descriptive and
token interfaces defined in [CAP-46-6].

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

[CAP-46-6] defines a Stellar Asset contract that implements a combination of
that standard functionality, as well as functions to support behaviors that are
specialized to Stellar assets.

Tokens could implement the interface that the Stellar Asset contract defines,
but it contains functions that support behaviors that are specialized to Stellar
assets.

An interface is needed that is less opinionated than the interface of the Stellar
Asset contract. Tokens that implement this interface would support at least the
standard functionality of tokens, without needing to support the specialized
behaviors of Stellar Assets.

An interface is needed that is a subset of the Stellar Asset contract, such that
the Stellar Asset contract would implement the interface. Tokens that implement
the interface and the Stellar Asset contract would be usable with contracts that
support the standard interface, and would be largely indistinguishable.

[CAP-46-6]: ../core/CAP-0046-06.md

## Specification

### Interface

```rust
pub trait TokenInterface {
/// 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(env: Env, from: Address, spender: Address) -> i128;

/// 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 available to `spender`.
/// - `live_until_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 live_until_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, live_until_ledger: u32]`
///
/// Emits an event with:
/// - topics - `["approve", from: Address, spender: Address]`
/// - data - `[amount: i128, live_until_ledger: u32]`
fn approve(env: Env, from: Address, spender: Address, amount: i128, live_until_ledger: u32);

/// 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`.
///
/// If the token implementation makes a portion of the balance unavailable
/// for general use, this should return the portion that is available for
/// withdrawal.
///
/// Should always return a value that is less than or equal to the amount
/// returned by `balance`.
///
/// # Arguments
///
/// - `id` - The address for which a spendable balance is being queried.
fn spendable_balance(env: Env, id: Address) -> i128;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this interface need a spendable_balance (I think this was discussed recently but don't remember what came out of that).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The discussion and resolution is here:

The resolution was we could go both ways so leave it. But, I've lost count how many times this has come up, and every time the opinion is "it doesn't belong."

I think we could remove it with little impact.

cc @tomerweller


/// 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);

/// Returns the number of decimals used to represent amounts of this token.
fn decimals(env: Env) -> u32;

/// Returns the name for this token.
fn name(env: Env) -> String;

/// Returns the symbol for this token.
fn symbol(env: Env) -> String;
}
```

### Events

#### Approve Event

The `approve` event is emitted when the allowance is set.

The event has topics:
- `Symbol` with value `"approve"`
- `Address` the address holding the balance of tokens to be drawn
from.
- `Address` the address spending the tokens held by `from`.

The event has data:
- `i128` the amount allowed to be spent.
- `u32` the expiration ledger.

#### Transfer Event

The `transfer` event is emitted when an amount is transferred from one address
to another.

The event has topics:
- `Symbol` with value `"transfer"`
- `Address` the address holding the balance of tokens that was drawn from.
- `Address` the address that received the tokens.

The event has data:
- `i128` the amount transferred.

#### Burn Event

The `burn` event is emitted when an amount is burned from one address.

The event has topics:
- `Symbol` with value `"burn"`
- `Address` the address holding the balance of tokens that was burned.

The event has data:
- `i128` the amount burned.


## Changelog

- `v0.1.0` - Initial draft based on [CAP-46-6].

## Implementations

- The [Rust soroban-sdk] contains a copy of the interface verbatim, and a
generated client for use in contracts.
- The Soroban Env contains a native implementation of the interface that is a
presentation layer on top of Stellar Assets.

[Rust soroban-sdk]: https://github.com/stellar/rs-soroban-sdk
Loading