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

Fungible Token standard #21

Merged
merged 8 commits into from
May 15, 2020
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
293 changes: 293 additions & 0 deletions docs/Architecture.html

Large diffs are not rendered by default.

223 changes: 223 additions & 0 deletions docs/ChainSpec/README.html

Large diffs are not rendered by default.

471 changes: 471 additions & 0 deletions docs/ChainSpec/Transactions.html

Large diffs are not rendered by default.

274 changes: 274 additions & 0 deletions docs/DataStructures/AccessKey.html

Large diffs are not rendered by default.

323 changes: 323 additions & 0 deletions docs/DataStructures/Account.html

Large diffs are not rendered by default.

232 changes: 232 additions & 0 deletions docs/DataStructures/README.html

Large diffs are not rendered by default.

222 changes: 222 additions & 0 deletions docs/DataStructures/Transaction.html

Large diffs are not rendered by default.

369 changes: 369 additions & 0 deletions docs/Economics/README.html

Large diffs are not rendered by default.

359 changes: 359 additions & 0 deletions docs/GenesisConfig/ExtCostsConfig.html

Large diffs are not rendered by default.

296 changes: 296 additions & 0 deletions docs/GenesisConfig/GenesisConfig.html

Large diffs are not rendered by default.

244 changes: 244 additions & 0 deletions docs/GenesisConfig/RuntimeConfig.html

Large diffs are not rendered by default.

239 changes: 239 additions & 0 deletions docs/GenesisConfig/RuntimeFeeConfig.html

Large diffs are not rendered by default.

232 changes: 232 additions & 0 deletions docs/GenesisConfig/RuntimeFeeConfig/AccessKeyCreationConfig.html

Large diffs are not rendered by default.

253 changes: 253 additions & 0 deletions docs/GenesisConfig/RuntimeFeeConfig/ActionCreationConfig.html

Large diffs are not rendered by default.

229 changes: 229 additions & 0 deletions docs/GenesisConfig/RuntimeFeeConfig/DataReceiptCreationConfig.html

Large diffs are not rendered by default.

229 changes: 229 additions & 0 deletions docs/GenesisConfig/RuntimeFeeConfig/Fee.html

Large diffs are not rendered by default.

226 changes: 226 additions & 0 deletions docs/GenesisConfig/RuntimeFeeConfig/Fraction.html

Large diffs are not rendered by default.

238 changes: 238 additions & 0 deletions docs/GenesisConfig/RuntimeFeeConfig/StorageUsageConfig.html

Large diffs are not rendered by default.

260 changes: 260 additions & 0 deletions docs/GenesisConfig/VMConfig.html

Large diffs are not rendered by default.

226 changes: 226 additions & 0 deletions docs/README.html

Large diffs are not rendered by default.

383 changes: 383 additions & 0 deletions docs/RuntimeSpec/Actions.html

Large diffs are not rendered by default.

273 changes: 273 additions & 0 deletions docs/RuntimeSpec/Components/BindingsSpec/BindingsSpec.html

Large diffs are not rendered by default.

376 changes: 376 additions & 0 deletions docs/RuntimeSpec/Components/BindingsSpec/ContextAPI.html

Large diffs are not rendered by default.

266 changes: 266 additions & 0 deletions docs/RuntimeSpec/Components/BindingsSpec/EconomicsAPI.html

Large diffs are not rendered by default.

269 changes: 269 additions & 0 deletions docs/RuntimeSpec/Components/BindingsSpec/MathAPI.html

Large diffs are not rendered by default.

309 changes: 309 additions & 0 deletions docs/RuntimeSpec/Components/BindingsSpec/MiscellaneousAPI.html

Large diffs are not rendered by default.

576 changes: 576 additions & 0 deletions docs/RuntimeSpec/Components/BindingsSpec/PromisesAPI.html

Large diffs are not rendered by default.

283 changes: 283 additions & 0 deletions docs/RuntimeSpec/Components/BindingsSpec/RegistersAPI.html

Large diffs are not rendered by default.

395 changes: 395 additions & 0 deletions docs/RuntimeSpec/Components/BindingsSpec/TrieAPI.html

Large diffs are not rendered by default.

224 changes: 224 additions & 0 deletions docs/RuntimeSpec/Components/Components.html

Large diffs are not rendered by default.

