Conflux的儲存抵押機制

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

背景介紹

近期有社群的小夥伴在開發智慧合約並部署到Conflux網路時遇到了關於給智慧合約傳送互動時進行抵押一部分cfx,並在互動後返還一部分cfx的問題

正如烤仔回答的那樣,在區塊鏈中由於需要根據呼叫者對智慧合約空間佔用的情況收取一定數量的儲存押金,這筆費用通常高過實際的需要,當空間釋放時,就會返回給操作者一定數量的GAS費用。

Conflux在其協議規範中專門對這一機制進行了詳細的介紹(參見第28頁)。

理論介紹

在Conflux中引入了Collateral for storage(簡稱CFS)機制,作為使用儲存的定價方式,相比Ethereum中的一次性儲存費用,CFS機制會更加公平合理。原則上,這種機制需要鎖定一筆資金,作為佔用儲存空間的抵押物。在相應的儲存空間被釋放或被他人覆蓋前,抵押物都會被鎖定,而被鎖定的抵押物所產生的相應利息會直接分配給礦工,用於儲存空間的維護。因此,Conflux的儲存成本也取決於空間佔用的時間長短。

在Conflux網路中,每個儲存條目佔用空間是64B(B為Bytes,位元組),這也是世界狀態下鍵/值對的大小,需要說明的是在區塊鏈中鍵一般為256bits長,值也是256bits長(各自都是32B長,合起來為64B長)。儲存所需的押金與能夠覆蓋所有儲存物品的64B的最小倍數成正比。對於每一個儲存條目,最後向該條目寫入的賬戶稱為該儲存條目的所有者。如果某儲存條目是在執行合約C時所寫,且有擔保人提供擔保,那麼C被視為該條目的寫者,也相應地成為所有者(詳見7.1節)。在世界狀態下,一個儲存條目的整個生命週期內,該條目的所有者必須鎖定固定數量的CFX作為佔用儲存空間的儲存押金。具體來說,每一個大小為64B的儲存條目,其主人會被鎖定1/16CFX。而佔用1KB空間則支付1CFX作為押金,其對應公式如下:

$$\left(\frac{1024}{64}\right)×\left(\frac{1}{16}\right)=1(CFX)$$

在賬戶α成為一個儲存條目的所有者時(無論是建立還是修改),α應立即為該條目鎖定1/16 CFX。如果α有足夠的餘額,那麼就會自動鎖定所需的押金,否則如果α沒有足夠的餘額,操作就會失敗,α無法建立或修改該條目。

當一個儲存條目從世界狀態中被刪除時,相應的1/16 CFX押金將被解鎖並返回到該條目所有者的餘額中。如果一個儲存條目的所有權發生變化,舊所有者的1/16 CFX押金被解鎖,而新的所有者必須同時鎖定1/16 CFX作為押金。

為了方便處理,Conflux中引入了函式CFS,它將一個帳戶地址α和一個世界狀態σ作為輸入,並返回 在世界狀態σ下,賬戶α儲存的鎖定押金總額。 如果世界狀態σ從上下文中明確,為了簡潔起見,我們用CFS(α)代替CFS(α;σ),其公式如下:

$$CFS(α)≡CFS(α;σ)≡賬戶a在世界狀態σ下擁有的儲存條目總數×\left(\frac{1}{16}\right)(CFX)$$

特別的,對於一個由α=S(T)傳送的交易T(或α=Tα如果T呼叫的是地址Tα處的贊助合約),令σ為T執行前後的世界狀態,σ'是交易執行結束後的世界狀態,針對儲存限制欄位Tl有一個CFS(α;σ)≤CFS(α;σ)+Tl/1018的斷言。

關鍵點:想弄清楚呼叫合約質押的CFX有多少,一定要弄清楚合約中變數的條目數,以及在通過函式呼叫合約進行操作時有多少條目被修改,且改動的數目被記錄到了區塊鏈中!

Solidity記憶體管理機制

