Conflux 內建合約功能介紹

Conflux中文社群發表於2022-01-19

Conflux引入了一些內建的內部合約,以便更好地進行系統維護和鏈上治理。而本文件將介紹如何使用這些內建合約。

idtitlecustom_edit_urlkeywords
internal_contractInternal Contracthttps://github.com/Conflux-Ch...conflux ,contract

(重要:在Tethys主網中該介面會被變更。該文件已與最新版本同步。)

Conflux引入了一些內嵌的合約,以便更好的進行系統維護及鏈上治理。目前Conflux含有三種內建合約:AdminControl 合約,SponsorWhitelistControl 合約以及 Staking 合約。

這些合約提供的solidity功能性api在此處定義。

這些功能只能通過呼叫 CALLSTATICCALL 操作進行呼叫。使用操作 CALLCODEDELEGATECALL 會觸發錯誤。

這三個內建合約的地址如下所示:

  • AdminControl: 0x0888000000000000000000000000000000000000
  • SponsorWhitelistControl: 0x0888000000000000000000000000000000000001
  • Staking: 0x0888000000000000000000000000000000000002

本文中的所有樣例程式碼會使用js-conflux-sdk進行開發。

AdminControl合約

總覽

AdminControl 合約是一款用於合約開發的除錯工具。 在交易過程中建立合約時,當前交易的傳送者會自動成為合約的管理者(admin)。

管理者 admin 的地址可以通過呼叫介面 setAdmin(address contractAddr, address newAdmin) 將管理權益轉交給其他的普通使用者地址零地址。而一個合約不可成為管理者。

合約的管理者具備多個管理許可權。管理者可以呼叫 destroy(address contractAddr) 介面以銷燬合約,該操作就像通過合約呼叫 suicide() 函式那樣。而SponsorWhitelist內部合約提供了一些管理員專用的函式。這些函式可以更新贊助者機制中的白名單。我們將在隨後進行介紹。

注意:對於所有和管理者許可權相關的介面,不論呼叫成功與否都不會在執行時觸發任何錯誤或異常。 例如,如果一個非管理者地址嘗試將管理者地址轉移給其自己,該交易會成功但不會造成任何改動。

如果合約擁有非零的管理者地址,ConfluxScan會將合約標記為除錯模式。因此請記住,如果你認為你的合約已經準備好進入實際生產環境,你應當將管理者地址設定為零地址。

AdminControl 合約同時也提供了一個可以被任何人呼叫的查詢介面 getAdmin(address contractAddr)

需要注意的細節:

  1. 預設管理者(交易傳送者)是在合約開始建立時設定的。因此,如果傳送者 A 建立合約 B 並在合約構建時設定管理者為 C ,在合約部署後合約的管理者為 C
  2. 然而,如果傳送者 A 呼叫合約 B ,隨後合約 B 建立合約 C 並在合約建立時將管理者設定為 D ,則該設定會失敗,原因是: C 合約的管理者是 A ,但建立合約 C 的發起者是 B
  3. Conflux會引入一種特別的規則。在案例2中,如果 D 是零地址,則管理者設定成功。這意味著合約建立時可以顯示地宣告其不需要管理者。

樣例

考慮到您可以已經部署了一個地址為 contract_addr 的合約。管理者可以通過呼叫AdminControl.setAdmin(contract_addr, new_admin) 以變更管理員以及通過呼叫AdminControl.destroy(contract_addr) 來銷燬合約。

const PRIVATE_KEY = '0xxxxxxx';
const cfx = new Conflux({
  url: 'http://test.confluxrpc.org',
  logger: console,
});
const account = cfx.wallet.addPrivateKey(PRIVATE_KEY); // create account instance

const admin_contract = cfx.InternalContract('AdminControl')
// to change administrator
admin_contract.setAdmin(contract_addr, new_admin).sendTransaction({
  from: account,
}).confirmed();

// to kill the contract
admin_contract.destroy(contract_addr).sendTransaction({
  from: account,
}).confirmed();

SponsorWhitelistControl合約

總覽

Conflux實現了一種贊助機制來補貼使用者對智慧合約的使用。 因此,只要對合約呼叫的交易被贊助(通常由Dapps的運營商贊助),使用餘額為零的新帳戶也能夠呼叫智慧合約。 通過引入內建的SponsorControl合約能夠記錄和管理智慧合約的贊助資訊。

