使用 Browser-solidity 在 Go-Ethereum1.7.2 上进行简单的智能合约部署

作者: melanc 分类: 区块链 发布时间: 2018-03-20 11:06

转自:https://www.jianshu.com/p/91d775d20599

基本概念

智能合约

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

Solidity

Solidity 是 Ethereum 的一种契约型编程语言,运行在Ethereum虚拟机(EVM)之上。

Solidity的语言特性

它的语法接近于Javascript,是一种面向对象的语言。但作为一种真正意义上运行在网络上的去中心合约,它又有很多的不同,下面列举一些:

  • 以太坊底层是基于帐户,而非UTXO的,所以有一个特殊的Address的类型。用于定位用户,定位合约,定位合约的代码(合约本身也是一个帐户)。
  • 由于语言内嵌框架是支持支付的,所以提供了一些关键字,如payable,可以在语言层面直接支持支付。
  • 存储是使用网络上的区块链,数据的每一个状态都可以永久存储,所以需要确定变量使用内存,还是区块链。
  • 运行环境是在去中心化的网络上,会比较强调合约或函数执行的调用的方式。因为原来一个简单的函数调用变为了一个网络上的节点中的代码执行。
  • 最后一个非常大的不同则是它的异常机制,一旦出现异常,所有的执行都将会被回撤,这主要是为了保证合约执行的原子性,以避免中间状态出现的数据不一致。

Browser-solidity

Browser-solidity 是一个官方提供的一个基于浏览器的合约编译器,非常好用,而且build版本会紧跟最新的Solidity的build版本。但由于网络原因以及GFW的存在,有可能会另一部分人访问很慢,进而影响开发效率。

可以直接在线使用,访问后面的地址:https://remix.ethereum.org

源码地址:https://github.com/ethereum/remix-ide

本文环境:

Mac OS 10.13.3

Geth v1.8.1

Browser-solidity

Nvm v0.33.8

本地安装Browser-solidity

安装NVM

安装Browser-solidity需要依赖node、npm、nvm。
官方GitHub:

Make sure that you have the correct version of node, npm and nvm.

使用以下命令确认是否安装:

node --version
npm --version
nvm --version

以下是三者简单的介绍及关系说明:原文

  1. nvm的官方叫法:nodejs版本管理工具。
    nvm相当于是家长,一个家长可以管理多个孩子。
    也就是说:一个nvm可以管理很多node版本和npm版本。
  1. nodejs
    在项目开发时的所需要的代码库
  1. npm
    在安装的nodejs的时候,npm也会跟着一起安装,它是包管理工具。
    npm管理nodejs中的第三方插件
  1. nvm、nodejs、npm的关系:
    nvm是爸爸,管理nodejs和npm这一对双胞胎兄弟。npm是哥哥, npm哥哥可以管理node弟弟的东西。

前面已经讲过node的安装了,这里安装nvm。

官方GitHub:https://github.com/creationix/nvm

curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | bash

安装完成后查看是否成功:

yuyangdeMacBook-Pro:~ yuyang$ nvm --version
0.33.8

安装Browser-solidity

git clone https://github.com/ethereum/remix-ide.git
cd remix-ide
npm install

安装完成后,使用以下命令启动:

npm start

启动后,控制台会输出如下信息:

yuyangdeMacBook-Pro:~ yuyang$ cd /Users/yuyang/browser-solidity 
yuyangdeMacBook-Pro:browser-solidity yuyang$ npm start

> browser-solidity@0.0.0 start /Users/yuyang/browser-solidity
> npm-run-all -lpr serve watch onchange remixd

