-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #18 from zie1ony/token
Token standard
- Loading branch information
Showing
1 changed file
with
272 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,272 @@ | ||
# Casper Payments: Casper Token Standard | ||
|
||
## Summary | ||
A standard interface for the CSPR and the custom made tokens. | ||
|
||
## Motivation | ||
Having a token standard enables other projects to generalized on top it and makes | ||
integration easy, especially for wallets, DAOs and DeFi products like decentralized | ||
exchanges and liquidity pools. Defining a token standard before releasing | ||
the mainnet gives use the unique opportunity to implement this standard for our | ||
native platform token CSPR. It allows developers to reduce amount of code, | ||
because it is no longer required to handle 2 cases: one for native | ||
token and the other for a tokens implementing current token standard. | ||
|
||
In the design we decided to get away from the purse based model and use | ||
the account based model. As a main inspiration we point at ERC-20 standard | ||
known from Ethereum. It is the most widely adopted standard in the blockchain | ||
space, successfully ported outside of Ethereum. Thanks to the unique ability | ||
of Casper to execute code in the account's context, the main drawback of ERC-20, | ||
which is the "approve and call" pattern is no longer a problem. | ||
|
||
## Guide-level explanation | ||
### Interface | ||
The token contract should implement following endpoints. | ||
|
||
#### name | ||
Returns the token name. | ||
```rust | ||
fn name() -> String | ||
``` | ||
|
||
#### symbol | ||
Returns the token symbol. | ||
```rust | ||
fn symbol() -> String | ||
``` | ||
|
||
#### decimals | ||
Returns the number of token decimals. | ||
```rust | ||
fn decimals() -> u8 | ||
``` | ||
|
||
#### balance_of | ||
Returns the amount of tokens given address holds. | ||
```rust | ||
fn balanceOf(address: Address) -> U512 | ||
``` | ||
|
||
#### batch_balance_of | ||
Returns the amounts of tokens given a list of addresses. | ||
```rust | ||
fn batch_balance_of(addresses: Vec<Address>) -> Vec<U512> | ||
``` | ||
|
||
#### transfer | ||
Transfer tokens from the direct function caller to the `recipient`. | ||
```rust | ||
fn transfer(recipient: Address, amount: U512) | ||
``` | ||
|
||
#### batch_transfer | ||
Transfer tokens to multiple recipients. | ||
```rust | ||
fn batch_transfer(recipient_and_amount_list: Vec<(Address, U512)>) | ||
``` | ||
|
||
#### approve | ||
Allow other address to transfer caller's tokens. | ||
```rust | ||
fn approve(spender: Address, amount: U512) | ||
``` | ||
|
||
#### batch_approve | ||
Allow other addresses to transfer caller's tokens. | ||
```rust | ||
fn batch_approve(spender_and_amount_list: Vec<(Address, U512)>) | ||
``` | ||
|
||
#### allowance | ||
Returns the amount allowed to spend. | ||
```rust | ||
fn allowance(owner: Address, spender: Address) -> U512 | ||
``` | ||
|
||
#### batch_allowance | ||
Returns the amounts allowed to spend for given addresses. | ||
```rust | ||
fn allowance(owner_spender_list: Vec<(Address, Address)>) -> Vec<U512> | ||
``` | ||
|
||
#### transfer_from | ||
Transfer tokens from `onwer` address to the `recipient` address if required | ||
amount was approved before to be spend by the direct caller. | ||
The operation should decrement approved amount. | ||
```rust | ||
fn transfer_from(owner: Address, recipient: Address, amount: U512) | ||
``` | ||
|
||
#### batch_transfer_from | ||
Transfer tokens from `onwer` address to the multiple `recipients` addresses if required | ||
amount was approved before to be spend by the direct caller. | ||
The operation should decrement approved amount. | ||
```rust | ||
fn batch_transfer_from(owner: Address, recipient_amount_list: Vec<(Address, U512)>) | ||
``` | ||
|
||
### Compare to ERC-20 | ||
While very similar to ERC-20, this standard is a bit different: | ||
1. Methods: `name`, `symbol` and `decimals` are required. | ||
2. Names of arguments are part of the standard. | ||
3. Added batch versions of methods. | ||
|
||
## Reference-level explanation | ||
### Custom tokens | ||
We have successfully tested the implementation of ERC-20-based token. | ||
Code is available at https://github.com/CasperLabs/erc20. | ||
|
||
### Casper Token | ||
Currently Casper Token implements purse-based model. To support presented | ||
standard reimplementation of Casper Token (Mint) is required. In addition | ||
Casper Token should be deployed at a know Address. | ||
|
||
It should be possible to implement host-side interface, that manipulates | ||
the memory directly, so CSPR transfers are as fast as possible. | ||
|
||
## Rationale | ||
In this section we compare purse-based model and account-based model. | ||
|
||
Purse-based model allows accounts and contracts on creation of object | ||
called `purses`. Each purse can have it's own balance. Each purse have | ||
unique token access, that allows for tokens spending. This token access | ||
has to passed to contracts to send tokens. | ||
|
||
### Scenario | ||
Let's consider the most widely used scenario of interaction between two | ||
contracts: sending tokens from contract to contract. In this example we | ||
assume that: | ||
1. `Oracle` contract is already deployed and can provide a dollar price for tokens. | ||
2. `TokenEx` contract is deployed. | ||
3. `Vault` contract is deployed. | ||
4. `Source` contract is deployed. | ||
5. Goal is to transfer 10 dollars worth of Ex tokens into the `Vault` from the `Source`. | ||
6. Scenario starts by calling `action` on `Source`. | ||
|
||
Below code is pseudo-code. | ||
|
||
### Purse-based Model. | ||
In this example `TokenEx` implements purse-based model. | ||
```rust | ||
[#casper_contract] | ||
mod Source { | ||
|
||
[#casper_method] | ||
fn action(max_amount_of_tokens_allowed: U512) { | ||
// Read purse from the local contract's memory. | ||
let main_purse: URef = runtime::get_key("contracts_main_purse_for_ex_tokens"); | ||
|
||
// Call the Token contract to transfer tokens into the new purse. | ||
// That's important, so the `Vault` contract is not exposed to the whole balance. | ||
let intermediate_purse: URef = | ||
runtime::call_contract("token_ex_address", "transfer_to_new_purse", runtime_args!{ | ||
from => main_purse, | ||
amount => max_amount_of_tokens_allowed | ||
}); | ||
|
||
// Deposit tokens. | ||
runtime::call_contract("vault_address", "deposit_10_dollars", runtime_args!{ | ||
ex_purse => intermediate_purse | ||
}); | ||
|
||
// Transfer back the remaining from intermediate_purse to have all | ||
// the Ex tokens on one purse. | ||
runtime::call_contract("token_ex_address", "transfer_all", runtime_args!{ | ||
from => intermediate_purse, | ||
to => main_purse | ||
}); | ||
} | ||
} | ||
|
||
[#casper_contract] | ||
mod Vault { | ||
|
||
[#casper_method] | ||
fn deposit_10_dollars(ex_purse: URef) { | ||
let main_purse: URef = runtime::get_key("contracts_main_purse_for_ex_tokens"); | ||
let price = runtime::call_contract("oracle_address", "ex_price"); | ||
let expected_amount = 10.0 / price; | ||
runtime::call_contact("token_ex_address", "transfer", runtime_args!{ | ||
from => ex_purse, | ||
to => main_purse, | ||
amount => expected_amount | ||
}); | ||
} | ||
} | ||
``` | ||
|
||
### Account-based Model. | ||
In this example `TokenEx` implements account-based model. | ||
```rust | ||
[#casper_contract] | ||
mod Source { | ||
|
||
[#casper_method] | ||
fn action(max_amount_of_tokens_allowed: U512) { | ||
// Approve tokens. | ||
runtime::call_contract("token_ex_address", "approve", runtime_args!{ | ||
spender => "vault_address", | ||
amount => max_amount_of_tokens_allowed | ||
}); | ||
|
||
// Deposit tokens. | ||
runtime::call_contract("vault_address", "deposit_10_dollars", runtime_args!{}); | ||
} | ||
} | ||
|
||
[#casper_contract] | ||
mod Vault { | ||
|
||
[#casper_method] | ||
fn deposit_10_dollars() { | ||
let price = runtime::call_contract("oracle_address", "ex_price"); | ||
let expected_amount = 10.0 / price; | ||
runtime::call_contact("token_ex_address", "transferFrom", runtime_args!{ | ||
from => runtime::get_caller(), | ||
to => "vault_address", | ||
amount => expected_amount | ||
}); | ||
} | ||
} | ||
``` | ||
|
||
|
||
Account-based model is better because: | ||
1. It doesn't require contracts and accounts to maintain purses in their | ||
named keys space. | ||
2. Transferring tokens in a purse-based model creates a lot of empty purses, that | ||
probably will never be reused. The solution for that might some sort of | ||
garbage collector, but that's just not a problem with the account-based model. | ||
3. In most cases accounts and contracts will want to have all the tokens of one | ||
type in a single purse. That makes accounting much easier (also much cheaper). | ||
Account-based model gives that by the design. | ||
4. Most of the platforms have token defined that way. Following that path, | ||
makes it much easier for developers to implement smart contract, because that's | ||
what they already know. | ||
5. It will allow for much better Solidity portability via the Caspiler transpiler. | ||
|
||
### Approve and Call Problem | ||
On Ethereum it is problematic for accounts to interact with contracts, that require | ||
tokens. The account has to first call the `approve` function, wait for it, to | ||
be executed and only then call desired contract's method. | ||
|
||
On Casper platform contracts can provide helper functions for accounts, that are executed | ||
in the account context. That helper function can easily aggregate multiple approves | ||
and contract calls into one call. | ||
|
||
## Drawbacks and alternatives | ||
The alternative is to: | ||
1. Keep current implementation of Mint and wait for more feedback on purse-based model. | ||
2. Do not define any standard and see what would the community do. | ||
|
||
## Prior art | ||
We point at ERC-20 a the main source of influence: https://eips.ethereum.org/EIPS/eip-20 | ||
|
||
## Unresolved questions | ||
It is useful if the interaction with contracts generates events, that describe | ||
what happened. Currently the Casper platform doesn't have that features, | ||
so this standard will have to be updated in a future, when the shape of the event | ||
functionality is known. | ||
|
||
The standard doesn't cover the upgradeability story. Maybe it should? | ||
|