以太坊研究系列【智能合约】

Words
2067
Reading
10 min
9y
Listen to this post
~14 min

此文档为个人整理学习使用,部分内容来源于网络,也期待能给你带来益处。

智能合约

智能合约是存储在区块链上的一段代码,它们可以被区块链上的交易所触发,触发后,这段代码可以从区块链上读取数据或者向区块链上写入数据。

部署到区块链后,按合约地址codehash存储在区块链上。

合约语言

Solidity中的智能合约和传统面向对象语言中的类很相像,因此有构造函数,有继承,有变量,有function,也有抽象类等等传统概念。
由Solidity所写的智能合约,经过编译后就会由EVM来部署执行。
Solidity语言是一种类JS的语言,因此很多编码规范和JS很相似。

Solidity编码风格
https://solidity.readthedocs.io/en/develop/style-guide.html

Browser-solidity
https://ethereum.github.io/browser-solidity

Browser-solidity Github
https://github.com/ethereum/browser-solidity

web3.js.API
https://github.com/ethereum/wiki/wiki/JavaScript-API

部署合约

注意合约部署的时候,以太坊的私有链必须处在挖矿进行的状态,否则合约部署将不会生效

先解锁帐号

> personal.unlockAccount(acc0)
Unlock account 0x54b865714068f5f03574ace39a1f3279c4e83e2c
Passphrase: 
true

简单乘法例子

复制合约内容到geth命令行

        var a_demotypesContract = web3.eth.contract([{"constant":false,"inputs":[{"name":"a","type":"uint256"}],"name":"f","outputs":[{"name":"b","type":"uint256"}],"payable":false,"type":"function"}]);
        var a_demotypes = a_demotypesContract.new(
           {
             from: web3.eth.accounts[0], 
             data: '0x6060604052341561000c57fe5b5b60ab8061001b6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063b3de648b14603a575bfe5b3415604157fe5b60556004808035906020019091905050606b565b6040518082815260200191505060405180910390f35b600060006008830290508091505b509190505600a165627a7a7230582010decdc0b0a43b565814fe904eae2544665457d6353c7d906fc2c43c81c867e40029', 
             gas: '4700000'
           }, function (e, contract){
            console.log(e, contract);
            if (typeof contract.address !== 'undefined') {
                 console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash);
            }
         })
         
    结果如下:
    
    > var a_demotypesContract = web3.eth.contract([{"constant":false,"inputs":[{"name":"a","type":"uint256"}],"name":"f","outputs":[{"name":"b","type":"uint256"}],"payable":false,"type":"function"}]);
    undefined
    > var a_demotypes = a_demotypesContract.new(
    ...    {
    ......      from: web3.eth.accounts[0], 
    ......      data: '0x6060604052341561000c57fe5b5b60ab8061001b6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063b3de648b14603a575bfe5b3415604157fe5b60556004808035906020019091905050606b565b6040518082815260200191505060405180910390f35b600060006008830290508091505b509190505600a165627a7a7230582010decdc0b0a43b565814fe904eae2544665457d6353c7d906fc2c43c81c867e40029', 
    ......      gas: '4700000'
    ......    }, function (e, contract){
    ......     console.log(e, contract);
    ......     if (typeof contract.address !== 'undefined') {
    .........          console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash);
    .........     }
    ......  })
    null [object Object]
    undefined
    > null [object Object]
    Contract mined! address: 0xf56d3164a66552e6208e972bc2f76bfcf0133cdf transactionHash: 0x026cf57f0841861f075aa6475a70a78787872948864b11319b873367fd8c4688

输入合约部署的实例a_demotypes,可以看到详情。

    > a_demotypes
    {
      abi: [{
          constant: false,
          inputs: [{...}],
          name: "f",
          outputs: [{...}],
          payable: false,
          type: "function"
      }],
      address: "0xf56d3164a66552e6208e972bc2f76bfcf0133cdf",
      transactionHash: "0x026cf57f0841861f075aa6475a70a78787872948864b11319b873367fd8c4688",
      allEvents: function(),
      f: function()
    }