在進行子呼叫(Message Call)時,Conflux不會再次檢查贊助情況。例如,如果一個普通使用者地址 A 呼叫合約 B ,然後合約 B 呼叫合約 C, Conflux僅僅會檢查 A 是否被合約 B 贊助。如果 A 被贊助,B 會在交易執行過程中負擔全部的燃料費用和/或儲存抵押費用,包含 B 呼叫 C 的費用。換句話說,只有交易的傳送者才能被贊助,B 不可能被贊助。

SponsorControl合約為每一個使用者建立的合約保留了如下的資訊:

  • sponsor_for_gas :是提供燃料補貼的賬戶;
  • sponsor_for_collateral :是提供儲存抵押補貼的賬戶;
  • sponsor_balance_for_gas :可用於燃料補貼的餘額;
  • sponsor_balance_for_collateral : 可用於提供儲存抵押補貼的餘額;
  • sponsor_limit_for_gas_fee :是向每筆交易贊助燃料費用的上界;
  • whitelist :有資格獲得補貼的普通使用者地址列表,全零地址則代表所有使用者地址。只有合約自身和管理員有許可權改動該列表。

有兩種資源能夠被贊助:燃料費用和儲存抵押物。

  • *對於燃料費用: 如果一筆交易使用非空的 sponsor_for_gas 呼叫智慧合約且交易傳送者處於合約的 whitelist 列表內,且交易指定的燃料費用在 sponsor_limit_for_gas_fee 範圍內,交易的燃料消耗將從合約的 sponsor_balance_for_gas 中支付(如果足夠的話),而不是由交易傳送者的賬戶餘額支付,如果 sponsor_balance_for_gas 無法承擔燃料消耗,則交易失敗。否則,交易傳送者應支付燃料費用。
  • 對於儲存抵押物: 如果一筆交易使用非空的 sponsor_balance_for_collateral 呼叫智慧合約且交易傳送者處於合約的 whitelist 列表內,在執行交易的過程中儲存抵押物將從智慧合約的 sponsor_balance_for_collateral 中扣除,並將這些修改後的儲存條目所有者相應設定為合約地址。 否則,交易傳送方應在執行過程中支付儲存抵押物。

當一個合約被建立的時候,它的 sponsor_for_gassponsor_for_collateral 會被置為零地址,相應的燃料補貼餘額也是零。 提供燃料補貼的賬戶和儲存押金補貼的賬戶都可以通過與 SponsorControl 合約互動完成。合約當前的贊助賬戶可以直接追加補貼餘額,也可以在滿足一定條件下提高 sponsor_limit_for_gas_fee。其他普通使用者賬戶如果提供高於當前餘額的資金,可以將原先的贊助者取而代之。更具體的細節如下。

贊助者替換

為了替換合約的 sponsor_for_gas ,新的贊助者需要呼叫函式 setSponsorForGas(address contractAddr, uint upperBound) 並向內建合約轉移一筆資金。只有在滿足下述條件時才能完成燃料費用贊助者的替換:

  1. 轉移的資金應當比合約當前的 sponsor_balance_for_gas 高。
  2. sponsor_limit_for_gas_fee 的新值(被指定為 upperBound 引數)應當不小於原贊助者的限制,除非原本的 sponsor_balance_for_gas 無法負擔原贊助者的限制。
  3. 轉移的資金應為新限額的1000倍以上,以便足以補貼至少 1000 次呼叫合約的交易。

如果上述條件滿足,剩餘的 sponsor_balance_for_gas 會返還給原贊助人 sponsor_for_gas ,隨後轉給內建合約的資金被注入 sponsor_balance_for_gassponsor_for_gas 以及 sponsor_limit_for_gas_fee 會根據新贊助人指定的值進行更新。如果上述條件沒有滿足,會觸發異常。

sponsor_for_collateral 的替換與替換燃料贊助者的方式類似,處理沒有燃料限額的邏輯。方法是 setSponsorForCollateral(address contractAddr) 。新的贊助者需要向合約轉移一筆比目前餘額更多的資金。隨後當前 sponsor_for_collateral 贊助的金額會被全部退還,即 sponsor_balance_for_collateral 和合約當前所有儲存抵押金之和,兩個與儲存押金贊助相關的欄位將按照新贊助者的要求進行相應的變更。

一個合約賬戶也被允許成為贊助者。

提高贊助額度