根據Solidity文件對於其記憶體管理的描述及Conflux儲存押金機制,我們能夠發現,合約儲存需要有key和value進行維護,一般情況下:key的長度為256bits,value的長度同樣為256bits,按一個智慧合約的儲存空間按照如下表格進行組織,其中{0,1}256表示256位位元串(位元串中只有0或1兩個值),每個key/value對就可以被理解為一個條目

條目鍵/地址({0,1}256)值({0,1}256)
10...000000
20...000011
30...000022
.........
2256f...fffff0

由於256bits=32bytes,兩個256bits對應的長度為:
$$32+32=64(Bytes)$$

Solidity中常見變數及其對應的條目數整理

變數長度定義方式
普通變數1個普通變數對應1個條目uint public count=0;
mappingmapping的每1個key都對應於1個條目mapping(address => uint) public balances;
array陣列每1個元素對應於1個條目,陣列長arr.length是額外的1個條目uint[5] fixedArr = [1,2,3,4,5]; string productname;
structstruct內每個field對應條目數的累加struct Person {uint age;uint stuID;string name;}

Conflux的儲存押金機制描述

Conflux 的網路中,儲存押金的費用是每 1024 位元組 1 CFX。由於每個條目佔用 64 位元組,因此,每個條目的押金費用就是 1/16 CFX. 每筆交易執行期間,新產生的押金費用會在交易執行結束的時統一收取。如果一個儲存條目被其他人改寫了,改寫的人將繳納儲存押金,而原先的押金繳納者將得到退回的押金。值得一提的是,押金退回是“悄悄”加在餘額裡的,並沒有轉賬交易可供查詢。

Conflux 的每筆交易中,需要填寫一個儲存上限(單位是位元組)。該上限規定了,押金繳納者在交易執行前後押金增量不得超過儲存上限乘 1/1024 CFX. 如果這個值填寫得過低,會導致執行後押金超過上限,執行失敗。如果填寫的過高,導致傳送者餘額不足以支付押金,也會導致交易失敗。

部署合約

請參考連結,嘗試部署和呼叫智慧合約。

例項講解

1. 一個含有1個普通uint變數的例子

智慧合約程式碼如下:

pragma solidity ^0.5.0;

contract Counter {
    uint public count=0;
    event SelfEvent(address indexed sender, uint current);

    constructor() public {
    }

    function inc(uint num) public returns (uint){
        return count += num;
    }

    function self() public {
        emit SelfEvent(msg.sender, count);
    }
}

由於該智慧合約中只有 uint public count=0; 一個變數,只對應於1個條目,按照之前的公式分析:
$$CFS(α)≡CFS(α;σ)≡賬戶a在世界狀態σ下擁有的儲存條目總數×\left(\frac{1}{16}\right)(CFX)$$

正是由於 uint public count=0 變數正好對應於一個64B的條目,結合所以之前我們給定的例項,真正質押的數額為:0.0625 CFX
$$\left(\frac{1}{16}\right)=0.0625(CFX)$$

下面結合對合約的實際呼叫進行驗證:
呼叫合約程式碼的案例如下(檔名為:call_calc.js):

