fip | title | status | type | author | created | updated |
---|---|---|---|---|---|---|
6 |
Transfer locked tokens |
Final |
Functionality |
Ed Rotthoff <ed@dapix.io>, Pawel Mastalerz <pawel@dapix.io> |
2020-04-16 |
2021-06-15 |
This FIP implements he ability to transfer tokens to a new account and lock those tokens on a pre-defined schedule.
Proposed new actions:
Action | Endpoint | Description |
---|---|---|
trnsloctoks | transfer_locked_tokens | Transfer and locks tokens per provided schedule. |
get_locks | Returns lock periods for account. |
Modified actions:
Action | Endpoint | Description |
---|---|---|
get_fio_balance | Modified to add available token balance. |
The FIO Protocol includes token locking functionality, which was built specifically to accommodate complex requirements for locking tokens minted at Mainnet. This functionality was not intended to be available post Mainnet.
The original use case was to allow the Foundation to grant or sell tokens with attached locks. During the design, the scope was extended to support the use case of allowing individuals to lock tokens for themselves as savings or for security reasons. However, supporting both use cases added unnecessary complexity and the FIP was scaled down to focus on the original use case. Individual users can still lock their own tokens using functionality developed for this FIP.
When the transfer of locked tokens is initiated, the target account (hashed from provided FIO Public Key) will be created, the specified amount will be transferred and a lock for that amount will be attached to the account. The account can function normally, funds may be transferred in and out of the account, except that the original amount of locked tokens remain locked until unlock periods are reached.
To allow for most flexibility when tokens are locked the following variables define the type of lock:
- Can vote - when set to 1, 100% of tokens can be voted/proxied, even when locked.
- Lock periods - one or many time intervals at which specified percentage of tokens is unlocked.
- trnsfiopubky - when a transfer is initiated, only unlocked tokens in account can be transferred.
- voteproducer and voteproxy
- If can_vote set to 0 only unlocked tokens are counted in vote.
- If can_vote set to 1 all tokens in account are counted in vote, even when locked.
- Payment of fee. When fee is collected, only unlocked tokens may be used to pay for that fee. Bundled transactions can always be used, even when all tokens are locked.
Funds in locked accounts are evaluated for unlocking and unlocked if eligible, whenever trnsfiopubky, voteproducer, voteproxy, or payment of fee are triggered.
Transfer and locks tokens per provided schedule.
"periods": [
{
"duration": 86400,
"percent": 1.2
},
{
"duration": 172800,
"percent": 90.8
},
{
"duration": 259200,
"percent": 8.0
}
]
The fee will be: (1 90-day period in 259200 seconds * 300000000) = 600000000, the fee type will be mandatory.
Parameter | Required | Format | Definition |
---|---|---|---|
payee_public_key | Yes | Valid FIO Public Key | FIO public key of account where locked tokens will be sent. |
can_vote | Yes | 0 is not can_vote, 1 is can_vote. | This indicates if the locked amount can vote/proxy while locked. |
periods | Yes | JSON Array of unlock periods. See periods below. See Lock period verification in Processing for requirements. | Schedule by which tokens become unlocked. |
amount | Yes | Int | The amount of tokens to transfer and lock in SUFs |
max_fee | Yes | Positive Int | Maximum amount of SUFs the user is willing to pay for fee. Should be preceded by /get_fee for correct value. |
tpid | Yes | FIO Address | FIO Address of the entity which generates this transaction. TPID rewards will be paid to this address. Set to empty if not known. |
actor | Yes | 12 character string | Valid actor of signer |
Parameter | Required | Format | Definition |
---|---|---|---|
duration | Yes | Int | Seconds from lock creation to when unlock of corresponding percentage occurs. |
percent | Yes | Double | Percentage of locked tokens that unlock after corresponding duration lapsed. |
{
"payee_public_key": "FIO8PRe4WRZJj5mkem6qVGKyvNFgPsNnjNN6kPhh6EaCpzCVin5Jj",
"can_vote": 0,
"periods": [
{
"duration": 86400,
"percent": 50.0
},
{
"duration": 172800,
"percent": 25.0
},
{
"duration": 259200,
"percent": 25.0
}
],
"amount": 40000000000000,
"max_fee": 40000000000,
"tpid": "rewards@wallet",
"actor": "aftyershcu22"
}
- Request is validated per Exception handling:
- Require auth of the actor.
- Ensure payee public key does not hash down to existing account.
- Lock period verification:
- Minimum of 1 period. Maximum: 50. "Invalid number of unlock periods"
- verify 3 digit precision of percentage for each period. "Invalid precision for percentage in unlock periods"
- Sum of percentage in all periods is 100%. "Invalid total percentage for unlock periods"
- Duration in each period is greater than 0. "Invalid duration value in unlock periods" * Validate that all values of duration are ordered from smallest to largest verify that each lock period is greater then the previous lock period in the locking periods. "Invalid duration value in unlock periods"
- Verify the locking account has necessary balance.
- Verify that the fee for this does not exceed the max fee specified.
- Verify transaction does not exceed max transaction size.
- Create account for the payee public key.
- Charge appropriate fee.
- Perform token transfer.
- Create entry in the locktokens table for these lock tokens.
- Increase RAM.
- Return response.
Error condition | Trigger | Type | fields:name | fields:value | Error message |
---|---|---|---|---|---|
Invalid payee public key | Specified public key is not valid FIO format. | 400 | "payee_public_key" | Value sent in, e.g. "notakey" | "Invalid FIO Public Key." |
Account already exist | Account hashed down from Public Key alreday exists. | 400 | "payee_public_key" | Value sent in | "Locked tokens can only be transferred to new account." |
Invalid can_vote | Value sent in is not 0 or 1 | 400 | "can_vote" | Value sent in, e.g. "-100" | "Invalid can_vote value." |
Invalid unlock periods | See Lock period verification in Processing | 400 | "unlock_periods" | "Invalid unlock_periods." | |
Invalid fee value | max_fee format is not valid | 400 | "max_fee" | Value sent in, e.g. "-100" | "Invalid fee value." |
Invalid amount | Amount is not numeric or not > 0 | 400 | "amount" | Value sent in, e.g. "-1000000000" | "Invalid amount." |
Insufficient balance | Account does not have enough funds for transfer and fee | 400 | "amount" | Value sent in, e.g. "1000000000" | "Insufficient balance." |
Invalid TPID | tpid format is not valid | 400 | "tpid" | Value sent in, e.g. "notvalidfioaddress" | "TPID must be empty or valid FIO address" |
Fee exceeds maximum | Actual fee is greater than supplied max_fee | 400 | max_fee" | Value sent in, e.g. "1000000000" | "Fee exceeds supplied maximum" |
Parameter | Format | Definition |
---|---|---|
status | String | OK if successful |
fee_collected | Int | Amount of SUFs collected as fee |
{
"status": "OK",
"fee_collected": 0
}
Returns lock periods for account.
Parameter | Required | Format | Definition |
---|---|---|---|
fio_public_key | Yes | Valid FIO Public Key | FIO Public Key of account, where locks exist. |
- Request is validated per Exception handling
- Ensure hash and account match.
- Hash Public Key to account.
- Get the account from the fio account mapping.
- Read the locktokens table by the specified account.
- Traverse all lock periods.
- Add each period to the results.
- Return list of all lock periods.
Error condition | Trigger | Type | fields:name | fields:value | Error message |
---|---|---|---|---|---|
No locked tokens in account. | No lock for this account. | 404 | "No locked tokens in account." |
Group | Parameter | Format | Definition |
---|---|---|---|
lock_amount | Int | SUFs initial locked. | |
remaining_lock_amount | Int | SUFs still locked. | |
time_stamp | String | Time when lock started. | |
payouts_performed | Int | Number of unlock periods which were unlocked. | |
can_vote | Int | Indicates if the locked amount can vote while locked: 0 - can not vote, 1 - can vote. | |
unlock_periods | duration | Int | Seconds from lock creation to when unlock of coresponding percentage occurs. |
unlock_periods | percent | Double | Percentage of locked tokens that unlock after coresponding duration lapsed. |
{
"lock_amount": 40000000000000,
"remaining_lock_amount": 20000000000000,
"time_stamp": "2020-03-11T18:30:56",
"payouts_performed": 2,
"can_vote": 1,
"unlock_periods": [
{
"duration": 86400,
"percent": 50.0
},
{
"duration": 172800,
"percent": 25.0
},
{
"duration": 259200,
"percent": 25.0
}
]
}
get_fio_balance API call will be modified to add available token balance.
Unchanged
{
"fio_public_key": "FIO8PRe4WRZJj5mkem6qVGKyvNFgPsNnjNN6kPhh6EaCpzCVin5Jj"
}
- Available balance is added to response and includes only tokens which are not locked (by Mainnet locks or locks developed for this FIP).
Unchanged
Parameter | Format | Definition |
---|---|---|
balance | Int | Total SUF balance associated with supplied public key (includes locked tokens). |
available | Int | Available SUF balance associated with supplied public key (does not include locked tokens). |
{
"balance": 100000000000,
"available": 100000000000
}
Since this will be another action which creates a new account, History plug-in has to be modified to properly recognize those accounts.
An approach was considered to lock tokens in smart contract instead of in account, but solution described in this FIP was deemed more appropriate as it's already used for Mainnet locks and offers more security than single contract account holding funds of multiple users.
- Removed clean token actions. This is consistent with our strategy Pre-Mainnet and in FIP (e.g. FIP-8 of not removing things from state, except when required for functionality (e.g. burn expired domains). We need a comprehensive approach to what gets removed when and how across all contracts. This way we do not end up with different strategy for removing locks and different for removing requests. There is already FIP planned to address this.
- Locking of existing accounts will not be allowed. Locks can only be applied to new account. The reason being it can change an account of a user without that user's knowledge or consent. For example, a User A may send 1 locked token to User B, without User B consenting to it. Now user B has locked tokens intermingled with unlocked tokens and that impacts their total and available token balance display, but they can't do anything about it.
- Ability to lock user's own account will not be supported as the use case of allowing individuals to lock tokens for themselves as savings or for security reasons was descoped from this FIP.
- Added TPID to lock_accounts. TPID should always be present on transactions expected to be exposed to users to encourage implementation by wallets.
- Changed fee to be based on longest duration in unlock_periods.
- Removed unlock_tokens action. This is handled automatically and not needed if only original use cases is being supported.
- get_locks paging has been eliminated as there is only one lock per account and unlock periods are limited.
- Added changes to get_fio_balance and History plug-in.
May need to be revised.
- Add new locking structures to the fio.system contract.
- Make a new table locktokens
- int64 lockid //this is the identifier of the lock
- name owner_account; //this is the account that owns the lock
- int64_t lock_amount = 0; //this is the amount of the lock in FIO SUF
- int32_t payouts_performed = 0; //this is the number of payouts performed thus far.
- int32 can_vote = 0; //this is the flag indicating if the lock is votable/proxy-able
- vector periods; // this is the locking periods for the lock
- int64_t remaining_lock_amount = 0; //this is the amount remaining in the lock in FIO SUF, get decremented as unlocking occurs.
- uint32_t timestamp = 0; //this is the time of creation of the lock, locking periods are relative to this time.
- struct lockperiods //this is the definition of locking periods.
- int64_t duration; //this is the duration of the lock period, relative to the timestamp on the lock.
- double percent; //this is the percent to unlock when the time period has passed.
- Add tables and indexes to fio.system contract. (1 day)
- Integrate accounting logic and check for can transfer into token contract, and voting. Affected files (fio.token.hpp, fio.token.cpp, voting.cpp) (2 days).
- Add new API end point for lock_tokens - modify chain_api_plugin to add new endpoint, modify chain_plugin.cpp and hpp to add new params and code.
- Add new action (locktokens) to fio.token.cpp. dev test api endpoint and push action and resolve all issues (1 days)
- Add new API end point for get_locks - modify chain_plugin cpp and hpp to add new params and code. dev test api endpoint and resolve all issues (1 days)
- Modify get_fio_balance to return balance and locked:numberlockedtokens. (4 hours)
- Modify History plugin for lock_tokens, to ensure tx gets into block explorer (1 days)
- Create a lock verify the schedule is obeyed: 1 locking period, multiple locking periods.
- Make a votable lock, vote for producers.
- Make a votable lock, proxy this accounts vote to another, verify voting power.
The contract action trnsloctoks was released in fio.contracts v2.3.0.
The transfer_locked_tokens and get_locks API endpoints released in fio v3.0.0.
/get_fio_balance is the only existing API method being modified, but only a new response element is added, so wallets currently implementing this call should not be affected.
Token locks described in this FIP are different from Mainnet locks and a single account cannot be locked in both ways. The only overlap exists when computing available tokens in /get_fio_balance.
Support the use case of allowing individuals to lock tokens for themselves as savings or for security reasons should be considered.