351 changes: 351 additions & 0 deletions docs/RuntimeSpec/Components/RuntimeCrate.html

Large diffs are not rendered by default.

282 changes: 282 additions & 0 deletions docs/RuntimeSpec/FunctionCall.html

Large diffs are not rendered by default.

222 changes: 222 additions & 0 deletions docs/RuntimeSpec/README.html

Large diffs are not rendered by default.

425 changes: 425 additions & 0 deletions docs/RuntimeSpec/Receipts.html

Large diffs are not rendered by default.

328 changes: 328 additions & 0 deletions docs/RuntimeSpec/Scenarios/CrossContractCall.html

Large diffs are not rendered by default.

316 changes: 316 additions & 0 deletions docs/RuntimeSpec/Scenarios/FinancialTransaction.html

Large diffs are not rendered by default.

223 changes: 223 additions & 0 deletions docs/RuntimeSpec/Scenarios/Scenarios.html

Large diffs are not rendered by default.

255 changes: 255 additions & 0 deletions docs/RuntimeSpec/Transactions.html

Large diffs are not rendered by default.

225 changes: 225 additions & 0 deletions docs/Standards/README.html

Large diffs are not rendered by default.

428 changes: 428 additions & 0 deletions docs/Standards/Tokens/FungibleToken.html

Large diffs are not rendered by default.

225 changes: 225 additions & 0 deletions docs/Terminology.html

Large diffs are not rendered by default.

226 changes: 226 additions & 0 deletions docs/index.html

Large diffs are not rendered by default.

3,472 changes: 3,472 additions & 0 deletions docs/print.html

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docs/searchindex.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docs/searchindex.json

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion specs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@ For the overview of the NEAR Protocol, read the following documents in numerical
3. [Architecture](Architecture.md)
4. [Chain specification](ChainSpec/README.md)
5. [Runtime specification](RuntimeSpec/README.md)
6. [Economics](Economics/README.md)
6. [Economics](Economics/README.md)

## Standards

Standards such as Fungible Token Standard can be found in [Standards](Standards/README.md) page.
2 changes: 2 additions & 0 deletions specs/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,5 @@
- [ExtCostsConfig](GenesisConfig/ExtCostsConfig.md)
- [StateRecord](GenesisConfig/StateRecord.md)
- [Economics](Economics/README.md)
- [Standards](Standards/README.md)
- [Fungible Token](Standards/Tokens/FungibleToken.md)
3 changes: 3 additions & 0 deletions specs/Standards/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## Standards

- [Fungible Token Standard](Tokens/FungibleToken.md)
217 changes: 217 additions & 0 deletions specs/Standards/Tokens/FungibleToken.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
# Fungible Token

## Summary
[summary]: #summary

A standard interface for fungible tokens allowing for ownership, escrow and transfer, specifically targeting third-party marketplace integration.

## Motivation
[motivation]: #motivation

NEAR Protocol uses an asynchronous sharded Runtime. This means the following:
- Storage for different contracts and accounts can be located on the different shards.
- Two contracts can be executed at the same time in different shards.

While this increases the transaction throughput linearly with the number of shards, it also creates some challenges for cross-contract development.
For example, if one contract wants to query some information from the state of another contract (e.g. current balance), by the time the first contract receive the balance the real balance can change.
It means in the async system, a contract can't rely on the state of other contract and assume it's not going to change.

Instead the contract can rely on temporary partial lock of the state with a callback to act or unlock, but it requires careful engineering to avoid dead locks.
In this standard we're trying to avoid enforcing locks, since most actions can still be completed without locks by transferring ownership to an escrow account.