const { Conflux, util } = require('js-conflux-sdk');
// 這個地址是上面列印出來的 receipt.contractCreated 
const public_address = '0x17b38613e633c2b8fb4686a3a62b9b782ac5e0ca';
const contractAddress = '0x845dd6f64bb3d2771a8f30dc85bb14f5ac26b75e';
const PRIVATE_KEY1 = '0x2772b19636f1d183a9a2a0d27da2a1d0efb97637b425********************';
const PRIVATE_KEY2 = '0x2adba218d5eacb5bc9bbb4c6fdecef7d1719c8184812********************';
const compiled = require(`./build/Counter.json`)
async function main() {
  const cfx = new Conflux({
    url: 'http://main.confluxrpc.org',
  });
  const contract = cfx.Contract({
    address : contractAddress,
    abi: compiled.abi,
  });
  
  const before_call_balance = await cfx.getBalance(public_address);
  console.log("before account1 call the current drip:"+before_call_balance.toString());
  console.log("before account1 call the current cfx:"+util.unit.fromDripToCFX(before_call_balance));
  
  let inc = await contract.inc(10);
  console.log("輸出:"  + inc.toString());
  
  const account1 = cfx.Account(PRIVATE_KEY1);//使用私鑰建立賬戶
  
  // 進行記錄並花費CFX
  await contract.inc(10).sendTransaction({ from: account1 }).confirmed();
  
  const after_call_balance = await cfx.getBalance(public_address);
  console.log("after account1 call inc(10) the current drip:"+after_call_balance.toString());
  console.log("after account1 call inc(10) the current cfx:"+util.unit.fromDripToCFX(after_call_balance));
    
  //建立account2,並嘗試呼叫合約希望釋放account1的cfx
  const account2 = cfx.Account(PRIVATE_KEY2);//使用私鑰建立賬戶
  
  const before_account2_call_balance = await cfx.getBalance(public_address);
  console.log("before account2 call inc(5) the current drip:"+after_call_balance.toString());
  console.log("before account2 call inc(5) the current cfx:"+util.unit.fromDripToCFX(after_call_balance));
  await contract.inc(5).sendTransaction({ from: account2 }).confirmed();
  
  const after_account2_call_balance = await cfx.getBalance(public_address);
  console.log("after account2 call inc(10) the current drip:"+after_account2_call_balance.toString());
  console.log("after account2 call inc(10) the current cfx:"+util.unit.fromDripToCFX(after_account2_call_balance));
  
  
}
main().catch(e => console.error(e));

呼叫方式為:

node call_calc.js

為了方便描述,將參與呼叫合約的賬戶用account1和account2進行表示,被呼叫的合約用contract進行表示,將其賬戶資訊和對應的賬戶地址進行彙總 ,所列表格如下:

賬戶名賬戶地址
account10x17b38613e633c2b8fb4686a3a62b9b782ac5e0ca
account20x1941E3137aDDf02514cBFeC292710463d41e8196
countract0x845dd6f64bb3d2771a8f30dc85bb14f5ac26b75e

為方便表示,後文使用上表中賬戶名指代各賬戶

在呼叫前,使用confluxscan查account1對應的CFX餘額,發現餘額為:1994.680912261955354268 CFX。

confluxscan1

(1)使用account1進行第一次合約呼叫:

call

由於account1呼叫合約佔用了空間,需要上交CFX作為押金

在程式啟動後:首先顯示account1的賬戶餘額:1994.680912261955383167 CFX。

程式會使用account1呼叫contract.inc(10)向合約發起互動,呼叫完成後發現account1的賬戶餘額會變為:1994.618412261955356217 CFX。

sub_call1

也就是說經過account1與合約contract的這一次互動操作,其賬戶扣除了:
$$1994.680912261955383167-1994.618412261955356217=0.06250000000002695(CFX)‬$$

這說明由於呼叫contract.inc(10)與合約進行互動。account1上交了0.06250000000002695的CFX。

使用account2呼叫合約,以幫助account1釋放空間

程式會繼續執行,並使用account2通過呼叫contract.inc(5)向合約發起互動

在呼叫前,account1的賬戶餘額為:1994.618412261955356217 CFX,與步驟(1)結束時account1的賬戶餘額保持一致。

account2呼叫合約後,account1的CFX餘額變為1994.680912261955356217 CFX

sub_call2

也就是說,經過account2對合約的呼叫後,account1的賬戶CFX餘額變動為:
$$1994.618412261955356217-1994.680912261955356217=-0.0625(CFX)‬$$

這意味著,由於account1佔用的64Bytes合約空間被釋放,0.0625 CFX會被退還到了account1的賬戶中。按照步驟(1)中計算得到的付款額:0.06250000000002695 CFX,我們能夠推測,account1呼叫contract.inc(10)實際所消耗的費用為:
$$0.06250000000002695-0.0625=0.00000000000002695(CFX)$$

呼叫合約前,程式顯示的account1的CFX餘額為:1994.618412261955437067 CFX。

而呼叫合約後,對應賬戶的CFX餘額為:1994.618412261955410117 CFX。

也就是說經過這次與合約的互動操作,賬戶扣除了0.00000000000002695個CFX,其計算公式如下:
$$1994.618412261955437067-1994.618412261955410117=0.00000000000002695(CFX)$$