贊助者可以在無需更換贊助者的情況下提供額外的贊助資金。在該情況下,贊助人需要呼叫函式 setSponsorForGas(address contractAddr, uint upperBound)setSponsorForCollateral(address contractAddr) 並滿足贊助者替換的三條要求中的條件 2,3. 如果滿足相關需求,交易的贊助資金會被加入贊助餘額中且 sponsor_limit_for_gas_fee 也會相應地被更新。

白名單列表維護

只有合約本身或合約管理者可以更新合約白名單列表。贊助者則無權變更白名單列表。

合約可以通過呼叫 addPrivilege(address[] memory) 將任何地址加入到白名單列表中。這意味著如果 sponsor_for_gas 被設定,合約會為白名單列表中的賬戶支付儲存押金。全零地址是一個特殊的地址 0x0000000000000000000000000000000000000000 。如果該地址被加入白名單列表,則所有呼叫該合約的交易都會被贊助。合約還可以呼叫方法 removePrivilege(address[] memory) 將部分正常賬戶從白名單列表中移除。移除一個不存在的地址不會導致錯誤或異常。

需要注意的細節:

  1. 一個合約地址也可被加入到白名單列表中,但該操作無任何意義,因為只有交易的傳送者可以被贊助。

合約的管理者可以使用 addPrivilegeByAdmin(address contractAddr, address[] memory addresses)removePrivilegeByAdmin(address contractAddr, address[] memory addresses) 以維護白名單列表。

樣例

假定你有一個如下所示的簡單合約。

pragma solidity >=0.4.15;

import "https://github.com/Conflux-Chain/conflux-rust/blob/master/internal_contract/contracts/SponsorWhitelistControl.sol";

contract CommissionPrivilegeTest {
    mapping(uint => uint) public ss;

    function add(address account) public payable {
        SponsorWhitelistControl cpc = SponsorWhitelistControl(0x0888000000000000000000000000000000000001);
        address[] memory a = new address[](1);
        a[0] = account;
        cpc.addPrivilege(a);
    }

    function remove(address account) public payable {
        SponsorWhitelistControl cpc = SponsorWhitelistControl(0x0888000000000000000000000000000000000001);
        address[] memory a = new address[](1);
        a[0] = account;
        cpc.removePrivilege(a);
    }

    function foo() public payable {
    }

    function par_add(uint start, uint end) public payable {
        for (uint i = start; i < end; i++) {
            ss[i] = 1;
        }
    }
}

部署合約且地址為 contract_addr 如果有些人希望贊助燃料費用,他/她可以傳送如下的交易:

const PRIVATE_KEY = '0xxxxxxx';
const cfx = new Conflux({
  url: 'http://test.confluxrpc.org',
  logger: console,
});
const account = cfx.wallet.addPrivateKey(PRIVATE_KEY); // create account instance

const sponsor_contract = cfx.InternalContract('SponsorWhitelistControl');
sponsor_contract.setSponsorForGas(contract_addr, your_upper_bound).sendTransaction({
  from: account,
  value: your_sponsor_value
}).confirmed();

如果需要贊助儲存抵押物,可以簡單的將 setSponsorForGas(contract_addr, your_upper_bound) 替代為 setSponsorForCollateral(contract_addr) 即可。

之後你可以使用 addPrivilegeremovePrivilege 為你的合約維護 whitelist 。特殊的全零地址 0x0000000000000000000000000000000000000000 則表示 所有人都處於 whitelist 中。需要謹慎使用。

you_contract.add(white_list_addr).sendTransaction({
  from: account,
})

you_contract.remove(white_list_addr).sendTransaction({
  from: account,
})

隨後在 whitelist 中的賬戶在呼叫 you_contract.foo()you_contract.par_add(1, 10) 時,不會支付任何費用。

Staking合約

總覽

Conflux引入權益質押機制的原因有兩個:一、權益機制提供了一種對佔用儲存空間更好的收費方式(相比於“一次付費,永久佔用”)。二、該機制還有助於定義分散治理中的投票權。

在頂層,Conflux實現了一個內建的Staking合約,以記錄所有賬戶的權益資訊。通過向該合約傳送交易,使用者(包括外部使用者和智慧合約)可以存入/提取資金,也被稱為合約內的權益。質押資金的利息在提款時發放,其數量取決於提款金額和質押時長。