Prior art:
- [ERC-20 standard](https://eips.ethereum.org/EIPS/eip-20)
- NEP#4 NEAR NFT standard: [nearprotocol/neps#4](https://github.com/nearprotocol/neps/pull/4)
- For latest lock proposals see [Safes (#26)](https://github.com/nearprotocol/neps/pull/26)

## Guide-level explanation
[guide-level-explanation]: #guide-level-explanation

We should be able to do the following:
- Initialize contract once. The given total supply will be owned by the given account ID.
- Get the total supply.
- Transfer tokens to a new user.
- Set a given allowance for an escrow account ID.
- Escrow will be able to transfer up this allowance from your account.
- Get current balance for a given account ID.
- Transfer tokens from one user to another.
- Get the current allowance for an escrow account on behalf of the balance owner. This should only be used in the UI, since a contract shouldn't rely on this temporary information.

There are a few concepts in the scenarios above:
- **Total supply**. It's the total number of tokens in circulation.
- **Balance owner**. An account ID that owns some amount of tokens.
- **Balance**. Some amount of tokens.
- **Transfer**. Action that moves some amount from one account to another account.
- **Escrow**. A different account from the balance owner who has permission to use some amount of tokens.
- **Allowance**. The amount of tokens an escrow account can use on behalf of the account owner.

Note, that the precision is not part of the default standard, since it's not required to perform actions. The minimum
value is always 1 token.

### Simple transfer

Alice wants to send 5 wBTC tokens to Bob.

**Assumptions**

- The wBTC token contract is `wbtc`.
- Alice's account is `alice`.
- Bob's account is `bob`.
- The precision on wBTC contract is `10^8`.
- The 5 tokens is `5 * 10^8` or as a number is `500000000`.

**High-level explanation**

Alice needs to issue one transaction to wBTC contract to transfer 5 tokens (multiplied by precision) to Bob.

**Technical calls**

1. `alice` calls `wbtc::transfer({"new_owner_id": "bob", "amount": "500000000"})`.

### Token deposit to a contract

Alice wants to deposit 1000 DAI tokens to a compound interest contract to earn extra tokens.

**Assumptions**

- The DAI token contract is `dai`.
- Alice's account is `alice`.
- The compound interest contract is `compound`.
- The precision on DAI contract is `10^18`.
- The 1000 tokens is `1000 * 10^18` or as a number is `1000000000000000000000`.
- The compound contract can work with multiple token types.

**High-level explanation**

Alice needs to issue 2 transactions. The first one to `dai` to set an allowance for `compound` to be able to withdraw tokens from `alice`.
The second transaction is to the `compound` to start the deposit process. Compound will check that the DAI tokens are supported and will try to withdraw the desired amount of DAI from `alice`.
- If transfer succeeded, `compound` can increase local ownership for `alice` to 1000 DAI
- If transfer fails, `compound` doesn't need to do anything in current example, but maybe can notify `alice` of unsuccessful transfer.

**Technical calls**

1. `alice` calls `dai::set_allowance({"escrow_account_id": "compound", "allowance": "1000000000000000000000"})`.
1. `alice` calls `compound::deposit({"token_contract": "dai", "amount": "1000000000000000000000"})`. During the `deposit` call, `compound` does the following:
1. makes async call `dai::transfer_from({"owner_id": "alice", "new_owner_id": "compound", "amount": "1000000000000000000000"})`.
1. attaches a callback `compound::on_transfer({"owner_id": "alice", "token_contract": "dai", "amount": "1000000000000000000000"})`.

### Multi-token swap on DEX

Charlie wants to exchange his wLTC to wBTC on decentralized exchange contract. Alex wants to buy wLTC and has 80 wBTC.

**Assumptions**

- The wLTC token contract is `wltc`.
- The wBTC token contract is `wbtc`.
- The DEX contract is `dex`.
- Charlie's account is `charlie`.
- Alex's account is `alex`.
- The precision on both tokens contract is `10^8`.
- The amount of 9001 wLTC tokens is Alex wants is `9001 * 10^8` or as a number is `900100000000`.
- The 80 wBTC tokens is `80 * 10^8` or as a number is `8000000000`.
- Charlie has 1000000 wLTC tokens which is `1000000 * 10^8` or as a number is `100000000000000`
- Dex contract already has an open order to sell 80 wBTC tokens by `alex` towards 9001 wLTC.
- Without Safes implementation, DEX has to act as an escrow and hold funds of both users before it can do an exchange.

**High-level explanation**

Let's first setup open order by Alex on DEX. It's similar to `Token deposit to a contract` example above.
- Alex sets an allowance on wBTC to DEX
- Alex calls deposit on Dex for wBTC.
- Alex calls DEX to make an new sell order.

Then Charlie comes and decides to fulfill the order by selling his wLTC to Alex on DEX.
Charlie calls the DEX
- Charlie sets the allowance on wLTC to DEX
- Alex calls deposit on Dex for wLTC.
- Then calls DEX to take the order from Alex.

When called, DEX makes 2 async transfers calls to exchange corresponding tokens.
- DEX calls wLTC to transfer tokens DEX to Alex.
- DEX calls wBTC to transfer tokens DEX to Charlie.

**Technical calls**

1. `alex` calls `wbtc::set_allowance({"escrow_account_id": "dex", "allowance": "8000000000"})`.
1. `alex` calls `dex::deposit({"token": "wbtc", "amount": "8000000000"})`.
1. `dex` calls `wbtc::transfer_from({"owner_id": "alex", "new_owner_id": "dex", "amount": "8000000000"})`
1. `alex` calls `dex::trade({"have": "wbtc", "have_amount": "8000000000", "want": "wltc", "want_amount": "900100000000"})`.
1. `charlie` calls `wltc::set_allowance({"escrow_account_id": "dex", "allowance": "100000000000000"})`.
1. `charlie` calls `dex::deposit({"token": "wltc", "amount": "100000000000000"})`.
1. `dex` calls `wltc::transfer_from({"owner_id": "charlie", "new_owner_id": "dex", "amount": "100000000000000"})`
1. `charlie` calls `dex::trade({"have": "wltc", "have_amount": "900100000000", "want": "wbtc", "want_amount": "8000000000"})`.
- `dex` calls `wbtc::transfer({"new_owner_id": "charlie", "amount": "8000000000"})`
- `dex` calls `wltc::transfer({"new_owner_id": "alex", "amount": "900100000000"})`


## Reference-level explanation
[reference-level-explanation]: #reference-level-explanation

The full implementation in Rust can be found there: https://github.com/nearprotocol/near-sdk-rs/blob/master/examples/fungible-token/src/lib.rs

**NOTES:**
- All amounts, balances and allowance are limited by U128 (max value `2**128 - 1`).
- Token standard uses JSON for serialization of arguments and results.
- Amounts in arguments and results have are serialized as Base-10 strings, e.g. `"100"`. This is done to avoid
JSON limitation of max integer value of `2**53`.

Interface:

```rust
/******************/
/* CHANGE METHODS */
/******************/

/// Sets the `allowance` for `escrow_account_id` on the account of the caller of this contract
/// (`predecessor_id`) who is the balance owner.
pub fn set_allowance(&mut self, escrow_account_id: AccountId, allowance: U128);

/// Transfers the `amount` of tokens from `owner_id` to the `new_owner_id`.
/// Requirements:
/// * `amount` should be a positive integer.
/// * `owner_id` should have balance on the account greater or equal than the transfer `amount`.
/// * If this function is called by an escrow account (`owner_id != predecessor_account_id`),
/// then the allowance of the caller of the function (`predecessor_account_id`) on
/// the account of `owner_id` should be greater or equal than the transfer `amount`.
pub fn transfer_from(&mut self, owner_id: AccountId, new_owner_id: AccountId, amount: U128);


/// Transfer `amount` of tokens from the caller of the contract (`predecessor_id`) to
/// `new_owner_id`.
/// Act the same was as `transfer_from` with `owner_id` equal to the caller of the contract
/// (`predecessor_id`).
pub fn transfer(&mut self, new_owner_id: AccountId, amount: U128);

/****************/
/* VIEW METHODS */
/****************/

/// Returns total supply of tokens.
pub fn get_total_supply(&self) -> U128;

/// Returns balance of the `owner_id` account.
pub fn get_balance(&self, owner_id: AccountId) -> U128;

/// Returns current allowance of `escrow_account_id` for the account of `owner_id`.
///
/// NOTE: Other contracts should not rely on this information, because by the moment a contract
/// receives this information, the allowance may already be changed by the owner.
/// So this method should only be used on the front-end to see the current allowance.
pub fn get_allowance(&self, owner_id: AccountId, escrow_account_id: AccountId) -> U128;
```

## Drawbacks
[drawbacks]: #drawbacks

- Current interface doesn't have minting, precision (decimals), naming. But it should be done as extensions, e.g. a Precision extension.
- It's not possible to exchange tokens without transferring them to escrow first.
- It's not possible to transfer tokens to a contract with a single transaction without setting the allowance first.
It should be possible if we introduce `transfer_with` function that transfers tokens and calls escrow contract. It needs to handle result of the execution and contracts have to be aware of this API.


## Future possibilities
[future-possibilities]: #future-possibilities

- Support for multiple token types
- Minting and burning
- Precision, naming and short token name.