這也間接佐證了,account1中資料儲存所佔用的合約空間就是1個64Bytes:
$$0.0625×16=1(個)‬$$

此時我們再去confluxscan處檢視賬戶account1對應的餘額為:1994.680912261955327318 CFX
confluxscan2
按照計算公式:
$$1994.680912261955354268-1994.680912261955327318=0.00000000000002695 (CFX)‬$$
這同時也佐證了:account1呼叫contract.inc(10)與合約進行互動時,其賬戶實際消耗了0.00000000000002695 CFX

2. 一個含有1個長度為5(且在呼叫時修改5個元素值)的定長陣列的例子

合約程式碼如下:

pragma solidity ^0.5.0;

contract Test {
    uint[5] arr = [1,2,3,4,5];
    event SelfEvent(address indexed sender, uint[5] current,uint length);
    
    function init() public{
        arr[0] = 100;
        arr[1] = 200;   
    }
    
    function getArrayContent() public returns(uint[5] memory){
        return arr;
    }
  
    function getArrayLength() public returns(uint){
        return arr.length;
    }
    
    function increment (uint data) public{
        for(uint i=0;i<arr.length;i++){
            arr[i]+=data;
        }
    }
    
    function getGrade() public returns (uint){
        uint grade = 0 ;
        for(uint i=0;i<arr.length;i++){
            grade += arr[i];
        }
        return grade;
    }
    function self() public {
        emit SelfEvent(msg.sender, arr,arr.length);
    }
}

由於該智慧合約中只有 uint [] arr = [1,2,3,4,5]; 這個長度為5的陣列,陣列內資料對應了5個條目,陣列長度5同時也對應了1個條目,共6個條目,由於呼叫合約中的 increment() 函式會修改陣列中的每一個元素,但沒有改動陣列長度,因此按照之前的公式分析:
$$CFS(α)≡CFS(α;σ)≡賬戶a在世界狀態σ下擁有的儲存條目總數×\left(\frac{1}{16}\right)(CFX)$$

正是由於 uint [] arr = [1,2,3,4,5]; 陣列內元素及陣列長度正好對應了6個64B長度的條目,但由於陣列長度沒有改變並寫入區塊鏈,結合之前我們給定的例項分析,質押的數額應當為:0.3125 CFX
$$\left(\frac{1}{16}\right)×5=0.3125(CFX)$$

呼叫合約的程式碼如下所示:

const { Conflux, util } = require('js-conflux-sdk');
// 這個地址是上面列印出來的 receipt.contractCreated 
const public_address = '0x17b38613e633c2b8fb4686a3a62b9b782ac5e0ca';
const contractAddress = '0x822ebe7eb36cdf159d6d544f6321e1a5c6619dc2';
const PRIVATE_KEY1 = '0x2772b19636f1d183a9a2a0d27da2a1d0efb97637b425*';
const PRIVATE_KEY2 = '0x2adba218d5eacb5bc9bbb4c6fdecef7d1719c8184812*';
const compiled = require(`./build/Test.json`)
async function main() {
  const cfx = new Conflux({
    url: 'http://main.confluxrpc.org',
  });
  const contract = cfx.Contract({
    address : contractAddress,
    abi: compiled.abi,
  });
  
  let inc = await contract.getGrade();
  console.log("output:"  + inc.toString());
  
  const before_call_balance = await cfx.getBalance(public_address);
  console.log("before account1 call the current drip:"+before_call_balance.toString());
  console.log("before account1 call the current cfx:"+util.unit.fromDripToCFX(before_call_balance));
  
  const account1 = cfx.Account(PRIVATE_KEY1);//使用私鑰建立賬戶
  
  // 進行記錄並花費CFX
  await contract.increment(1).sendTransaction({ from: account1 }).confirmed();
  
  const after_call_balance = await cfx.getBalance(public_address);
  console.log("after account1 call increment() the current drip:"+after_call_balance.toString());
  console.log("after account1 call increment() the current cfx:"+util.unit.fromDripToCFX(after_call_balance));
    
  //建立account2,並嘗試呼叫合約希望釋放account1的cfx
  const account2 = cfx.Account(PRIVATE_KEY2);//使用私鑰建立賬戶
  
  const before_account2_call_balance = await cfx.getBalance(public_address);
  console.log("before account2 call increment() the current drip:"+after_call_balance.toString());
  console.log("before account2 call increment() the current cfx:"+util.unit.fromDripToCFX(after_call_balance));
  await contract.increment(2).sendTransaction({ from: account2 }).confirmed();
  
  const after_account2_call_balance = await cfx.getBalance(public_address);
  console.log("after account2 call increment() the current drip:"+after_account2_call_balance.toString());
  console.log("after account2 call increment() the current cfx:"+util.unit.fromDripToCFX(after_account2_call_balance));
}
main().catch(e => console.error(e));

