關鍵字:REX,資源交易,資源租賃,系統費用,bancor,成熟期,EOS,eosio.system,voting
EOSIO 智能合約在v1.6.0版本增加了一個system合約使用的例子,可提供EOS資源交易。以供社區評估、調整和構建。REX只是智能合約層面提供的功能,而並沒有相應的用戶界面,部署選擇等內容。
REX介紹
按照設計思路,REX是鏈上的主幣持有者參與的一個CPU和網絡資源租賃市場,參與者可以通過買賣REX池中的REX幣來借出或收回他們的現有資源。下面有幾個限制條件:
- 主幣持有者,只有為21個超級節點投票或者通過代理抵押投票的主幣持有者,才能參與從REX池中租賃cpu和net資源以滿足他們的需要。
- 每一筆借出的持續時間被設定為30天。
- 資源借出的價格由自動的市場作價者來決定。
- REX幣並不能作為數字貨幣直接交易,僅是便於做核算的單位,並有助於反應租賃活動的情況,計算確定REX持有者的回報率。
- 可選方案:未來內存資源的買賣以及賬戶的拍賣收益均可導入REX池,從而提供更多的來源讓REX持有者獲益。
系統費用轉向REX
系統費用目前包含了內存資源的買賣,(網絡cpu的抵押費用)以及賬戶的拍賣費用。在當前新版本的eosio.system合約中,默認設置將系統費用轉由REX池收集,該設置生效以后,所有新產生的系統費用將由REX負責收集,但這並不影響之前作為管理內存買賣的eosio.ramfee賬戶以及收集賬戶拍賣費用的eosio.names賬戶的固有資金。這中設計的目的是為了保持系統的向前兼容。
同時,超級節點管理者仍然保留了是否切換REX的權利,只需要在system合約源代碼中修改宏CHANNEL_RAM_AND_NAMEBID_FEES_TO_REX的定義。
// 默認是1,由REX收集系統費用,如果想保持原樣不使用REX,則修改下面的值為0.
#define CHANNEL_RAM_AND_NAMEBID_FEES_TO_REX 1
依賴
- EOSIO/eos 要部署v1.7.0版本及以上
- EOSIO/eosio.cdt 要部署v1.5.0版本及以上
- EOSIO/eosio.contracts 要部署v1.6.0版本及以上
部署
REX功能對system系統合約的初始化有了新的要求。
[1]eosio.rex
eosio.rex賬戶必須加入原有的系統賬戶,在合約部署前要被創建成功,同時該賬戶不是一個特權賬戶。
目前構建用於測試的本地EOS鏈的常用方式是使用python腳本eos/tutorials/bios-boot-tutorial/bios-boot-tutorial.py。
關於鏈啟動時序的內容請轉到此處復習一下。
那么按照這個要求,把eosio.rex加入到bbt腳本中的系統賬戶集合中。
systemAccounts = [
'eosio.bpay',
'eosio.msig',
'eosio.names',
'eosio.ram',
'eosio.ramfee',
'eosio.saving',
'eosio.stake',
'eosio.token',
'eosio.vpay',
'eosio.rex'
]
[2]eosio::init
system合約的eosio::init接口,最早於v1.4.0版本正式引入,只在system合約首次部署的時候被使用到。在當前版本,該接口被修改增加了一個內聯調用eosio.token::open接口的操作,用來幫助eosio.rex賬戶開啟一個主幣余額為0的入口。
eosio.token::open接口最早於v1.3.0版本引入,所以建議先由eosio.token賬戶部署一個最近的版本(指超過v1.3.0的,本篇研究時的環境均為v1.6.0版本)eosio.token合約,然后再部署system合約。
如果是最新版本替換舊版本,則eosio::init動作是不必要的甚至不允許的。區塊生產者可執行eosio.token::open動作來幫助eosio.rex賬戶開啟一個主幣余額為0的入口。所以在bbt腳本中無須針對此處做任何修改。
[3]rex.results.abi
ABI文件rex.results.abi需要被賬戶eosio.rex部署,而相應的rex.results.wasm不能被部署。rex.results合約的接口 buyresult, sellresult, rentresult, 和 orderresult 都沒有外部操作。他們都作為一種內聯的操作集成進接口 rentnet, rentcpu, buyrex, unstaketorex, and sellrex。內聯的操作不會造成任何影響,他們的數據包含在父接口的動作中,可被追蹤。
按照這個要求,需要在bbt腳本中補充:
def stepSetSystemContract():
retry(args.cleos + 'set contract eosio.rex ' + args.contracts_dir + '/eosio.system/ eosio.system.wasm rex.results.abi ')
retry(args.cleos + 'set contract eosio ' + args.contracts_dir + '/eosio.system/')
...
REX實現
本節通過以下12個方面介紹REX實現的詳細邏輯。
(一)用戶REX基金
要想得到REX的相關操作,用戶需要首先創建一個REX基金,並且使用主幣向該基金充值。
基金不僅用於burrex動作,還用於所有的涉及到修改用戶余額的動作。它也方便退款以及延遲賣單,因為它可能會被另一個用戶所執行。
- deposit: 首次調用時,會為該用戶創建一條rex_fund記錄並通過轉入主幣數量設置余額。后續的繼續充值的操作會修改rex_fund的balance字段。接着會調用內聯轉賬,從用戶的主幣余額中真實划賬。下面給測試賬戶useraaaaaaaa創建rex基金,觀察余額變化,賬戶余額從100000減少了10塊被轉到了REX基金,還剩下99990。:
evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 get currency balance eosio.token useraaaaaaaa "SYS"
100000.0000 SYS
evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex deposit useraaaaaaaa "10.0000 SYS"
executed transaction: 084717efbf446f25e58226e63ce71b1a17745f85a56cb4dbf663b6e410e2393d 120 bytes 465 us
# eosio <= eosio::deposit {"owner":"useraaaaaaaa","amount":"10.0000 SYS"}
# eosio.token <= eosio.token::transfer {"from":"useraaaaaaaa","to":"eosio.rex","quantity":"10.0000 SYS","memo":"deposit to REX fund"}
# useraaaaaaaa <= eosio.token::transfer {"from":"useraaaaaaaa","to":"eosio.rex","quantity":"10.0000 SYS","memo":"deposit to REX fund"}
# eosio.rex <= eosio.token::transfer {"from":"useraaaaaaaa","to":"eosio.rex","quantity":"10.0000 SYS","memo":"deposit to REX fund"}
warning: transaction executed locally, but may not be confirmed by the network yet ]
evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 get currency balance eosio.token useraaaaaaaa "SYS"
99990.0000 SYS
- unstaketorex:所有REX的消費和收益均體現在對rex_fund的減少和增加操作,但其中只要一個例外就是unstaketorex動作,允許用戶使用抵押幣來購買REX。下面通過賬戶useraaaaaaaa的抵押幣夠買rex基金,觀察抵押幣的變化,確實net和cpu各減少了100塊,對上賬了(順便再查看一下賬戶的余額,確實沒有變化,仍舊是99990)。
evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system listbw useraaaaaaaa
Receiver Net bandwidth CPU bandwidth
useraaaaaaaa 193728833.4889 SYS 193728833.4889 SYS
evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex unstaketorex useraaaaaaaa useraaaaaaaa "100.0000 SYS" "100.0000 SYS"
executed transaction: 45ea6749c406f496b5554e3729e8e53fbc848c964635298483a25ec8dc6a6e79 144 bytes 1042 us
# eosio <= eosio::unstaketorex {"owner":"useraaaaaaaa","receiver":"useraaaaaaaa","from_net":"100.0000 SYS","from_cpu":"100.0000 SYS...
# eosio.token <= eosio.token::transfer {"from":"eosio.stake","to":"eosio.rex","quantity":"200.0000 SYS","memo":"buy REX with staked tokens"...
# eosio.stake <= eosio.token::transfer {"from":"eosio.stake","to":"eosio.rex","quantity":"200.0000 SYS","memo":"buy REX with staked tokens"...
# eosio.rex <= eosio.token::transfer {"from":"eosio.stake","to":"eosio.rex","quantity":"200.0000 SYS","memo":"buy REX with staked tokens"...
# eosio.rex <= eosio.rex::buyresult {"rex_received":"2000000.0000 REX"}
warning: transaction executed locally, but may not be confirmed by the network yet ]
evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system listbw useraaaaaaaa
Receiver Net bandwidth CPU bandwidth
useraaaaaaaa 193728733.4889 SYS 193728733.4889 SYS
- withdraw:允許用戶從rex_fund中取出主幣。通過調用一個內聯token轉賬到用戶賬戶的操作。下面測試該動作,首先為賬戶useraaaaaaaa提取100塊,但提示資金不足,說明使用抵押幣購買的REX基金不能被withdraw動作取出,因此改為提取1塊,執行成功,檢查賬戶余額,由99990變為99991,對上賬了。
evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex withdraw useraaaaaaaa "100.0000 SYS"
Error 3050003: eosio_assert_message assertion failure
Error Details:
assertion failure with message: insufficient funds
pending console output:
evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex withdraw useraaaaaaaa "1.0000 SYS"
executed transaction: 1d24ac5d5e1cce3eb13b108793ea31b8ee5cb73abbf68002d18d7a8196951c7d 120 bytes 644 us
# eosio <= eosio::withdraw {"owner":"useraaaaaaaa","amount":"1.0000 SYS"}
# eosio.token <= eosio.token::transfer {"from":"eosio.rex","to":"useraaaaaaaa","quantity":"1.0000 SYS","memo":"withdraw from REX fund"}
# eosio.rex <= eosio.token::transfer {"from":"eosio.rex","to":"useraaaaaaaa","quantity":"1.0000 SYS","memo":"withdraw from REX fund"}
# useraaaaaaaa <= eosio.token::transfer {"from":"eosio.rex","to":"useraaaaaaaa","quantity":"1.0000 SYS","memo":"withdraw from REX fund"}
warning: transaction executed locally, but may not be confirmed by the network yet ]
evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 get currency balance eosio.token useraaaaaaaa "SYS"
99991.0000 SYS
(二)REX池余額
REX池代表了REX系統的全局狀態,它由多種余額組成:
- total_lendable,表示REX池的總主幣價值。它是購買REX的資金,以及NET和CPU抵押的資金、內存交易的資金,還有賬戶名拍賣的收益之和(后三項取決於系統費用轉向REX的設置)。
- total_rex,表示該賬戶所有的REX幣總量,取決於買賣REX動作的交易量。任何時候,賬戶的REX幣余額都指的是total_lendable除以total_rex。++系統費用轉向REX有利於增加REX幣的價值同時可提供更多的主幣出租。++
REX pool balances = total_lendable / total_rex
- total_unlent,表示total_lendable的其中可以被出租的一部分。
- total_rent,表示total_lendable的其中已出租的一部分。所以按照定義:
total_lendable = total_unlent + total_lent
total_rent
total_rent是一個虛擬余額,它的初始化值必須是正數,基於對預期的主幣能夠在部署后不久就可用的評估得到這個初始化值,所以出租成本與其他市場類似。total_unlent和total_rent是Bancor算法中的兩個連接器,決定了CPU和NET的出租價格。為了更好地理解這個算法在REX中的應用,請參照一篇文章。
區塊生產者可按需通過setrex動作來重置REX池的total_rent余額。但這個行為在初始化REX系統時並不是必須的,也不推薦使用超過一次。這是一個備份機制,當初始設置有誤或者不符合token借出的金額時,可讓區塊生產者能夠平衡租借市場的價格。setrex動作不會使total_rent加入或刪除某個真的token。
(三)余額購買REX
余額指的是用戶的REX基金rex_fund的余額,單位是以主幣計算,使用該余額來購買REX,可以通過buyrex動作。
payment: 是指用戶通過提供一定數量的主幣,來交易得到REX幣。
payment將被添加到賬戶的投票抵押,相應的超級節點的投票數量也會更新,所以余額購買REX幣的過程與CPU,NET資源抵押的過程非常相似。
該機制也正是為了decreasing voter apathy(減少選民冷漠)
- buyrex:該動作可以讓用戶出租他們的主幣。REX幣的發行是預先計算好的,因此在該動作執行前后,total_lendable除以total_rex的比率是一樣的。也就是說,buyrex動作並未對REX的價值造成變化。buyrex動作強制只有抵押投票或代理參與投票21個超級節點的賬戶才可以調用。
evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex buyrex useraaaaaaaa "4.0000 SYS"
executed transaction: c713e98584f971d71d21676a830ac15a8b557d48582677b334cdc34cd12e8860 120 bytes 894 us
# eosio <= eosio::buyrex {"from":"useraaaaaaaa","amount":"4.0000 SYS"}
# eosio.rex <= eosio.rex::buyresult {"rex_received":"40000.0000 REX"}
warning: transaction executed locally, but may not be confirmed by the network yet ]
(四)抵押購買REX
上面談了使用在rex_fund基金中的主幣余額購買REX的方法,用戶也可以通過抵押幣來購買REX,不需要先解除自己的抵押到余額,這個方法就是在上面已經演示過的 unstaketorex 動作。該動作會分別減少賬戶的CPU資源抵押額: from_cpu,和NET資源抵押額: from_net,購買REX的總量是from_net + from_cpu。同時也要更新對應超級節點的投票數量。
(五)REX成熟期
購買REX后的賣出限制是4天,也就是說4天以后才可以賣出你的REX幣。根據不同的購買方式,token將被累計到不同的獨立的位置(稱作成熟桶)記錄,通過賬戶的rex_balance的rex_maturities字段。這些成熟桶們分別記錄着不同購買來源的REX距離可售賣的倒計時時間(稱作成熟期),例如4天,3天,2天...。已成熟的REX將沒有成熟期,可以隨時被賣出,這部分REX被存儲於賬戶的rex_balance的matured_rex字段,這種基於成熟期的延遲機制是為了給租賃市場反應時間。
- consolidate:該動作允許用戶合並所有的成熟桶以及已成熟的REX到一個新的成熟桶內,且該桶的初始成熟期4天重新生效。(為了減少那么多桶,看着麻煩)
evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex consolidate useraaaaaaaa
executed transaction: 7cc631030d159e21d6327253a5261b04bbb1f5fa73c5455975e616bc9f70c29f 104 bytes 415 us
# eosio <= eosio::consolidate {"owner":"useraaaaaaaa"}
warning: transaction executed locally, but may not be confirmed by the network yet ]
(六)REX儲蓄桶
正如上面提到的成熟桶,一個REX持有者可以利用一個特殊的桶,稱作儲蓄桶。REX在這個桶中永遠不能成熟,所以不可對外售出。
- mvtosavings:該動作可以讓用戶將其擁有的其他桶中購買的REX轉到儲蓄桶。
evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex mvtosavings useraaaaaaaa "10.0000 REX"
executed transaction: a93fe2254b236d470de8c1366de8eab9aed5230a75bef8b2868daee1003ca889 120 bytes 409 us
# eosio <= eosio::mvtosavings {"owner":"useraaaaaaaa","rex":"10.0000 REX"}
warning: transaction executed locally, but may not be confirmed by the network yet ]
- mvfromsavings:該動作可以讓用戶從儲蓄桶中移出REX幣到一個新的成熟桶內,且該桶的初始成熟期4天重新生效。
evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex mvfromsavings useraaaaaaaa "10.0000 REX"
executed transaction: 4357c4cac358b66b28e05953603228581f3689a7191bc987d9e26cef7e9b1edf 120 bytes 447 us
# eosio <= eosio::mvfrsavings {"owner":"useraaaaaaaa","rex":"10.0000 REX"}
warning: transaction executed locally, but may not be confirmed by the network yet ]
(七)賣出REX
- sellrex:允許用戶賣出指定數量的REX,換取相應的主幣。這相當於未出借的主幣,如果total_unlent中的主幣足夠用戶賣出的數量,則該筆訂單將被處理,否則訂單排隊,直到條件滿足。賣單的價格是在處理時間時決定的,而不是在訂單創建時間(假設這兩個時間是不同的)。當訂單被填寫時,該用戶的投票抵押金額也會更新為當前REX幣的值。下面測試該命令的使用方法,但由於REX成熟期限制,目前還沒辦法有效賣出REX。
evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex sellrex useraaaaaaaa "500.0000 REX"
Error 3050003: eosio_assert_message assertion failure
Error Details:
assertion failure with message: insufficient available rex
pending console output:
- cancelrexorder:在訂單被填寫之前,用戶可以通過這個動作取消一個正在排隊中的訂單。REX賣出的銷售所得將被添加到用戶的rex_fund中。下面測試該命令的使用方法,但由於目前沒有排隊訂單,所以提示無法成功執行訂單取消動作。
evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex cancelrexorder useraaaaaaaa
Error 3050003: eosio_assert_message assertion failure
Error Details:
assertion failure with message: no sellrex order is scheduled
pending console output:
(八)處理REX賣單
當訂單不能被填寫時,會被加入到一個隊列,就是創建一條rex_order記錄,該數據結構的字段包括:
-
owner, 訂單擁有者(PK)
-
rex_requested, 訂單要賣出多少的REX
-
order_time, 訂單時間,默認是當前時間current_time_point()
-
proceeds, 訂單收益,也就是主幣結算的數量,默認是“0.0000 SYS”
-
stake_change, 對抵押數量的影響,默認是“0.0000 SYS”
-
is_open, 訂單狀態是否開放,默認是true
-
函數fill_rex_order:用來填寫一個訂單,其中成功執行的條件是一些動作提供了足夠的未借出主幣,換句話講,賣出REX換來的主幣是從REX池中的主幣余量結算的,所以REX池要保證有足夠的未借出主幣。這個時候,設置is_open=flase,設置proceeds為rex_requested與當前REX與主幣的匯率計算的一個主幣收益值,然后計算抵押數量的改變stake_change,同時要更新用戶的REX余額,減去賣出的REX數量,rex_balance -= rex_requested,然后設置vote_stake為當前rex_balance的值,rex_pool的余額也相應改變。然后通過二級索引order_time以及is_open兩個條件,該訂單將被移到隊列的最末端。
/**
* @brief 執行一筆sellrex訂單並返回包含結果的對象
*
* 執行一筆剛進來或已在隊列中的訂單。如果REX池中已有足夠的未凍結在資源租賃的主幣,則成功填寫該訂單。
* 這種情況下,REX池總量,用戶的rex_balance以及用戶vote_stake字段都會被更新。然而,這個函數不更新
* 用戶的投票權利。函數返回成功標志,訂單收益,和投票抵押內容。這些將在不同的函數中使用到,用來完成
* 訂單處理,收益轉賬到用戶的REX基金並更新用戶的投票權重。
*
* @param bitr - 迭代器,直系想rex_balance數據庫記錄
* @param rex - 要被賣出的rex數量
*
* @return 結構體rex_order_outcome,包含成功標志位,訂單收益以及投票抵押更改
*/
rex_order_outcome system_contract::fill_rex_order( const rex_balance_table::const_iterator& bitr, const asset& rex )
{
auto rexitr = _rexpool.begin(); // rex池
const int64_t S0 = rexitr->total_lendable.amount; // total_lendable值
const int64_t R0 = rexitr->total_rex.amount; // total_rex值
const int64_t p = (uint128_t(rex.amount) * S0) / R0;
const int64_t R1 = R0 - rex.amount;
const int64_t S1 = S0 - p;
asset proceeds( p, core_symbol() ); // proceeds資產
asset stake_change( 0, core_symbol() ); // stake_change資產
bool success = false;
const int64_t unlent_lower_bound = ( uint128_t(2) * rexitr->total_lent.amount ) / 10;
const int64_t available_unlent = rexitr->total_unlent.amount - unlent_lower_bound; // available_unlent <= 0 is possible
if ( proceeds.amount <= available_unlent ) { // 余額充足
const int64_t init_vote_stake_amount = bitr->vote_stake.amount;
const int64_t current_stake_value = ( uint128_t(bitr->rex_balance.amount) * S0 ) / R0;
_rexpool.modify( rexitr, same_payer, [&]( auto& rt ) { // 修改rex池狀態表
rt.total_rex.amount = R1;
rt.total_lendable.amount = S1;
rt.total_unlent.amount = rt.total_lendable.amount - rt.total_lent.amount;
});
_rexbalance.modify( bitr, same_payer, [&]( auto& rb ) { // 修改_rexbalance狀態表
rb.vote_stake.amount = current_stake_value - proceeds.amount;
rb.rex_balance.amount -= rex.amount;
rb.matured_rex -= rex.amount;
});
stake_change.amount = bitr->vote_stake.amount - init_vote_stake_amount;
success = true;
} else {
proceeds.amount = 0;
}
return { success, proceeds, stake_change };
}
- 函數update_rex_account:其余rex_order的處理必須由owner執行一個動作,這涉及到將proceeds按要求打入用戶的rex_fund,另外通過增加stake_change(正負均可)到voter_info的staked字段來更新用戶的投票權重,同時對應的超級節點的選票也會更新,到此就全部執行完畢,刪除該訂單。
/**
* @brief 執行用戶已填寫的sellrex訂單並更新投票權重
*
* 檢查用戶是否有在隊列內的已填寫sellrex訂單,執行它然后刪除它。執行時要將訂單收益轉賬給
* 用戶的REX基金以及更新用戶的投票權重。
*
* @param owner - owner EOS賬戶
* @param proceeds - 額外收益,轉給owner的REX基金
* @param delta_stake - 額外抵押,加到owner的投票權重
* @param force_vote_update - 設為true的時候,投票權重被更新即使沒變化
*
* @return asset - 如果存在訂單,owner未填寫的賣單的REX數量
*/
asset system_contract::update_rex_account( const name& owner, const asset& proceeds, const asset& delta_stake, bool force_vote_update )
{
asset to_fund( proceeds );
asset to_stake( delta_stake );
asset rex_in_sell_order( 0, rex_symbol );
auto itr = _rexorders.find( owner.value );
if ( itr != _rexorders.end() ) { // 找到已存在的訂單
if ( itr->is_open ) { //訂單開放狀態,修改賣單價格
rex_in_sell_order.amount = itr->rex_requested.amount;
} else { // 未開放則添加至已有訂單
to_fund.amount += itr->proceeds.amount;
to_stake.amount += itr->stake_change.amount;
_rexorders.erase( itr );
}
}
if ( to_fund.amount > 0 )
transfer_to_fund( owner, to_fund );
if ( force_vote_update || to_stake.amount != 0 )
update_voting_power( owner, to_stake ); // 更新投票權重
return rex_in_sell_order;
}
一個用戶可以只有一個開放的rex_order。如果該賬戶有執行了一個新的sellrex動作,這筆訂單不會被立即填寫,請求賣出的REX數量會被添加到已存在的訂單的rex_requested字段中去,相當於在有開放訂單的狀態下,新訂單會更新該開放訂單,不必冗余生成新的,節約了訂單量。
(九)REX租賃
REX租賃就是通過REX來租賃資源,包括CPU,NET資源。
- rentcpu:一個用戶可以作為reveiver通過rentcpu動作獲得對應支付主幣數量的CPU資源。該動作將創建一條rex_loan記錄在cpuloan狀態表中。至於用戶獲得的資源的數量,是通過bancor算法計算出當前市場價格,按照支付的主幣數量進行核算,加到用戶的資源抵押額中,同時該值也會記錄在rex_loan的total_staked字段。
evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex rentcpu useraaaaaaaa useraaaaaaaa "3.0000 SYS" "1.0000 SYS"
Error 3050003: eosio_assert_message assertion failure
Error Details:
assertion failure with message: loan price does not favor renting
pending console output:
- rentnet:一個用戶可以作為reveiver通過rentnet動作獲得對應支付主幣數量的NET資源。該動作將創建一條rex_loan記錄在netloan狀態表中。至於用戶獲得的資源的數量,是通過bancor算法計算出當前市場價格,按照支付的主幣數量進行核算,加到用戶的資源抵押額中,同時該值也會記錄在rex_loan的total_staked字段。
evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex rentnet useraaaaaaaa useraaaaaaaa "3.0000 SYS" "1.0000 SYS"
Error 3050003: eosio_assert_message assertion failure
Error Details:
assertion failure with message: loan price does not favor renting
pending console output:
錯誤分析
這兩個動作的調用都報出了“loan price does not favor renting”的錯誤,在源碼中尋找該錯誤的解釋。
int64_t rented_tokens = get_bancor_output( pool->total_rent.amount, pool->total_unlent.amount, payment.amount );
check( payment.amount < rented_tokens, "loan price does not favor renting" );
payment即第三個參數,是指用戶支付的主幣金額,從rex_fund余額中划出。而rented_tokens變量是通過bancor算法得到的一個已出租token的值,該值是由REX池中的是total_unlent和total_rent來決定,即bancor算法的兩個連接器,或者說兩個條件值是total_unlent和total_rent,一個是REX池中所有的未出借主幣數量,一個是已出借主幣數量。計算得到的total_staked的金額是從total_unlent轉入到total_rent的數量,並且支付的主幣也會被添加到total_rent。REX資源租賃被創建后,支付的主幣會被添加到REX池的total_lendable,同時total_unlent因此被增加了REX幣以及增加了可供租借的主幣。資源貸款期限是30天,到期時會從receiver的資源中減去對應的抵押金額total_staked。total_staked會從total_lent遷回到total_unlent,total_lent會根據Bancor相應更新。
所以分析上面無法調通rentcpu以及rentnet的原因是rented_token在目前的環境下太低所致,bancor市場沒有建立起來,也就是可租借額度很低,我們買不到資源,所以要提高REX池的可租借主幣的額度。下面是get_bancor_output函數的計算方式。
/**
* 該函數通過給定的兩個連接器余額,以及一個輸入的金額,使用Bancor算法計算出結果。
*
* @param in - 輸入的金額
* @param conin - 輸入連接器的余額
* @param conout - 輸出連接器的余額
*
* @return int64_t - 轉換輸出金額
*/
int64_t get_bancor_output( int64_t conin, int64_t conout, int64_t in )
{
const double F0 = double(conin);
const double T0 = double(conout);
const double I = double(in);
auto out = int64_t((I*T0) / (I+F0)); // 公式
if ( out < 0 ) out = 0;
return out;
}
(十)租賃自動更新
在rentcpu和rentnet動作中,用戶均可提供一個額外的主幣金額增加到租賃balance字段,在到期日,如果有足夠的基金該筆租賃單子可以被重新恢復,即balance >= payment,否則租賃關閉,退還用戶所有仍在租賃balance中的主幣。如果一筆租賃單子被重新恢復了,total_staked會使用當前市場價和receiver收到影響而更新的資源限制重新計算。REX池的余額也會被更新。一個租賃單子(由rentcpu和rentnet產生)的擁有者可以投資一筆租賃,以loan_num作為id鑒別,使用動作fundcpuloan和fundnetloan。owner也可以從loan余額中提取,使用動作defundcpuloan和defundnetloan。
evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex fundcpuloan useraaaaaaaa 1 "1.0000 SYS"
Error 3050003: eosio_assert_message assertion failure
Error Details:
assertion failure with message: loan not found
pending console output:
evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex fundnetloan useraaaaaaaa 1 "1.0000 SYS"
Error 3050003: eosio_assert_message assertion failure
Error Details:
assertion failure with message: loan not found
pending console output:
evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex defundcpuloan useraaaaaaaa 1 "1.0000 SYS"
Error 3050003: eosio_assert_message assertion failure
Error Details:
assertion failure with message: loan not found
pending console output:
evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex defundnetloan useraaaaaaaa 1 "1.0000 SYS"
Error 3050003: eosio_assert_message assertion failure
Error Details:
assertion failure with message: loan not found
pending console output:
由於前面未成功生成loan記錄,所以無法得到有效的loan_num對應的記錄,這4個動作都依賴該loan記錄,所以無法成功執行。
查詢loan租賃記錄的方式:
cleos get table eosio eosio cpuloan --index 3 --key-type name -L strarteosfee -U strarteosfee
(十一)REX維護
在大部分的REX動作中,都是調用runrex函數。它啟動固定的2筆賣單的進程(默認為2),計算着他們的CPU以及NET租賃的過期時間,執行着上面描述的REX賣單成熟以及租賃到期的工作。
- rexexec:任何賬戶都能通過執行rexexec動作直接調用runrex函數,該函數拿到輸入的最大訂單量,網絡租賃以及待處理的CPU租賃,REX賣單會比租賃享受更高的優先級。這意味着當rex_order隊列不為空時,沒有新的租賃訂單會被創建,也沒有已存在的租賃訂單被重新恢復。
evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex rexexec useraaaaaaaa 2
executed transaction: 5e85255c4dd3e6ed6c98b73eafbc26ed84d4cf05b0c91c2e47e2b447a5653edd 104 bytes 280 us
# eosio <= eosio::rexexec {"user":"useraaaaaaaa","max":2}
warning: transaction executed locally, but may not be confirmed by the network yet ]
- updaterex:該動作通過當前REX余額的主幣價值更新了一個用戶的投票抵押。也更新了該用戶的投票對象的投票權重。
evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex updaterex useraaaaaaaa
executed transaction: e3dc3814dacb85313a76683217b023b010a7c19334f6e67489a2b8c0e94f616b 104 bytes 900 us
# eosio <= eosio::updaterex {"owner":"useraaaaaaaa"}
warning: transaction executed locally, but may not be confirmed by the network yet ]
- closerex:該動作從所有REX相關的狀態表中刪除了一個用戶的記錄並且釋放了已使用的內存資源。如果該用戶的REX余額不是0,該動作失敗,否則,該用戶的rex_balance值將被刪除。如果該用戶沒有額外的租賃訂單並且rex_fund為0,刪除其rex_fund字段。下面測試該命令的使用方法,執行響應報錯表示,必須用戶的REX余額為0才可以被刪除,所有需要賣光所有REX幣,否則該動作失敗。
evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex closerex useraaaaaaaa
Error 3050003: eosio_assert_message assertion failure
Error Details:
assertion failure with message: account has remaining REX balance, must sell first
pending console output:
(十二)投票需求
重申一下REX系統關於投票的硬性要求,就是所有持有REX的賬戶必須直接或者通過代理參與了為超級節點的投票。
總結
本文詳盡地介紹了REX系統的內容。REX是2019年以來EOS最新的重大功能發布,該項目由BM牽頭提出核心算法分析,繼而由blockone公司開發相關功能。本文從核心的bancor算法分析,到具體的命令,包括deposit,buyrex,sellrex,rentcpu,rentnet,closerex等一系列REX動作的分析與實踐,及時同步了EOS的最新動作。