[serve   ] 
[serve   ] > browser-solidity@0.0.0 serve /Users/yuyang/browser-solidity
[serve   ] > execr --silent http-server .
[serve   ] 
[onchange] 
[onchange] > browser-solidity@0.0.0 onchange /Users/yuyang/browser-solidity
[onchange] > onchange build/app.js -- npm-run-all lint
[onchange] 
[remixd  ] 
[remixd  ] > browser-solidity@0.0.0 remixd /Users/yuyang/browser-solidity
[remixd  ] > node ./node_modules/remixd/src/main.js -s ./contracts
[remixd  ] 
[watch   ] 
[watch   ] > browser-solidity@0.0.0 watch /Users/yuyang/browser-solidity
[watch   ] > watchify src/index.js -dv -p browserify-reload -o build/app.js
[watch   ] 
[remixd  ] example: --dev-path /home/devchains/chain1 --mist --geth --frontend /home/frontend --frontend-port 8084 --auto-mine
[remixd  ] 
[remixd  ]   Usage: main -s <shared folder>
[remixd  ] 
[remixd  ]   Provide a two ways connection between the local computer and Remix IDE
[remixd  ] 
[remixd  ] 
[remixd  ]   Options:
[remixd  ] 
[remixd  ]     -s, --shared-folder <path>            Folder to share with Remix IDE
[remixd  ]     -m, --mist                            start mist
[remixd  ]     -g, --geth                            start geth
[remixd  ]     -p, --dev-path <dev-path>             Folder used by mist/geth to start the development instance
[remixd  ]     -f, --frontend <front-end>            Folder that should be served by remixd
[remixd  ]     -p, --frontend-port <front-end-port>  Http port used by the frontend (default 8082)
[remixd  ]     -a, --auto-mine                       mine pending transactions
[remixd  ]     -r, --rpc <cors-domains>              start rpc server. Values are CORS domain
[remixd  ]     -rp, --rpc-port                       rpc server port (default 8545)
[remixd  ]     -h, --help                            output usage information
[remixd  ] [WARN] Any application that runs on your computer can potentially read from and write to all files in the directory.
[remixd  ] [WARN] Symbolinc links are not forwarded to Remix IDE
[remixd  ] 
[remixd  ] [WARN] Symbolic link modification not allowed : ./contracts | /Users/yuyang/browser-solidity/contracts
[remixd  ] Shared folder : ./contracts
[remixd  ] Sat Feb 24 2018 16:15:42 GMT+0800 (CST) Remixd is listening on 127.0.0.1:65520
[watch   ] WS server listening on  51844

然后打开浏览器,在地址栏输入:http://127.0.0.1:8080,可以看到以下效果:

图中代码是我已经准备好的代码

使用 Browser-solidity 编译代码

下面是一个简单的智能合约代码,每次调用increment方法,count的值增加1。

pragma solidity ^0.4.20;

contract Counter {
	
	// 状态变量
    uint count = 0;
	//存储Counter合约owner
    address owner;

    event CountIncreased(address from, uint count);

	// 构造函数
    function Counter() {
       owner = msg.sender;
    } 

	// 函数
    function increment() public {
		// 判断是谁在调用`increment`方法
       if (owner == msg.sender) {
          count = count + 1;
       }

        // 激活CountIncreased事件
       emit CountIncreased(msg.sender, count);
    }
 
    // 函数
    function getCount() constant returns (uint) {
       return count;
    }

}

 

智能合约Solidity源代码分析

接下来试着做一些简单的分析,并介绍一些最基本的Solidity知识。

第一行

pragma solidity 0.4.20;
  • 第一行代码是必须的,否则编译器将不知道该如何选择编译器,以及编译器版本。
  • 第一行的pragma solidity 中pragma是关键词,代表程序开始,solidity 代表本智能合约是由Solidity语言所撰写。
  • 0.4.20 代表的是编译器版本,注意:从0.4.9起可以在前面不打^ ,0.4.8/0.4.7等版本还是需要打^,编译器版本向下兼容。

第二行

contract Counter {
       ...
}

这里引用一段说明Solidity里的Contract

Contracts in Solidity are similar to classes in object-oriented languages. They contain persistent data in state variables and functions that can modify these variables. Calling a function on a different contract (instance) will perform an EVM function call and thus switch the context such that state variables are inaccessible. (引用自 here)