為了方便描述,將參與呼叫合約的賬戶用account1和account2進行表示,被呼叫的合約用contract進行表示,將其賬戶資訊和對應的賬戶地址進行彙總 ,所列表格如下:

賬戶名賬戶地址
account10x17b38613e633c2b8fb4686a3a62b9b782ac5e0ca
account20x1941E3137aDDf02514cBFeC292710463d41e8196
countract0x822ebe7eb36cdf159d6d544f6321e1a5c6619dc2

為方便表示,後文使用上表中賬戶名指代各賬戶

在呼叫前,使用confluxscan查account1對應的CFX餘額,發現餘額為:1986.673099761952452902 CFX。

sample2_scan1

(1)使用account1進行第一次合約呼叫:

由於account1呼叫的increment()函式修改了合約儲存的記錄佔用了儲存空間,且修改的資料被區塊鏈記錄,所以需要上交CFX作為押金

在程式啟動後:首先顯示account1的賬戶餘額:1986.673099761952481801 CFX。

程式會使用account1呼叫contract.increment(1)向合約發起互動,該函式會對陣列中的每個元素進行加1操作,呼叫完成後發現account1的賬戶餘額會變為:1986.360599761952433413 CFX。

sample2_sub_call1

也就是說經過account1與合約contract的這一次互動操作,其賬戶扣除了:
$$1986.673099761952481801-1986.360599761952433413=0.312500000000048388(CFX)‬$$

這說明通過呼叫contract.increment(1)與合約進行互動對資料進行修改並將日誌寫入區塊鏈。account1上交了0.312500000000048388的CFX。

使用account2呼叫合約,以幫助account1釋放空間

程式會繼續執行,並使用account2通過呼叫contract.increment(2)向合約發起互動

在呼叫前,account1的賬戶餘額為:1986.360599761952433413 CFX,與上一操作結束時account1的賬戶餘額保持一致。

account2呼叫合約後,account1的CFX餘額變為1986.673099761952433413 CFX

sub_call2

也就是說,經過account2對合約的呼叫後,account1的賬戶CFX餘額變動為:
$$1986.360599761952433413-1986.673099761952433413=-0.3125(CFX)‬$$

這意味著,由於account1呼叫increment函式改動並佔用的320Bytes大小的合約空間被釋放,0.3125 CFX會被退還到了account1的賬戶中。按照步驟(1)中計算得到的付款額:0.06250000000002695 CFX,我們能夠推測,account1呼叫contract.increment(1)實際所消耗的費用為:
$$0.312500000000048388-0.3125=0.000000000000048388(CFX)$$

程式中account1呼叫合約前,程式顯示的account1的CFX餘額為:1986.673099761952481801 CFX。

程式中account2呼叫合約後,account1賬戶的CFX餘額為:1986.673099761952433413 CFX。

也就是說經過這次與合約的互動操作,account1賬戶扣除了0.000000000000048388個CFX,其計算公式如下:
$$1986.673099761952481801-1986.673099761952433413=0.000000000000048388(CFX)$$

此時我們再去confluxscan處檢視賬戶account1對應的餘額為:1986.673099761952404514 CFX
confluxscan2
w=559&h=159&f=png&s=23450)

按照計算公式:
$$1986.673099761952452902-1986.673099761952404514=0.000000000000048388 (CFX)‬$$
這同時也佐證了:account1呼叫contract.incement(1)與合約進行互動時,其賬戶實際消耗了0.000000000000048388 CFX

