以太坊开发(五)使用 Browser-solidity 在 Go
基本概念
智能合约
智能合约是存储在区块链上的一段代码,它们可以被区块链上的交易所触发,触发后,这段代码可以从区块链上读取数据或者向区块链上写入数据。
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/browser-solidity
本文环境:
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
以下是三者简单的介绍及关系说明:原文
- nvm的官方叫法:nodejs版本管理工具。
nvm相当于是家长,一个家长可以管理多个孩子。
也就是说:一个nvm可以管理很多node版本和npm版本。
- nodejs
在项目开发时的所需要的代码库
- npm
在安装的nodejs的时候,npm也会跟着一起安装,它是包管理工具。
npm管理nodejs中的第三方插件
- 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 browser-solidity
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 编译代码
下面是一个简单的智能合约代码,输入任何数值,都加上100。
pragma solidity 0.4.20;
contract testContract {
function add(uint a) public returns (uint b) {
uint resutl = a + 100;
return (resutl);
}
}
智能合约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 testContract {
...
}
这里引用一段说明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。
Function的核心代码
function add(uint a) public returns (uint b) {
uint resutl = a + 100;
return (resutl);
}
这是一段很平常的js代码,值得注意的是以下两点:
- Solidity是一个类型语言,因此每个变量都需要定义他的类型,uint/int/string/var。
Solidity is a statically typed language, which means that the type of each variable (state and local) needs to be specified (or at least known – see Type Deduction below) at compile-time. Solidity provides several elementary types which can be combined to form complex types. 引用自here。
-
关于编码风格
uint result = a + 100;
,solidity鼓励在操作符中有一个空格。More than one space around an assignment or other operator to align with 引用自here。
Yes:
x = 1;
y = 2;
long_variable = 3;
更多关于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。
transaction cost 21716 gas
execution cost 252 gas
Compile标签 & 编译合约
切换到Compile标签,点击Publish on Swarm 提示成功 点击Details 编译后的内容稍微解释一下编译后的内容:
var testcontractContract = web3.eth.contract([{"constant":false,"inputs":[{"name":"a","type":"uint256"}],"name":"add","outputs":[{"name":"b","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"}]);
var testcontract = testcontractContract.new(
{
from: web3.eth.accounts[0],
data: '0x6060604052341561000f57600080fd5b60b68061001d6000396000f300606060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680631003e2d2146044575b600080fd5b3415604e57600080fd5b606260048080359060200190919050506078565b6040518082815260200191505060405180910390f35b600080606483019050809150509190505600a165627a7a723058208f5727572e0f6113593417a5211692c99ed69158e62764f4e3f76c0c5846afc30029',
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 testcontractContract
第二行
testcontract
就是根据这个接口类,生成一个实例。 -
from代表合约由哪个账户生成,哪个账户生成,则生成所需的gas就需要该账户承担,默认为eth.accounts[0],因为所有挖矿所得的以太币也默认都存入该账户中。
-
data代表的就是bytecode。是编译后的代码,你的代码越长,这块的字符串越多。
-
gas代表的是为了部署该合约最多准备的gas数量,当然实际上可能用不了这么多gas,具体消耗以实际使用量为准,这里只是设定一个最大量。gas是调用合约要扣除的gas单位,可以理解为以太币,gas和ether之间有个汇率,汇率受矿机的算率影响会有调整,在公网上,这些gas用于奖励给挖矿者。
-
最后这段是一个典型的javascript的异步调用的写法,将上面的new方法的结果传递给下一个方法
function(e,contract)
,在下一个方法中处理如果挖矿成功的显示结果。如果合约成功部署在区块上,则在控制台打印出来Contract mined!
、contract address
、transactionHash
等信息。 -
address表示已经部署智能合约的帐户地址,智能合约也相当于一个帐户。
-
transactionHash表示智能合约产生时的hash值,会永久保存到区块链条里面。
将 Browser-solidity编译后的合约部署到Geth
发送部署合约的交易
> var testcontract = testcontractContract.new(
... {
...... from: web3.eth.accounts[0],
...... data: '0x6060604052341561000f57600080fd5b60b68061001d6000396000f300606060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680631003e2d2146044575b600080fd5b3415604e57600080fd5b606260048080359060200190919050506078565b6040518082815260200191505060405180910390f35b600080606483019050809150509190505600a165627a7a723058208f5727572e0f6113593417a5211692c99ed69158e62764f4e3f76c0c5846afc30029',
...... gas: '4700000'
...... }, function (e, contract){
...... console.log(e, contract);
...... if (typeof contract.address !== 'undefined') {
......... console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash);
......... }
...... })
INFO [02-25|18:18:54] Submitted contract creation fullhash=0x2f659199d3aceee8d61d90b42260e0aceeebf8ce30f6555896c67b1696a6b376 contract=0x810B81978509E6E8110C7A2eF7d2837d8054dcC6
null [object Object]
undefined
这里如果出现Error: exceeds block gas limit undefined
的报错信息,请查看这篇文章。
输入testcontract可以看到合约的一些信息
> testcontract
{
abi: [{
constant: false,
inputs: [{...}],
name: "add",
outputs: [{...}],
payable: false,
stateMutability: "nonpayable",
type: "function"
}],
address: undefined,
transactionHash: "0x2f659199d3aceee8d61d90b42260e0aceeebf8ce30f6555896c67b1696a6b376"
}
查看本地交易池中待确认的交易
输入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
。
调用合约
通过testcontract.add.call调用:
> testcontract.add.call(10)
TypeError: Cannot access member 'call' of undefined
at <anonymous>:1:1
出现TypeError: Cannot access member 'call' of undefined的错误,是因为我们没有挖矿,之前提到过,如果停止挖矿,是不能进行转帐和智能合约的部署。
重新启动挖矿,等待一段时间,停止挖矿,调用合约,就可以输出正确的值了。
> miner.start()
.... ....(mining)
> miner.stop()
> testcontract.add.call(10)
110
> testcontract.add.call(100)
200
这时再查询交易详情,可以看到已经写到编号为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
}
参考: