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

Native tokens RFC WIP #416

Draft
wants to merge 14 commits into
base: master
Choose a base branch
from
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ implemented or considered for implementation.

## [Serialization formats](./serializations.md)

## [Native tokens](./tokens/tokens.md)

## Aeternity node

The Aeternity node is the reference implementation of the Aeternity protocol. Since we don't
Expand Down
1 change: 1 addition & 0 deletions node/api/api_encoding.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ tag.
| kh | base58 | Key block hash |
| mh | base58 | Micro block hash |
| nm | base58 | Name |
| nt | base58 | Aeternity Native Token (ANT) |
| ok | base58 | Oracle pubkey |
| oq | base58 | Oracle query id |
| or | base64 | Oracle response |
Expand Down
118 changes: 118 additions & 0 deletions serializations.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ ambiguity.
| 4 | oracle |
| 5 | contract |
| 6 | channel |
| 7 | token |

In Erlang notation, the `id()` type pattern is:
```
Expand Down Expand Up @@ -252,6 +253,12 @@ subsequent sections divided by object.
| Sophia byte code | 70 |
| Generalized accounts attach transaction | 80 |
| Generalized accounts meta transaction | 81 |
| Aeternity Native Token (ANT) | 90 |
| ANT create transaction | 91 |
| ANT mint transaction | 92 |
| ANT finalize transaction | 93 |
| Token trade transaction | 94 |
| Token burn transaction | 95 |
| Key block | 100 |
| Micro block | 101 |
| Light micro block | 102 |
Expand Down Expand Up @@ -282,6 +289,24 @@ subsequent sections divided by object.
]
```

#### Accounts (version 4, Normal accounts with flags and tokens, from XXX release)
```
[ <flags> :: int()
hanssv marked this conversation as resolved.
Show resolved Hide resolved
, <nonce> :: int()
, <balance> :: int()
, <tokens> :: [{id(),int()}]
```

#### Accounts (version 5, Generalized accounts with tokens, from XXX release)
```
[ <flags> :: int()
, <nonce> :: int()
, <balance> :: int()
, <ga_contract> :: id()
, <ga_auth_fun> :: binary()
, <tokens> :: [{id(),int()}]
```

### Signed transaction
```
[ <signatures> :: [binary()]
Expand Down Expand Up @@ -309,6 +334,28 @@ The recipient must be one of the following:
* A contract identifier.
* A name identifier, whose related name entry has an identifier as value of pointer with key `account_pubkey`.
If multiple pointer entries are present for such key, then the first of such entries is used.
`

### Spend token transaction From the XXX release
```
[ <sender> :: id()
, <recipient> :: id()
, <token> :: id()
, <amount> :: int()
, <fee> :: int()
, <ttl> :: int()
, <nonce> :: int()
, <payload> :: binary()
]
```

The recipient must be one of the following:
* An account identifier.
* An oracle identifier.
* A contract identifier.
* A name identifier, whose related name entry has an identifier as value of pointer with key `account_pubkey`.
If multiple pointer entries are present for such key, then the first of such entries is used.
`

#### Oracles
```
Expand Down Expand Up @@ -1013,3 +1060,74 @@ NOTE:
, <pubkey> :: binary()
]
```

#### Aeternity Native Token (ANT)
```
{ creator :: id()
, meta_data :: binary()
, contract :: id()
, total_supply :: int()
, parent :: id() TODO: Decide if we should have hierarchical tokens
, final :: bool()
}
```


#### ANT create transaction
```
[ <creator> :: id()
, <meta_data> :: binary()
, <contract> :: id()
, <amount> :: int()
, <recipient> :: id()
, <parent> :: id() TODO: Decide on hierarchical tokens
, <final> :: bool()
, <ttl> :: int()
, <fee> :: int()
, <nonce> :: int()
]
```

#### ANT mint transaction
```
[ <owner> :: id()
, <ANT> :: id()
, <amount> :: int()
, <recipient> :: id()
, <final> :: bool()
, <ttl> :: int()
, <fee> :: int()
, <nonce> :: int()
]
```

#### ANT finalize transaction
```
[ <owner> :: id()
, <ANT> :: id()
, <ttl> :: int()
, <fee> :: int()
, <nonce> :: int()
]
```

#### Token trade transaction

```
[ <trades> :: [{<sender> :: id(), <receiver> :: id(), <amount> :: int(), <ANT> :: id()}]
, <ttl> :: int()
, <fee> :: int()
, <nonce> :: int()
]
```

#### Token burn transaction
```
[ <account> :: id()
, <ANT> :: int()
, <amount> :: int()
, <ttl> :: int()
, <fee> :: int()
, <nonce> :: int()
]
```
207 changes: 207 additions & 0 deletions tokens/tokens.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
# Aeternity Native Tokens

## Overview

We need to distinguish between a specification of a token, and a
countable amount of the token. In this document we use **ANT**
(Aeternity Native Token) to represent the specification, and **native
token** or simply **token** to represent the countable asset.

Informally, when you need a new type of token for whatever purpose,
you create an ANT describing the token, and then you can mint new
tokens and start sending them to accounts.

Owners of the tokens can spend them to other accounts, or trade them
(atomically) for other assets with other accounts. If tokens are no
longer wanted you can burn them.

Only the owner of the ANT can mint new tokens. Only the owner can
finalize the ANT, preventing more minting. An ANT can also be
created with a final supply already minted.

An ANT can only be destroyed (TODO: design decision) if the total
token supply of the ANT is zero (i.e., all tokens are burnt).

Aeons are not part of the ANT system. There are no consensus
Copy link
Contributor

Choose a reason for hiding this comment

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

*Aettos

controlled exchange rate between tokens and aeons. Fees cannot be
payed by native tokens, but only by aeons. Other services could be
payed for with tokens (e.g., contracts accepting tokens as
payment). (TODO: Design decision: Should oracles be allowed to set
fees in tokens?)

The owner of a token can decide to govern the behavior of the tokens
by connecting a contract when an ANT is created. The contract must
provide entrypoints according to a specified ACI where one or many of
the primitive operation (spend, trade, mint, etc) is called to decide
if the operation is allowed or not.

## Definitions

Note that when *account* is used below, the account can belong to a
contract, the account may be an oracle, etc.

An Aeternity Native Token (ANT):
- is a *specification* of tokens
- is a *first class object* on the chain with its own state tree
- is *created* by an account which becomes its owner
- *governs the behavior* when interacting with tokens (trading, sending, minting, etc).
- can have a *connected contract* to govern the handling of its tokens

A native token:
- has an ANT describing its behavior.
- is *countable*
- is recorded as a *balance in the account* that owns it (in the accounts state tree).
- is *mintable* (`mint`)
- is *transferable* (`spend`, `trade`)
- is *destroyable* (`burn`)

## Tokens state tree

The tokens state tree contains ANT objects. Tokens are stored in the
accounts state tree in the owner's account.

### ANT object

The token id is determined by the creator's account and nonce.
```
id := Blake2b(<CreatorPubkey><CreateTxNonce>)
```
where `Blake2b` is the 256 bit hash. The [API
serialization](../node/api/api_encoding.md) of an ANT id is tagged by
`nt_`

The ANT contains the fields:
- `creator` - The account id of the creator
- `meta_data` - A byte array field, uninterpreted, but under consensus
- `contract` - A contract id if there is a governing contract, or the empty binary otherwise
- `total_supply` - A counter of the currently available amount of the token
- `parent` - The id of the parent ANT (TODO: Hierarchical tokens?)
- `final` - If the ANT is final, new tokens cannot be minted. Can flip to true, but never back to false again.

The `meta data` is an uninterpreted string but token minters are
encouraged to use the following json object: (TODO: Extend). The
intention of the meta data is to have a consensus controlled way of
providing information usable by tools (e.g., displaying an intended
denominator, a display name, etc).
```
{
"type" : "object",
"properties": { "name": {"type" : "string"},
}
Copy link
Contributor

Choose a reason for hiding this comment

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

and an identifier... sure, that could be the hash of the string, but it has to be the unique thing to work with in the contract. We could even allow a string of 3 unique characters, like in valutas... that's limited, but makes contracts very readable.

}
```

A contract governing the usage of tokens must have a non-empty subset
Copy link
Contributor

Choose a reason for hiding this comment

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

but there must not be a governing contract?

of the following ACI:

```
spend(recipient : address, payload : Type) : boolean
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the payload type here? A binary string, I guess, like in normal spend?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it probably could be something ABI encoded. Just as in oracles. 🤔

trade([(from : address, to: address, Option(token : address))], payload : Type) : boolean
Comment on lines +98 to +99
Copy link
Contributor

Choose a reason for hiding this comment

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

amounts?

mint(amount: integer) : boolean
burn(amount : integer) : boolean
Copy link
Contributor

Choose a reason for hiding this comment

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

Call.caller = the account burning the tokens, presumably. And same for spend.

```

The contract may contain other endpoints as well, but at least one of
the entrypoints above must be implemented. The contract can only be
attached at create time.

If a contract is provided, any transaction (spend, trade, mint, burn)
would call this contract and the transaction only goes through if the
result of the corresponding contract call returns true. Any other
Comment on lines +108 to +110
Copy link
Contributor

Choose a reason for hiding this comment

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

Note that in the case of a trade there will be one contract call per unique ANT used in the trade. What if different ANTs share the same contract?

transaction using the token (such as contract call) would only be
executed if a call to spend returns true. (TODO: Decide how this plays
with contract calls that tries to pass tokens as value, etc).
Comment on lines +108 to +113
Copy link
Contributor

Choose a reason for hiding this comment

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

Who pays for the gas (the caller of the transaction presumably)? This means that all transactions dealing with tokens need gas/gas price fields.


Note that the `final` field in the ANT takes precedence over the
governing contract. If the ANT is final, no minting can occur. (TODO:
Perhaps the contract should have a `finalize` endpoint as well?)
Copy link
Contributor

Choose a reason for hiding this comment

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

👍


## ANT transactions

### ANT create transaction (`ant_create_tx`)

The ANT create transaction takes the argument:
- Meta data : string
- Amount
- Recipient
- Final
- Contract
- Parent (TODO: Hierarchical tokens?)
Copy link
Contributor

Choose a reason for hiding this comment

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

What does hierarchical tokens mean?


The `amount` is the number of tokens to mint at create time. Set to
`0` if none should be minted. This can for example be combined with
setting the `final` argument to true, thereby immediately minting all
tokens that will ever exist.

The `recipient` is the recipient of the minted tokens in `amount`. If
not provided, the tokens are given to the creator's account.

The `parent` is a pointer to a parent token for hierarchical
tokens. (TODO: hierarchical tokens?)

### ANT finalize transaction (`ant_finalize_tx`)

The ANT finalize transaction takes the arguments:
- owner
- ANT

The finalize transaction can only be submitted by the actual owner,
and only if the ANT is not already finalized.

### ANT mint transaction (`ant_mint_tx`)
The ANT mint transaction takes the argument:
- owner
- ANT
- amount
- recipient
- final

Only the `owner` can mint new tokens, but it can pass the minted
tokens to a recipient. If `final` is set to true, the ANT will be
finalized after the new tokens are minted.

### ANT destroy transaction
TODO: Should we be able to destroy an ANT that has a `total_supply` of 0?
Copy link
Contributor

Choose a reason for hiding this comment

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

What would be the incentive for the owner to destroy the ANT? Also it seems unlikely that the total supply will ever reach 0 (case-in-point: aeternity token migration).



## Token transactions

### Token trade transaction
The token trade transaction takes the argument:
- trades

The `trades` field contans a non-empty list of token transfers. A
single token transfer consists of the fields:

- Sender
- Receiver
- Amount
- An ANT id

This construct makes it possible to atomically perform complicated
trade operations involving more than one ANT and also aeons (TODO: How
to signal that a trade concerns aeons) between multiple parties.

The transaction must be signed by all senders. (TODO: Should we use
some alternative multisig format here?).


### Token burn transaction

The token burn transaction contains the fields
- Account (owner of the tokens, not necessarily of the ANT)
- Amount
- ANT

Destroy an `Amount` of `ANT` tokens currently owned by the
`account`. The burned amount is also counted from the `total_supply`
in the ANT object.

### Other transactions on tokens
Copy link
Contributor

Choose a reason for hiding this comment

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

this PR should probably include the protocol changes to these other transactions as well


Tokens can also be transfered from one account to another through any
other transaction that can pass an amount. You can:
- spend tokens through the `spend_tx`
- pass tokens as value in a `contract_call_tx` or `contract_create_tx`
- pass tokens as value in contract calls in a smart contract.
Comment on lines +205 to +206
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider allowing multiple currencies (for instance, some tokens + a tip in aeons).

- spend tokens as query fees in `oracle_query_tx` (TODO: Might be a future extension).