3. 一個內含 stringuint (且在呼叫時修改uint)的struct樣例

合約程式碼如下:

pragma solidity ^0.5.0;

contract Struct_test {

    struct Animal {
        string name;
        uint age;
    }
    event SelfEvent(address indexed sender,uint current);
    event SelfEvent_string(address indexed sender,string current);
    Animal animal1 = Animal("英短",5);
    Animal animal2 = Animal("美短",5);
    
    
    function getAnimal(uint inc) public{
        animal1.age+=inc;
        animal2.age-=inc;
    }
    function get() public view returns(uint256){
        return animal1.age;
    }
    function self() public {
        emit SelfEvent(msg.sender, animal1.age);
        emit SelfEvent(msg.sender, animal2.age);
        emit SelfEvent_string(msg.sender, animal1.name);
        emit SelfEvent_string(msg.sender, animal2.name);
    }    
}

由於該智慧合約中有一個包含 string name;uint age; 的結構體變數 Animal ,其中string對應變長陣列,對應條目數根據實際設定的內容為準,而uint對應於1個條目。在合約中呼叫 getAnimal() 函式會修改例項化的animal1和animal2的age變數,改動的條目數為2,因此按照之前的公式分析:
$$CFS(α)≡CFS(α;σ)≡賬戶a在世界狀態σ下擁有的儲存條目總數×\left(\frac{1}{16}\right)(CFX)$$

正是由於 animal1.age+=inc;animal2.age-=inc; 呼叫改動了2個64B長度的條目,並將改變記錄進區塊鏈,結合之前給定的例項進行分析,質押的數額應當是改動的結構體例項元素內的age變數,正好對應了2個64B長度的條目,由於沒有改動name,結合之前我們給定的例項分析,質押的數額應當為:0.125 CFX
$$\left(\frac{1}{16}\right)×2=0.125(CFX)$$

呼叫合約的程式碼如下所示:

const { Conflux, util } = require('js-conflux-sdk');
// 這個地址是上面列印出來的 receipt.contractCreated 
const public_address = '0x17b38613e633c2b8fb4686a3a62b9b782ac5e0ca';
const contractAddress = '0x84dd09cd48e07426c4ac50a389930c034be6c82a';
const PRIVATE_KEY1 = '0x2772b19636f1d183a9a2a0d27da2a1d0efb97637b425*';
const PRIVATE_KEY2 = '0x2adba218d5eacb5bc9bbb4c6fdecef7d1719c8184812*';
const compiled = require(`./build/Struct_test`)
async function main() {
  const cfx = new Conflux({
    url: 'http://main.confluxrpc.org',
  });
  const contract = cfx.Contract({
    address : contractAddress,
    abi: compiled.abi,
  });
  
  const before_call_balance = await cfx.getBalance(public_address);
  console.log("before account1 call the current drip:"+before_call_balance.toString());
  console.log("before account1 call the current cfx:"+util.unit.fromDripToCFX(before_call_balance));
  
  const account1 = cfx.Account(PRIVATE_KEY1);//使用私鑰建立賬戶
  
  // 進行記錄並花費CFX
  await contract.getAnimal(3).sendTransaction({ from: account1 }).confirmed();
  
  const after_call_balance = await cfx.getBalance(public_address);
  console.log("after account1 call getAnimal() the current drip:"+after_call_balance.toString());
  console.log("after account1 call getAnimal() the current cfx:"+util.unit.fromDripToCFX(after_call_balance));
    
  //建立account2,並嘗試呼叫合約希望釋放account1的cfx
  const account2 = cfx.Account(PRIVATE_KEY2);//使用私鑰建立賬戶
  
  const before_account2_call_balance = await cfx.getBalance(public_address);
  console.log("before account2 call getAnimal() the current drip:"+after_call_balance.toString());
  console.log("before account2 call getAnimal() the current cfx:"+util.unit.fromDripToCFX(after_call_balance));
  await contract.getAnimal(3).sendTransaction({ from: account2 }).confirmed();
  
  const after_account2_call_balance = await cfx.getBalance(public_address);
  console.log("after account2 call getAnimal() the current drip:"+after_account2_call_balance.toString());
  console.log("after account2 call getAnimal() the current cfx:"+util.unit.fromDripToCFX(after_account2_call_balance));
}
main().catch(e => console.error(e));