使用者可以通過呼叫 deposit(uint amount) 來存入用於抵押的金額,隨後 amount 數量的資金將從其 balance 移至 stakingBalance. 需要注意的是該函式不是 payable 的,使用者只需要指定抵押的金額而無需將資金轉入到內部合約中。

使用者還可以通過呼叫 withdraw(uint amount) 來提取餘額。呼叫者可以呼叫該函式從Conflux內嵌質押合約提取部分代幣。這會觸發利息結算。抵押資金和利息將會及時的轉入使用者餘額中。所有提款的順序將按照先到先服務的方式進行處理。

利率

目前的年化利率為4.08%。 複利是以區塊的顆粒度來實現的。

如果在執行區塊 B 中的交易時,嘗試提取 價值 v 並且在區塊 B' 中抵押的資金,其利息計算公式如下:

interest issued = v * (1 + 4% / 63072000)^T - v

其中 T = BlockNo(B)−BlockNo(B') 是以區塊數目衡量的質押時長,而 63072000 是在區塊生成時間為 0.5 秒前提下 365 天生成區塊數目的期望值。

鎖定與投票權

通過鎖定質押餘額,使用者可獲取投票權以進一步進行鏈上治理。通過呼叫 voteLock(uint amount, uint unlock_block_number) 函式,一個帳戶可以做出如下承諾:“我的 stakingBalance 在未來 unlock_block_number 中將始終具有至少 amount 的資金”。單個賬戶可以做出多個承諾,比如說“今年我將至少持有10CFX,並且在明年至少持有5CFX。” 一旦做出承諾,無法取消!但是該賬戶可通過鎖定更多的金額覆蓋原有的承諾。每當賬戶嘗試提取 stakingBalance 時,內部合約會檢查剩餘餘額是否與鎖定承諾吻合。

在此處我們將通過幾個樣例介紹鎖定餘額的邏輯細節。假設Conflux在今年的剩餘時間將產生 x 個區塊,在明年的產生 y 個區塊。由於Conflux網路每秒能生成兩個區塊,因此 y 近似等於 2 * 60 * 60 * 24 * 365 。而 x 取決於您何時閱讀到該文章。

  1. 假設一個賬戶的 stakingBalance 中有10CFX,如果其呼叫 voteLock(100 * 10^18, x) ,說明賬戶嘗試鎖定 100CFX. 但由於其缺少足夠的 stakingBalance,交易執行失敗。
  2. 然而,如果該賬戶呼叫 voteLock(8 * 10^18, x) ,則交易會成功。
  3. 隨後,如果該賬戶呼叫 voteLock(6 * 10^18, x+y),交易同樣會成功。這意味著交易執行後 2CFX 在今年結束時解鎖,而另外 6CFX 會被鎖定直到明年結束。
  4. 如果賬戶再呼叫 voteLock(0, x),沒有任何事情會發生。在交易執行過程中交易不會觸發錯誤。內建合約會將此呼叫視為無意義的承諾:該帳戶在步驟 2、3 做出的舊承諾有效的前提下,再次承諾在今年結束前至少鎖定 0 CFX.
  5. 如果該賬戶呼叫 voteLock(9 * 10^18, x+y),則兩個較老的承諾將會因為“鎖定 9CFX 直到明年結束是一個更強的承諾”被覆蓋。

鎖定對利息無任何影響。當賬戶成功取出抵押餘額時,利息會將照常計算。

在任何時間,每一個鎖定的金額將根據其解鎖時間被分配0到1的表決權。鎖定期超過1年的部分將擁有全額的投票權利。檢視 Conflux Protocol Specification 章節 8.3.2 獲取更多資訊。

樣例

const PRIVATE_KEY = '0xxxxxxx';
const cfx = new Conflux({
  url: 'http://test.confluxrpc.org',
  logger: console,
});
const account = cfx.wallet.addPrivateKey(PRIVATE_KEY); // create account instance

const staking_contract = cfx.InternalContract('Staking');
// deposit some amount of tokens
staking_contract.deposit(your_number_of_tokens).sendTransaction({
  from: account,
}).confirmed();

// withdraw some amount of tokens
staking_contract.withdraw(your_number_of_tokens).sendTransaction({
  from: account,
}).confirmed();

// lock some tokens until some block number
staking_contract.voteLock(your_number_of_tokens, your_unlock_block_number).sendTransaction({
  from: account,
}).confirmed();

相關資料庫:

相關文章