中文翻译:

  • Solidity中Contract和面向对象语言中的类很相像。有带持久数据的变量,以及能改变这些变量的function. 在不同的Contract实例中调用一个function,将会执行一个在EVM(以太坊虚拟机)中的function调用。
  • 由此可见,Solidity中的智能合约和传统面向对象语言中的类很相像,因此有构造函数,有继承,有变量,有function,也有抽象类等等传统概念。
  • 由Solidity所写的智能合约,经过编译后就会由EVM来部署执行
    Solidity语言是一种类JS的语言,因此很多编码规范和JS很相似。

第三行

function f(uint a) returns (uint b) 
        {
            ...
        }
  • 上面说过,contract中包含了变量&方法(function)。function f(uint a) returns (uint b) 代表定义了一个名为f的方法,输入变量为uint a, 输出为uint b
  • uint 代表无状态的整型数字,即大于0的整数。
  • uint = uint256, 最大值为2的256次方,这个数字对于绝大多数的数学运算是足够得了。
  • 相对于uint来说还有带负数的整数类型,即int, int=int256, 取值范围从 负2的128次方到正2的128次方
  • uint/int类型细节请参见后面的Int类型介绍。或者点击here

 

更多关于Solidity的编码风格,请参考官方文档: https://solidity.readthedocs.io/en/develop/style-guide.html

Browser-solidity 细节详解

Setting标签

Setting

在Setting标签下可以查看当前的solidity的版本,并且要和代码框中的版本一致。

点击下拉框,可以选择不同的版本,包括还未成熟的最新构建版本,或者是之前的版本等。 个人强烈建议,尽量选择release版本,如下图所示的这些:

版本选择

Run标签

点击Create后的效果
  • 点击Create可以在Browser-solidity中创建并调用所创建的智能合约。
  • 输入数字,点击add,就可以进行合约的调用了。
  • 点击Details查看创建详情,可以看到实际创建所消耗的Gas。
transaction cost    101270 gas 
execution cost  36482 gas 

创建合约

刚刚通过点击Create已经创建了合约。

调试合约

