REX Implementation
This issue describes the EOSIO resource exchange (REX) implementation details. For the mathematical details of the Bancor algorithm used in REX, please see this article. In the following, we use SYS
to represent the core token of an EOSIO based blockchain.
User REX funds
For REX related actions, a user needs to create a REX fund and deposit SYS tokens into the fund. deposit
action, when called for the first time, creates a rex_fund
record for the user and sets the balance to the passed SYS amount. Subsequent deposit
calls add the passed amount to rex_fund's balance
field. An inline token transfer
from user's SYS balance is executed. All REX expenses and proceeds are taken out of or added to rex_fund
, with one exception being the action unstaketorex
which allows buying REX with staked tokens. withdraw
action allows a user to take SYS tokens out of rex_fund
. An inline token transfer
to user's SYS balance is executed.
REX pool balances
REX pool represents the global state of the REX system. It comprises multiple balances:
total_lendable
is the total SYS value of the REX pool. It's the sum of all payments coming from buying REX actions, all CPU and Network loan fees, and RAM trading fees and name auction proceeds (if these system fees are channeled to REX).
total_rex
represents the total number of REX tokens. It is incremented (decremented) upon buying (selling) REX. At any point in time, the value of a REX token is given by total_lendable/total_rex
. CPU and Network loan payments are added to total_lendable
thus increasing the value of a REX token and providing more SYS tokens for renting, and similarly for the system fees mentioned above.
total_unlent
represents the portion of total_lendable
that is available for renting, whereas total_lent
represents portion tied in outstanding loans. By definition, total_lendable = total_unlent + total_lent
.
total_rent
is a virtual balance. The initial value of this balance must be strictly positive. This initial value is determined based on an estimate of the expected SYS to be available shortly after deployment, so that renting costs are similar to other markets. The balances total_rent
and total_unlent
are the two connectors of the Bancor algorithm which determines CPU and Network renting price. The details are available here.
Buying REX with liquid tokens
As the name suggests, the action buyrex
allows a user to buy REX tokens in exchange for a provided number of SYS tokens (let's call it payment
). It is the action that allows a users lend their SYS tokens. The number of issued REX tokens is calculated such that the ratio total_lendable/total_rex
is the same before and after the action is executed. That is, this action leaves the value of a REX token unchanged. A voting requirement is imposed in buyrex
: a user must have voted for a proxy or at least 21 producers. payment
is added to user's vote stake, and votes of corresponding producers are updated. SYS tokens used for the purchase are taken out of user's rex_fund
.
Buying REX with staked tokens
A user can buy REX using staked tokens, without the need for unstaking to liquid tokens first, via unstaketorex
action. The action takes as input the staked tokens owner account name (owner
), the account name to whom the tokens have been previously staked (receiver
), the amount to be unstaked from Network bandwidth (from_net
), and the amount to be unstaked from CPU bandwidth (from_cpu
). The amount of REX tokens rewarded in return for from_net + from_cpu
is calculated the same way as in buyrex
. Same voting requirement also applies. owner
vote weight is updated to its current value, and from_net
and from_cpu
are subtracted from the resource limits of receiver
.
REX maturities
A delay is imposed on selling REX tokens after they're purchased. Tokens bought cannot be sold until 4 days
after end of the day using UTC. Upon multiple purchases, tokens are accumulated into separate maturity buckets represented by rex_maturities
field of owner's rex_balance
. These buckets hold the amounts that can be sold within less than a day, 2 days, ..., 5 days. The amount of matured REX tokens that can be sold immediately is stored in matured_rex
field of rex_balance
. Note that this delay is imposed in order to give the renting market time to react. The action consolidate
allows the owner to consolidate all maturity buckets (not including REX tokens in the savings bucket described below) and matured REX into one bucket with maturity date of 4 days after the end of the day.
REX savings bucket
In addition to maturity buckets described above, a REX owner can utilize a special bucket which we call the "savings bucket". REX held in this bucket does not mature and cannot be sold directly. Users can move already purchased REX from other buckets to their savings bucket at will using the action mvtosavings
. This action moves REX out from the user's buckets as necessary starting with the bucket with furthest maturity date. In order to sell REX in the savings bucket, the user must first explicitly move tokens out of it using the action mvfrsavings
which can be executed at any time. The mvfrsavings
action moves REX from the savings bucket to a bucket with a maturity date that is 4 days after the end of the day.
Selling REX
sellrex
allows user to sell a given number of REX tokens in exchange for SYS. This is equivalent to unlending SYS tokens. If enough SYS tokens are available in total_unlent
, the order is processed; otherwise the order is queued until the condition is satisfied. Sell order price is determined at processing time and not order creation time, assuming the two are different. The owner's vote stake is updated to current value of REX tokens held after the order is filled. Using action cnclrexorder
, an owner can cancel a queued order any time before it's filled. The proceeds of selling REX are added to owner's rex_fund
.
Processing REX sell orders
When a sellrex
order can't be filled, it is added to a queue. That is, a rex_order
record is created. Its fields are owner
which is the unique primary key, rex_requested
, order_time
, proceeds
, stake_change
, and is_open
. Initially we set rex_requested
to REX tokens to be sold, is_open = true
, proceeds = 0.0000 SYS
, stake_change = 0.0000 SYS
, and order_time = current_time_point()
.
When a rex_order
is filled (function fill_rex_order
), which can happen because some action provides enough unlent SYS tokens, we set is_open = false
, set proceeds
to the current exchange price of rex_requested
, and calculate the change in vote stake and save it to stake_change
. We also update owner's REX balance, rex_balance -= rex_requested
, and set vote_stake
to current value of rex_balance
. rex_pool
balances are updated accordingly. The order is then moved to the end of the queue. This is achieved by using a secondary key that is a function of order_time
and is_open
. The rest of rex_order
processing has to be executed in an action pushed by owner
. That involves transfering proceeds
to owner's rex_fund
, updating owner's vote weight by adding stake_change
(can be positive or negative) to vote stake (field staked
in voter_info
), and then deleting the order. That is done by calling function update_rex_account
which also updates the votes of corresponding producers.
An owner can have only one open rex_order
. If the owner pushes a second sellrex
action that can not be filled immediately, the requested REX tokens are added to the rex_requested
field of the already existing order.
REX loans
A user can rent CPU and Network resources on behalf of a receiver
account using rentcpu
and rentnet
actions in exchange for a specified SYS payment
. These actions create a rex_loan
record in cpuloan
and netloan
tables, respectively. The number of SYS tokens to be added to receiver
CPU or Network resources (staked tokens denoted by total_staked
field of rex_loan) is calculated from payment
using the current market price determined by Bancor algorithm. The two connectors of the algorithm are total_unlent
and total_rent
. Calculated total_staked
is transferred from total_unlent
to total_lent
, and payment
is added to total_rent
. After the loan is created, payment
is added to REX pool total_lendable
and total_unlent
thus increasing value of REX token and increasing SYS tokens available for renting. Loan duration is 30 days. At the end of the duration, staked tokens (total_staked
) are subtracted from receiver
resource limits. total_staked
is moved back from total_lent
to total_unlent
, and total_rent
is updated accordingly using Bancor equation.
Loan automatic renewal
In rentnet
and rentcpu
, a user can provide an additional amount of SYS tokens that are added to the loan's balance
field. At expiration, a loan is renewed if it has enough funds, i.e. balance >= payment
, otherwise the loan is closed and the user is refunded any tokens remaining in the loan's balance. If a loan is renewed, total_staked
is recalculated using current market price and receiver
resource limits are updated to reflect that. REX pool balances are updated as well. A loan owner can fund a loan, identified by loan_num
, using fundcpuloan
or fundnetloan
. The owner can also withdraw from a loan's balance using defcpuloan
or defnetloan
.
REX maintenance
In most REX actions, the function runrex
is called. It processes a fixed number (set to 2) of unfilled REX sell orders, expired Network loans, and expired CPU loans. Processing REX orders and loans is described above. Any account can call runrex
directly by pushing the rexexec
action which takes as input the maximum number (max
) of orders, Network loans, and CPU loans to be processed. REX sell orders are given higher priority than loans. Which means that no new loans are created and no existing loans are renewed if rex_order
queue is not empty.
The action updaterex
updates an owner's vote stake to the current SYS value of their REX balance. It also updates the vote weights of producers that the owner has voted for.
The action closerex
deletes an owner's records from REX tables and frees the used RAM. If the owner has a non-zero REX balance, the action fails; otherwise, the owner's rex_balance
entry is deleted. If the owner has no outstanding loans and a zero rex_fund
balance, rex_fund
entry is deleted.
Voting requirements
In order to buy REX, an account must have voted for at least 21 producers or delegated their vote to a proxy. As long as an account holds any REX tokens, the condition must be satisfied.