為了方便描述,將參與呼叫合約的賬戶用account1和account2進行表示,被呼叫的合約用contract進行表示,將其賬戶資訊和對應的賬戶地址進行彙總 ,所列表格如下:

賬戶名賬戶地址
account10x17b38613e633c2b8fb4686a3a62b9b782ac5e0ca
account20x1941E3137aDDf02514cBFeC292710463d41e8196
countract0x84dd09cd48e07426c4ac50a389930c034be6c82a

為方便表示,後文使用上表中賬戶名指代各賬戶

在呼叫前,使用confluxscan查account1對應的CFX餘額,發現餘額為:1983.472904449450987608 CFX。

sample3_scan1

(1)使用account1進行第一次合約呼叫:

由於account1呼叫的getAnimal()函式修改了合約儲存的記錄佔用了儲存空間,且修改的資料被區塊鏈記錄,所以需要上交CFX作為押金

在程式啟動後:首先顯示account1的賬戶餘額:1983.472904449451016507 CFX。

程式會使用account1呼叫contract.getAnimal(3)向合約發起互動,該函式會對animal1.age進行加3操作,對animal2.age進行減3操作,呼叫完成後發現account1的賬戶餘額會變為:1983.347904449450984359 CFX。

sample3_sub_call1

也就是說經過account1與合約contract的這一次互動操作,其賬戶扣除了:
$$1983.472904449451016507-1983.347904449450984359=0.125000000000032148(CFX)‬$$

這說明通過呼叫contract.getAnimal(3)與合約進行互動對資料進行修改並將日誌寫入區塊鏈。account1上交了額度為0.125000000000032148的CFX。

使用account2呼叫合約,以幫助account1釋放空間

呼叫合約的程式會繼續執行,並使用account2通過呼叫contract.getAnimal(3)向合約發起互動

在呼叫前,account1的賬戶餘額為:1983.347904449450984359 CFX,與上一操作結束時account1的賬戶餘額保持一致。

account2呼叫合約後,account1的CFX餘額變為1983.472904449450984359 CFX

sample3_sub_call2

也就是說,經過account2對合約的呼叫後,account1的賬戶CFX餘額變動為:
$$1983.347904449450984359-1983.472904449450984359=-0.125(CFX)‬$$

這意味著,由於account1呼叫getAnimal()函式改動並佔用的128Bytes大小的合約空間被account2對合約的呼叫所釋放,0.125 CFX會被退還到了account1的賬戶中。按照步驟(1)中計算得到的付款額:0.125000000000032148 CFX,我們能夠推測,account1呼叫contract.getAnimal(3)實際所消耗的費用為:
$$0.125000000000032148-0.125=0.000000000000032148(CFX)$$

程式中account1呼叫合約前,程式顯示的account1的CFX餘額為:1983.472904449451016507 CFX。

程式中account2呼叫合約後,account1賬戶的CFX餘額為:1983.472904449450984359 CFX。

也就是說經過這次與合約的互動操作,account1賬戶扣除了0.000000000000032148個CFX,其計算公式如下:
$$1986.673099761952481801-1986.673099761952433413=0.000000000000032148(CFX)$$

此時我們再去confluxscan處檢視賬戶account1對應的餘額為:1983.47290444945095546 CFX

sample3_confluxscan2

按照計算公式:
$$1983.472904449450987608-1983.47290444945095546=0.000000000000032148 (CFX)‬$$
這同時也佐證了:account1呼叫contract.getAnimal(3)與合約進行互動時,其賬戶實際消耗了0.000000000000032148 CFX

4. 一個含有mapping的例子

合約程式碼如下:

pragma solidity ^0.5.0;

contract mapping_test {
    mapping(address => uint) public balances;
    event SelfEvent(address indexed sender,uint current);
    function update(uint newBalance) public {
        balances[msg.sender] = newBalance;
    }
    function self() public {
        emit SelfEvent(msg.sender, balances[msg.sender]);
    }    
}

這段合約中使用到了 mapping 結構,由於該智慧合約中有一個包含 mapping(address => uint) balance; ,並且呼叫相關函式時,會更新 address 對應條目的資料,呼叫改動了1個64B長度的條目,並將改變記錄進區塊鏈,結合之前給定的例項進行分析,應當質押了0.0625個CFX。

呼叫合約程式碼如下:

const { Conflux, util } = require('js-conflux-sdk');
// 這個地址是上面列印出來的 receipt.contractCreated 
const public_address = '0x17b38613e633c2b8fb4686a3a62b9b782ac5e0ca';
const contractAddress = '0x8433f943dd6a4cbf13209b9e8674c08349872ce8';
const PRIVATE_KEY1 = '0x2772b19636f1d183a9a2a0d27da2a1d0efb977';
const PRIVATE_KEY2 = '0x2adba218d5eacb5bc9bbb4c6fdecef7d1719c';
const compiled = require(`./build/mapping_test`)
async function main() {
  const cfx = new Conflux({
    url: 'http://main.confluxrpc.org',
  });
  const contract = cfx.Contract({
    address : contractAddress,
    abi: compiled.abi,
  });
  
  const before_call_balance = await cfx.getBalance(public_address);
  console.log("before account1 call the current drip:"+before_call_balance.toString());
  console.log("before account1 call the current cfx:"+util.unit.fromDripToCFX(before_call_balance));
  
  const account1 = cfx.Account(PRIVATE_KEY1);//使用私鑰建立賬戶
  
  // 進行記錄並花費CFX
  await contract.update(3).sendTransaction({ from: account1 }).confirmed();
  
  const after_call_balance = await cfx.getBalance(public_address);
  console.log("after account1 call update() the current drip:"+after_call_balance.toString());
  console.log("after account1 call update() the current cfx:"+util.unit.fromDripToCFX(after_call_balance));
    
  //建立account2,並嘗試呼叫合約希望釋放account1的cfx
  const account2 = cfx.Account(PRIVATE_KEY2);//使用私鑰建立賬戶
  
  const before_account2_call_balance = await cfx.getBalance(public_address);
  console.log("before account2 call update() the current drip:"+after_call_balance.toString());
  console.log("before account2 call update() the current cfx:"+util.unit.fromDripToCFX(after_call_balance));
  await contract.update(5).sendTransaction({ from: account2 }).confirmed();
  
  const after_account2_call_balance = await cfx.getBalance(public_address);
  console.log("after account2 call update() the current drip:"+after_account2_call_balance.toString());
  console.log("after account2 call update() the current cfx:"+util.unit.fromDripToCFX(after_account2_call_balance));
  
  
}
main().catch(e => console.error(e));

為了方便描述,將參與呼叫合約的賬戶用account1進行表示,被呼叫的合約用contract進行表示,將其賬戶資訊和對應的賬戶地址進行彙總 ,所列表格如下:

賬戶名賬戶地址
account10x17b38613e633c2b8fb4686a3a62b9b782ac5e0ca
account20x1941E3137aDDf02514cBFeC292710463d41e8196
countract0x8433f943dd6a4cbf13209b9e8674c08349872ce8

為方便表示,後文使用上表中賬戶名指代各賬戶

呼叫時的輸出情況如下所示:

在程式啟動後:首先顯示account1的賬戶餘額:98.955078124999570464 CFX。

程式會使用account1呼叫contract.update(5)向合約發起互動,該函式會對balances[msg.sender]進行設定新值的操作,呼叫完成後發現account1的賬戶餘額會變為:98.892578124999543647 CFX。

也就是說經過account1與合約contract的這一次互動操作,其賬戶扣除了:
$$98.955078124999570464-98.892578124999543647=0.062500000000026817(CFX)‬$$

由於儲存抵押的條目為1,所以質押0.0625 CFX是正確的,而呼叫該合約花費了0.000000000000026817 (CFX)


相關資料庫:

相關文章