Conflux的存储抵押机制

7,965 阅读19分钟

背景介绍

近期有社区的小伙伴在开发智能合约并部署到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作为押金,其对应公式如下:

(102464)×(116)=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在世界状态σ下拥有的存储条目总数×(116)CFXCFS(α)≡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=64Bytes32+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在世界状态σ下拥有的存储条目总数×(116)CFXCFS(α)≡CFS(α;σ)≡账户a在世界状态σ下拥有的存储条目总数×\left(\frac{1}{16}\right)(CFX)

正是由于 uint public count=0 变量正好对应于一个64B的条目,结合所以之前我们给定的实例,真正质押的数额为:0.0625 CFX (116)=0.0625CFX\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.6809122619553831671994.618412261955356217=0.06250000000002695CFX1994.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.6184122619553562171994.680912261955356217=0.0625CFX1994.618412261955356217-1994.680912261955356217=-0.0625(CFX)

这意味着,由于account1占用的64Bytes合约空间被释放,0.0625 CFX会被退还到了account1的账户中。按照步骤(1)中计算得到的付款额:0.06250000000002695 CFX,我们能够推测,account1调用contract.inc(10)实际所消耗的费用为: 0.062500000000026950.0625=0.00000000000002695CFX0.06250000000002695-0.0625=0.00000000000002695(CFX)

调用合约前,程序显示的account1的CFX余额为:1994.618412261955437067 CFX。

而调用合约后,对应账户的CFX余额为:1994.618412261955410117 CFX。

也就是说经过这次与合约的交互操作,账户扣除了0.00000000000002695个CFX,其计算公式如下: 1994.6184122619554370671994.618412261955410117=0.00000000000002695CFX1994.618412261955437067-1994.618412261955410117=0.00000000000002695(CFX)

这也间接佐证了,account1中数据存储所占用的合约空间就是1个64Bytes: 0.0625×16=1(个)0.0625×16=1(个)

此时我们再去confluxscan处查看账户account1对应的余额为:1994.680912261955327318 CFX confluxscan2 按照计算公式: 1994.6809122619553542681994.680912261955327318=0.00000000000002695(CFX)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在世界状态σ下拥有的存储条目总数×(116)CFXCFS(α)≡CFS(α;σ)≡账户a在世界状态σ下拥有的存储条目总数×\left(\frac{1}{16}\right)(CFX)

正是由于 uint [] arr = [1,2,3,4,5]; 数组内元素及数组长度正好对应了6个64B长度的条目,但由于数组长度没有改变并写入区块链,结合之前我们给定的实例分析,质押的数额应当为:0.3125 CFX (116)×5=0.3125CFX\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.6730997619524818011986.360599761952433413=0.312500000000048388CFX1986.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.3605997619524334131986.673099761952433413=0.3125CFX1986.360599761952433413-1986.673099761952433413=-0.3125(CFX)

这意味着,由于account1调用increment函数改动并占用的320Bytes大小的合约空间被释放,0.3125 CFX会被退还到了account1的账户中。按照步骤(1)中计算得到的付款额:0.06250000000002695 CFX,我们能够推测,account1调用contract.increment(1)实际所消耗的费用为: 0.3125000000000483880.3125=0.000000000000048388CFX0.312500000000048388-0.3125=0.000000000000048388(CFX)

程序中account1调用合约前,程序显示的account1的CFX余额为:1986.673099761952481801 CFX。

程序中account2调用合约后,account1账户的CFX余额为:1986.673099761952433413 CFX。

也就是说经过这次与合约的交互操作,account1账户扣除了0.000000000000048388个CFX,其计算公式如下: 1986.6730997619524818011986.673099761952433413=0.000000000000048388CFX1986.673099761952481801-1986.673099761952433413=0.000000000000048388(CFX)

此时我们再去confluxscan处查看账户account1对应的余额为:1986.673099761952404514 CFX confluxscan2 w=559&h=159&f=png&s=23450)

按照计算公式: 1986.6730997619524529021986.673099761952404514=0.000000000000048388(CFX)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在世界状态σ下拥有的存储条目总数×(116)CFXCFS(α)≡CFS(α;σ)≡账户a在世界状态σ下拥有的存储条目总数×\left(\frac{1}{16}\right)(CFX)

正是由于 animal1.age+=inc;animal2.age-=inc; 调用改动了2个64B长度的条目,并将改变记录进区块链,结合之前给定的实例进行分析,质押的数额应当是改动的结构体实例元素内的age变量,正好对应了2个64B长度的条目,由于没有改动name,结合之前我们给定的实例分析,质押的数额应当为:0.125 CFX (116)×2=0.125CFX\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.4729044494510165071983.347904449450984359=0.125000000000032148CFX1983.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.3479044494509843591983.472904449450984359=0.125CFX1983.347904449450984359-1983.472904449450984359=-0.125(CFX)

这意味着,由于account1调用getAnimal()函数改动并占用的128Bytes大小的合约空间被account2对合约的调用所释放,0.125 CFX会被退还到了account1的账户中。按照步骤(1)中计算得到的付款额:0.125000000000032148 CFX,我们能够推测,account1调用contract.getAnimal(3)实际所消耗的费用为: 0.1250000000000321480.125=0.000000000000032148CFX0.125000000000032148-0.125=0.000000000000032148(CFX)

程序中account1调用合约前,程序显示的account1的CFX余额为:1983.472904449451016507 CFX。

程序中account2调用合约后,account1账户的CFX余额为:1983.472904449450984359 CFX。

也就是说经过这次与合约的交互操作,account1账户扣除了0.000000000000032148个CFX,其计算公式如下: 1986.6730997619524818011986.673099761952433413=0.000000000000032148CFX1986.673099761952481801-1986.673099761952433413=0.000000000000032148(CFX)

此时我们再去confluxscan处查看账户account1对应的余额为:1983.47290444945095546 CFX

sample3_confluxscan2

按照计算公式: 1983.4729044494509876081983.47290444945095546=0.000000000000032148(CFX)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.95507812499957046498.892578124999543647=0.062500000000026817CFX98.955078124999570464-98.892578124999543647=0.062500000000026817(CFX)

由于存储抵押的条目为1,所以质押0.0625 CFX是正确的,而调用该合约花费了0.000000000000026817 (CFX)


相关资料库: