关于区块链:Conflux的存储抵押机制

43次阅读

共计 19256 个字符,预计需要花费 49 分钟才能阅读完成。

背景介绍

近期有社区的小伙伴在开发智能合约并部署到 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 执行前后的世界状态,σ是交易执行完结后的世界状态,针对 存储限度 字段 T l有一个 CFS(α;σ)≤CFS(α;σ)+Tl/1018的断言。

# <table><tr><td bgcolor=yellow> 关键点:想弄清楚调用合约质押的 CFX 有多少,肯定要弄清楚合约中变量的条目数,以及在通过函数调用合约进行操作时有多少条目被批改,且改变的数目被记录到了区块链中!</td></tr></table>

Solidity 内存管理机制

依据 Solidity 文档对于其内存治理的形容及 Conflux 存储押金机制,咱们可能发现,合约存储须要有 key 和 value 进行保护,个别状况下:key 的长度为 256bits,value 的长度同样为 256bits,按一个智能合约的存储空间依照如下表格进行组织,其中 {0,1}256 示意 256 位比特串 (比特串中只有 0 或 1 两个值), 每个 key/value 对就能够被了解为一个条目

条目 键 / 地址({0,1}256) 值({0,1}256)
1 0…00000 0
2 0…00001 1
3 0…00002 2
2256 f…fffff 0

因为 256bits=32bytes,两个 256bits 对应的长度为:
$$32+32=64(Bytes)$$

Solidity 中常见变量及其对应的条目数整顿

变量 长度 定义形式
一般变量 1 个一般变量对应 1 个条目 uint public count=0;
mapping mapping 的每 1 个 key 都对应于 1 个条目 mapping(address => uint) public balances;
array 数组每 1 个元素对应于 1 个条目,数组长 arr.length 是额定的 1 个条目 uint[5] fixedArr = [1,2,3,4,5]; string productname;
struct struct 内每个 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 进行示意,将其账户信息和对应的账户地址进行汇总,所列表格如下:

账户名 账户地址
account1 0x17b38613e633c2b8fb4686a3a62b9b782ac5e0ca
account2 0x1941E3137aDDf02514cBFeC292710463d41e8196
countract 0x845dd6f64bb3d2771a8f30dc85bb14f5ac26b75e

为不便示意,后文应用上表中账户名指代各账户

在调用前,应用 confluxscan 查 account1 对应的 CFX 余额,发现余额为:1994.680912261955354268 CFX。

(1)应用 account1 进行第一次合约调用:

因为 account1 调用合约占用了空间,须要上交 CFX 作为押金

在程序启动后:首先显示 account1 的账户余额:1994.680912261955383167 CFX。

程序会应用 account1 调用 contract.inc(10)向合约发动交互,调用实现后发现 account1 的账户余额会变为:1994.618412261955356217 CFX。

也就是说通过 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

也就是说,通过 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

依照计算公式:
$$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 进行示意,将其账户信息和对应的账户地址进行汇总,所列表格如下:

账户名 账户地址
account1 0x17b38613e633c2b8fb4686a3a62b9b782ac5e0ca
account2 0x1941E3137aDDf02514cBFeC292710463d41e8196
countract 0x822ebe7eb36cdf159d6d544f6321e1a5c6619dc2

为不便示意,后文应用上表中账户名指代各账户

在调用前,应用 confluxscan 查 account1 对应的 CFX 余额,发现余额为:1986.673099761952452902 CFX。

(1)应用 account1 进行第一次合约调用:

因为 account1 调用的 increment()函数批改了合约存储的记录占用了存储空间,且批改的数据被区块链记录,所以须要上交 CFX 作为押金

在程序启动后:首先显示 account1 的账户余额:1986.673099761952481801 CFX。

程序会应用 account1 调用 contract.increment(1)向合约发动交互,该函数会对数组中的每个元素进行加 1 操作,调用实现后发现 account1 的账户余额会变为:1986.360599761952433413 CFX。

也就是说通过 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

也就是说,通过 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

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 进行示意,将其账户信息和对应的账户地址进行汇总,所列表格如下:

账户名 账户地址
account1 0x17b38613e633c2b8fb4686a3a62b9b782ac5e0ca
account2 0x1941E3137aDDf02514cBFeC292710463d41e8196
countract 0x84dd09cd48e07426c4ac50a389930c034be6c82a

为不便示意,后文应用上表中账户名指代各账户

在调用前,应用 confluxscan 查 account1 对应的 CFX 余额,发现余额为:1983.472904449450987608 CFX。

(1)应用 account1 进行第一次合约调用:

因为 account1 调用的 getAnimal()函数批改了合约存储的记录占用了存储空间,且批改的数据被区块链记录,所以须要上交 CFX 作为押金

在程序启动后:首先显示 account1 的账户余额:1983.472904449451016507 CFX。

程序会应用 account1 调用 contract.getAnimal(3)向合约发动交互,该函数会对 animal1.age 进行加 3 操作,对 animal2.age 进行减 3 操作,调用实现后发现 account1 的账户余额会变为:1983.347904449450984359 CFX。

也就是说通过 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

也就是说,通过 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

依照计算公式:
$$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 进行示意,将其账户信息和对应的账户地址进行汇总,所列表格如下:

账户名 账户地址
account1 0x17b38613e633c2b8fb4686a3a62b9b782ac5e0ca
account2 0x1941E3137aDDf02514cBFeC292710463d41e8196
countract 0x8433f943dd6a4cbf13209b9e8674c08349872ce8

为不便示意,后文应用上表中账户名指代各账户

调用时的输入状况如下所示:

在程序启动后:首先显示 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)

正文完
 0