add按钮旁的输入框输入数字,点击add进行调试,下方窗口会出现新的信息,点击Details`查看调试详情,可以看到实际调用所消耗的Gas。

点击add后的效果
transaction cost    21716 gas 
execution cost  252 gas 

Compile标签 & 编译合约

切换到Compile标签,点击Publish on Swarm
提示成功
点击Details
编译后的内容

稍微解释一下编译后的内容:

var counterContract = web3.eth.contract([{"constant":true,"inputs":[],"name":"getCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"increment","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]);
var counter = counterContract.new(
   {
     from: web3.eth.accounts[0], 
     data: '0x60806040526000805534801561001457600080fd5b5033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550610123806100656000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063a87d942c14604e578063d09de08a146076575b600080fd5b348015605957600080fd5b506060608a565b6040518082815260200191505060405180910390f35b348015608157600080fd5b5060886093565b005b60008054905090565b3373ffffffffffffffffffffffffffffffffffffffff16600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141560f5576001600054016000819055505b5600a165627a7a723058207536cc4ab02f1032a981376f82a77e005fdc754d53b5e1cddf1fc45c3065187e0029', 
     gas: '4700000'
   }, function (e, contract){
    console.log(e, contract);
    if (typeof contract.address !== 'undefined') {
         console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash);
    }
 })

 

WEB3DEPLOY

  • web3.deploy代码,是可以直接部署在geth网络上的一段部署代码。只要复制黏贴这段代码,就可以直接在一个区块链私有链上进行部署,并且调用它。
  • interface。是其他函数或者自身调用所必须通信的借口,它是该智能合约和外界沟通的核心。第一行就定义了一个interface类:
    var counterContract
    

    第二行counterContract就是根据这个接口类,生成一个实例。

  • from代表合约由哪个账户生成,哪个账户生成,则生成所需的gas就需要该账户承担,默认为eth.accounts[0],因为所有挖矿所得的以太币也默认都存入该账户中。
  • data代表的就是bytecode。是编译后的代码,你的代码越长,这块的字符串越多。
  • gas代表的是为了部署该合约最多准备的gas数量,当然实际上可能用不了这么多gas,具体消耗以实际使用量为准,这里只是设定一个最大量。gas是调用合约要扣除的gas单位,可以理解为以太币,gas和ether之间有个汇率,汇率受矿机的算率影响会有调整,在公网上,这些gas用于奖励给挖矿者。
  • 最后这段是一个典型的javascript的异步调用的写法,将上面的new方法的结果传递给下一个方法function(e,contract),在下一个方法中处理如果挖矿成功的显示结果。如果合约成功部署在区块上,则在控制台打印出来Contract mined!contract addresstransactionHash等信息。
  • address表示已经部署智能合约的帐户地址,智能合约也相当于一个帐户。
  • transactionHash表示智能合约产生时的hash值,会永久保存到区块链条里面。

将 Browser-solidity编译后的合约部署到Geth

发送部署合约的交易

> var counterContract = web3.eth.contract([{"constant":true,"inputs":[],"name":"getCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"increment","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]);
undefined
> var counter = counterContract.new(
... {
...... from: web3.eth.accounts[0], 
...... data: '0x60806040526000805534801561001457600080fd5b5033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550610123806100656000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063a87d942c14604e578063d09de08a146076575b600080fd5b348015605957600080fd5b506060608a565b6040518082815260200191505060405180910390f35b348015608157600080fd5b5060886093565b005b60008054905090565b3373ffffffffffffffffffffffffffffffffffffffff16600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141560f5576001600054016000819055505b5600a165627a7a7230582008fc8827b7a469b6ac5f6e9516c75d284bce54ce16628cb794d014c6ddfde13b0029', 
...... 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]
> miner.start()
null
> null [object Object]
Contract mined! address: 0xbc35a8d267b05cf67495b91384485c5f2e76cae0 transactionHash: 0x899e6daa82483672f7483806ed36e7497d3fbd34da6498a8f0b1c7f178c5126f

这里如果出现Error: exceeds block gas limit undefined的报错信息,请查看这篇文章

输入counter可以看到合约的一些信息

> counter
{
 abi: [{
 constant: true,
 inputs: [],
 name: "getCount",
 outputs: [{...}],
 payable: false,
 stateMutability: "view",
 type: "function"
 }, {
 constant: false,
 inputs: [],
 name: "increment",
 outputs: [],
 payable: false,
 stateMutability: "nonpayable",
 type: "function"
 }, {
 inputs: [],
 payable: false,
 stateMutability: "nonpayable",
 type: "constructor"
 }],
 address: "0xbc35a8d267b05cf67495b91384485c5f2e76cae0",
 transactionHash: "0x899e6daa82483672f7483806ed36e7497d3fbd34da6498a8f0b1c7f178c5126f",
 allEvents: function(),
 getCount: function(),
 increment: function()
}

查看本地交易池中待确认的交易

输入txpool.status

> txpool.status
{
  pending: 1,
  queued: 0
}

可以看到交易池中有一个待确认的交易

查看待确认交易的详情

使用命令web3.eth.getTransaction,参数为transactionHash:

>web3.eth.getTransaction("0x2f659199d3aceee8d61d90b42260e0aceeebf8ce30f6555896c67b1696a6b376")
{
  blockHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
  blockNumber: null,
  from: "0xb6cd75af6594f46374378cf3a7d9cbfc06485994",
  gas: 4700000,
  gasPrice: 18000000000,
  hash: "0x2f659199d3aceee8d61d90b42260e0aceeebf8ce30f6555896c67b1696a6b376",
  input: "0x6060604052341561000f57600080fd5b60b68061001d6000396000f300606060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680631003e2d2146044575b600080fd5b3415604e57600080fd5b606260048080359060200190919050506078565b6040518082815260200191505060405180910390f35b600080606483019050809150509190505600a165627a7a723058208f5727572e0f6113593417a5211692c99ed69158e62764f4e3f76c0c5846afc30029",
  nonce: 2,
  r: "0x98087a648c5a1307206820863ee889b8ff874a72033a323bac3b246294d70d7b",
  s: "0xe73e891d87b8a11b21268cf6e6fea70ea2b6dc4dd37e6e71f3d4c1a5f08ed26",
  to: null,
  transactionIndex: 0,
  v: "0x37",
  value: 0
}

由于交易还未确认,所以所属区块为null,blockNumber: null

获取合约实例

通过ABI和合约address可以获取到合约的实例:

var abi = /* abi as generated by the compiler */;
var counterContract = web3.eth.contract(abi);
var counter = counterContract.at("0x123..." /* address */);

 

 

调用合约

counter.increment.sendTransaction({from: acc0})
counter.getCount.call()

 

通过交易修改状态变量的值:

counter.increment.sendTransaction({from: acc0})

通过调用读取状态变量的值:
counter.getCount.call()

> acc0 = web3.eth.accounts[0]
> counter.increment.sendTransaction({from: acc0});
"0x25a2639fda8e5187419d0e9a297d75155b25b834ba464dbcf5b4d7e98d329225"
> counter.getCount.call()
1

出现TypeError: Cannot access member ‘call’ of undefined的错误,是因为我们没有挖矿,之前提到过,如果停止挖矿,是不能进行转帐和智能合约的部署。

重新启动挖矿,等待一段时间,停止挖矿,调用合约,就可以输出正确的值了。

> miner.start()

这时再查询交易详情,可以看到已经写到编号为21的区块上了blockNumber: 21

> web3.eth.getTransaction("0x2f659199d3aceee8d61d90b42260e0aceeebf8ce30f6555896c67b1696a6b376")
{
  blockHash: "0xe88ca3e86d7bca40effd2b90e821818cf90c35b4801f8cc949b4a7a14fdcadde",
  blockNumber: 21,
  from: "0xb6cd75af6594f46374378cf3a7d9cbfc06485994",
  gas: 4700000,
  gasPrice: 18000000000,
  hash: "0x2f659199d3aceee8d61d90b42260e0aceeebf8ce30f6555896c67b1696a6b376",
  input: "0x6060604052341561000f57600080fd5b60b68061001d6000396000f300606060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680631003e2d2146044575b600080fd5b3415604e57600080fd5b606260048080359060200190919050506078565b6040518082815260200191505060405180910390f35b600080606483019050809150509190505600a165627a7a723058208f5727572e0f6113593417a5211692c99ed69158e62764f4e3f76c0c5846afc30029",
  nonce: 2,
  r: "0x98087a648c5a1307206820863ee889b8ff874a72033a323bac3b246294d70d7b",
  s: "0xe73e891d87b8a11b21268cf6e6fea70ea2b6dc4dd37e6e71f3d4c1a5f08ed26",
  to: null,
  transactionIndex: 2,
  v: "0x37",
  value: 0
}

此时交易池中也没有等待确认的交易了。

> txpool.status
{
  pending: 0,
  queued: 0
}

激活合约事件

// 激活CountIncreased事件
emit CountIncreased(msg.sender, count);

客户端监听合约事件

var abi = /* abi as generated by the compiler */;
var counterContract = web3.eth.contract(abi);
var counter = counterContract.at(0x123 /* address */);

var event = counter.CountIncreased();
// 事件监听
event.watch(function(error, result){
    // result包含了CountIncreased调用的参数
    if (!error)
        console.log(result);
});

// 或者传递一个回调函数监听事件
var event = counter.CountIncreased(function(error, result) {
    if (!error)
        console.log(result);
});

 

作者:yuyangray

链接:https://www.jianshu.com/p/91d775d20599
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

发表评论

电子邮件地址不会被公开。 必填项已用*标注

标签云