调用合约方法:

    > a_demotypes.f.call(100)
    800
    > a_demotypes.f.call(8.8)
    64

如果挖矿没有开始,布署合约时会提示undefined


众筹例子(骗钱的)

在网上找到一个最简单的众筹合约,试试:

pragma solidity 0.4.9;
contract NBICO {

address public organizer;

event FundTransfer(address backer, uint amount, bool isContribution);

function NBICO() {

organizer = msg.sender;

}

function destory() {

if(msg.sender == organizer) {

suicide(organizer);

}

}

}

在Browser-solidity中得到布署代码,粘贴到命令行:

var nbicoContract = web3.eth.contract([{"constant":true,"inputs":[],"name":"organizer","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function","stateMutability":"view"},{"constant":false,"inputs":[],"name":"destory","outputs":[],"payable":false,"type":"function","stateMutability":"nonpayable"},{"inputs":[],"payable":false,"type":"constructor","stateMutability":"nonpayable"},{"anonymous":false,"inputs":[{"indexed":false,"name":"backer","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"isContribution","type":"bool"}],"name":"FundTransfer","type":"event"}]);
var nbico = nbicoContract.new(
   {
     from: web3.eth.accounts[0], 
     data: '0x6060604052341561000c57fe5b5b33600060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505b5b6101908061005f6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806361203265146100465780636bdebcc914610098575bfe5b341561004e57fe5b6100566100aa565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34156100a057fe5b6100a86100d0565b005b600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561016157600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16ff5b5b5600a165627a7a72305820b3bdd88f2f26c2b101929d343f50df91008cf31b742107b471701767ec53b0630029', 
     gas: '4700000'
   }, function (e, contract){
    console.log(e, contract);
    if (typeof contract.address !== 'undefined') {
         console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash);
    }
 })

看起来已经布署成功了,如下:

> miner.start()
null
> null [object Object]
Contract mined! address: 0x9bf83a6139b77ff94464907c7923d9565f72d5d3 transactionHash: 0x1a7c9feff38e6ddc34cc9dacdb5c89d1386e153398ce256f6550ad53d6d95dbf
> nbico
{
  abi: [{
      constant: true,
      inputs: [],
      name: "organizer",
      outputs: [{...}],
      payable: false,
      stateMutability: "view",
      type: "function"
  }, {
      constant: false,
      inputs: [],
      name: "destory",
      outputs: [],
      payable: false,
      stateMutability: "nonpayable",
      type: "function"
  }, {
      inputs: [],
      payable: false,
      stateMutability: "nonpayable",
      type: "constructor"
  }, {
      anonymous: false,
      inputs: [{...}, {...}, {...}],
      name: "FundTransfer",
      type: "event"
  }],
  address: "0x9bf83a6139b77ff94464907c7923d9565f72d5d3",
  transactionHash: "0x1a7c9feff38e6ddc34cc9dacdb5c89d1386e153398ce256f6550ad53d6d95dbf",
  FundTransfer: function(),
  allEvents: function(),
  destory: function(),
  organizer: function()
}

也可以给这个合约地址转帐,执行如下:

> nbico = "0x9bf83a6139b77ff94464907c7923d9565f72d5d3"
"0x9bf83a6139b77ff94464907c7923d9565f72d5d3"
> personal.unlockAccount(acc0)
Unlock account 0x54b865714068f5f03574ace39a1f3279c4e83e2c
Passphrase: 
true
> eth.sendTransaction({from:acc0, to:nbico, value:100})
"0x8d880ae0e5a38d4dc4340f6d610ac071b4cb081a1d3f4ea2584a80c92f284348"

数组的例子

pragma solidity 0.4.9;
/*数组类型Demo*/
contract DemoTypes303 {
  /*String数组例子*/
  string[] strArr;
  function add(string str){
    strArr.push(str);
  }

  function getStrAt(uint n) constant returns (string s){
    string tmp = strArr[n];
    return tmp;
  }

  function updateStrAt(uint n, string str) {
    strArr[n] = str;
  }

  function deleteStrAt(uint index) {
    uint len = strArr.length;
    if (index >= len) return;
    for (uint i = index; i<len-1; i++) {
      strArr[i] = strArr[i+1];
    }

    delete strArr[len-1];
    strArr.length--;
  }
}

代币例子

pragma solidity 0.4.9;

contract Token4 {
  uint[] public balancesOf;
  function Token() {
    balancesOf.push(100);
    balancesOf.push(200);
  }

  function transfer(uint _from, uint _to, uint _amount) {
    balancesOf[_from] -= _amount;
    balancesOf[_to] += _amount;
  }

  function mint(uint value) {
    balancesOf[0] += value;
  }
}

帐号结合的代币例子

pragma solidity 0.4.9;
contract Token {
  mapping (address => uint) public balancesOf;
  address public owner;
  function Token() {
    owner = msg.sender;
    balancesOf[msg.sender] = 10000;
  }

  function transfer(address _to, uint _value) {
    if (balancesOf[msg.sender] < _value) throw; //避免转移出去的代币超过当前的存货
    if (balancesOf[_to] + _value < balancesOf[_to]) throw; //避免自己调用自己,或者递归调用
    balancesOf[msg.sender] -= _value;
    balancesOf[_to] += _value;
  }

  function mint(uint _amount) {
    balancesOf[owner] += _amount;
  }
}

--

参会买票的例子

contract Conference {
  address public organizer;
  mapping (address => uint) public registrantsPaid;
  uint public numRegistrants;
  uint public quota;

  event Deposit(address _from, uint _amount);  // so you can log these events
  event Refund(address _to, uint _amount); 

  function Conference() { // Constructor
    organizer = msg.sender;
    quota = 500;
    numRegistrants = 0;
  }
  function buyTicket() public returns (bool success) {
    if (numRegistrants >= quota) { return false; }
    registrantsPaid[msg.sender] = msg.value;
    numRegistrants++;
    Deposit(msg.sender, msg.value);
    return true;
  }
  function changeQuota(uint newquota) public {
    if (msg.sender != organizer) { return; }
    quota = newquota;
  }
  function refundTicket(address recipient, uint amount) public {
    if (msg.sender != organizer) { return; }
    if (registrantsPaid[recipient] == amount) { 
      address myAddress = this;
      if (myAddress.balance >= amount) { 
        recipient.send(amount);
        registrantsPaid[recipient] = 0;
        numRegistrants--;
        Refund(recipient, amount);
      }
    }
  }
  function destroy() { // so funds not locked in contract forever
    if (msg.sender == organizer) { 
      suicide(organizer); // send funds to organizer
    }
  }
}

代码解释:
1、address public organizer;
存放组织者的钱包地址,在构造函数中被以下语句
organizer = msg.sender;
赋值,也叫做“owner”。
2、对一些操作可以判断是否组织者,如下:
if (msg.sender != organizer)
3、转给合约的资金会保存在合约地址中,这些资金通过destroy函数被释放给了构造函数中的组织者地址。如下:
suicide(organizer);

智能合约布署测试几点情况

  • 合约布署成功后,合约的构造函数也会被调用,可以取到合约组织者地址了,如下:

    conference.organizer()
    "0x54b865714068f5f03574ace39a1f3279c4e83e2c"
    acc0
    "0x54b865714068f5f03574ace39a1f3279c4e83e2c"

  • public变量并不是直接使用,在abi中是function()类型,使用时也得带上括号,如下:

    conference.quota()
    500
    conference.organizer()
    "0x54b865714068f5f03574ace39a1f3279c4e83e2c"

  • 只从区块链取数据,而不修改数据,在函数后加上.call()的方式

    conference.quota.call()
    888

  • 函数要修改合约内部的数据,必须向区块链发送交易,在函数后面加上.sendTransaction()来指明这是一笔交易,同时要指定{from:}参数,也就是需要矿工把改变存储到链上去,如下:

    personal.unlockAccount(acc0);
    Unlock account 0x54b865714068f5f03574ace39a1f3279c4e83e2c
    Passphrase:
    true
    conference.changeQuota.sendTransaction(888, {from:acc0});
    "0xf45851fc09fdac6562100b1dc9ea1a96759af5510f8be33b23335f42ac7eb323"
    conference.buyTicket.sendTransaction({from:acc1, value:18})
    "0x1e69ae29695f2fa9345f87a09fb71c7738cefbfbd3697dd36fc3dd3bff75358e"
    conference.registrantsPaid.call(acc1)
    18
    conference.registrantsPaid.call(acc0)
    0
    conference.refundTicket.sendTransaction(acc1, 18, {from:acc0})
    "0xb98eff4290bd515875412df169a7e33325c7739148350e3a07ad03b899de0f46"
    conference.numRegistrants()
    0
    conference.destroy.sendTransaction({from:acc0})
    "0x02fce40c5c7d498514bccca3e5e2b403147602be87e324d021c3c6a79dc1ee97"

  • 合约上的以太币数量也可以通过eth.getBalance()获得,如下:

    eth.getBalance(conference.address)
    18

智能合约和存储空间

合约布署后会创建一个合约帐户,合约帐户里保存着智能合约的可执行字节码,并且有存储空间,合约能对合约帐户的空间写入信息。而abi是一个接口结构,利用abiDefinition可以创建调用该合约的结构。abi应该由合约所有方自己保存和提供。

存储费用

黄皮书中费用存储256bit的字需要20kgas,1KB是640kgas。如果1Gas=50Gwei,则1KB存储花费0.032ETH,1GB存储需要32000ETH。

也可以用类似的合约进行测试:

contract test {
bytes data;

function addData(bytes _data) public {
    data = _data;
}

}

有人测试数据
title

数据位置

所有的复杂类型如数组(arrays)和数据结构(struct)有一个额外属性:数据的存储位置,可为memory和storage。

根据上下文不同,大多数时候数据位置有默认值,也可指定关键字memory和storage修改它。

函数参数(包含返回的参数)默认是memory。局部复杂类型变量(local variables)和状态变量(state variables)默认是storage。

碰到的几个问题

  • 合约destroy()了,还能继续调用?
    答:是可以继续调用的,这个destroy()其实只是一个函数,把函数名改为nothing(),就容易理解了。

  • geth中可以直接模拟假的地址,实际网络呢?
    答:这个私链、测试、公网应该是一样的,毕竟变量地址是可以随便设定的,但是只能读取数据,因为一旦交易就需要密码或者私钥了。

智能合约的测试

一种方法是提供Event,在外部接收事件并日志;
另外可以设置pulic测试变量,函数执行结果后修改,直接用Mist查看。

更成熟的例子

这个比大小的智能合约是比较完整的了
https://etheroll.com

参考

http://www.8btc.com/browser-solidity-token

http://ethfans.org/posts/101-noob-intro

http://blog.csdn.net/misshqzzz/article/details/77834856

https://hackernoon.com/ether-purchase-power-df40a38c5a2f

https://hackernoon.com/costs-of-a-real-world-ethereum-contract-2033511b3214

https://github.com/ethereum/wiki/wiki/Standardized_Contract_APIs

http://solidity.readthedocs.io/en/develop/introduction-to-smart-contracts.html

拍卖的例子
http://blog.csdn.net/wo541075754/article/details/53256504

***************************************

作者简介


老鱼,从事互联网技术十余年,知名上市公司CTO;炒股十余年,从亏损几十万到略有盈利;顺应创业大潮多次创业,在传统行业和手游行业有所获;研习佛学易理,性格淡泊清净!

欢迎有兴趣的朋友加入微信群和电报群,一起学习探讨,一起实现咱们的小目标!

  • 百分百区块链群,微信扫码后会再显示一个二维码,长按图片“识别图中二维码”加入!


以太坊研究系列【智能合约】 | Ecency