4.2 合约开发入门

4.2.1 注意事项

  1. 在编写智能合约前,需要对区块链基础有一定的了解。(附:ethereum-overviewSolidity)
  2. Ethereum Virtual Machine 是在以太坊上为智能合约提供运行时环境的虚拟机。迅雷链开放平台兼容EVM,但需遵循官方平台的平台使用约束。
  3. 账户类型分为外部账户(普通的交易账户地址)和合约账户。
  4. 创建合约就是向目标账户地址0发送交易的过程。
  5. 合约部署和调用过程中(改变合约内部状态变量)会消耗gas,gas_price*gas是每次交易产生的手续费。

4.2.2 compile

  1. 可以使用官方合约编译IDE Catalyst编写和部署调试合约。
  2. 可以使用浏览器编译调试运行智能合约环境Remix来快速编写合约,无依赖环境配置。
  3. 使用npm包solcjs,全局安装npm i -g solc,命令行使用solc编译对应合约。
  4. 使用 truffle 的 compile 命令。
  5. 本平台推荐使用Catalyst进行开发。

Catalyst使用指南 智能合约solidity开发框架truffle。 提供了一套完善的开发、调试、编译、部署、测试的本地环境。

4.2.3 安装truffle

  1. npm i -g truffle
  2. [root@opennode sandai]# truffle version
  3. Truffle v4.1.5 (core: 4.1.5)
  4. Solidity v0.4.21 (solc-js)

4.2.3 开始

  1. 使用truffle 初始化合约工程

    1. mkdir simple-storage
    2. cd simple-storage
    3. truffle init
  2. 新建合约文件:可以使用 truffle create contract SimpleStorage 命令行新建,也可以直接新建文件 contract/SimpleStorage.sol

    1. // SimpleStorage.sol
    2. pragma solidity ^0.4.21;
    3. contract SimpleStorage {
    4. uint myVariable;
    5. function set(uint x) public {
    6. myVariable = x;
    7. }
    8. function get() constant public returns (uint) {
    9. return myVariable;
    10. }
    11. }
  3. 添加migrate脚本:可以使用truffle create migration 2_deploy_contract 命令行方式新增,也可以直接新建文件 migrations/2_deploy_contract.js

    1. // 2_deploy_contract.js;truffle migrate命令的执行顺序与文件名有关,所以多个部署脚本需要按照顺序命名
    2. var SimpleStorage = artifacts.require("SimpleStorage");
    3. module.exports = function(deployer) {
    4. deployer.deploy(SimpleStorage);
    5. };
  4. 执行truffle compile编译合约,编译后的合约在build文件夹下。每个合约有一个对应的json文件,内含部署所需的bytecode,abiCode等

  5. 编辑 truffle.js ,设置truffle部署合约及与区块链交互的rpc连接。

    1. [root@localhost opennode]# vi truffle.js
    2. module.exports = {
    3. networks: {
    4. development: {
    5. host: "127.0.0.1",
    6. port: 8545,
    7. network_id: "*"
    8. }
    9. }
    10. };
  6. 控制台开启truffle默认的区块链环境。

    1. truffle develop
    2. Truffle Develop started at http://127.0.0.1:9545/
    3. Accounts:
    4. (0) 0x627306090abab3a6e1400e9345bc60c78a8bef57
    5. (1) 0xf17f52151ebef6c7334fad080c5704d77216b732
    6. (2) 0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef
    7. (3) 0x821aea9a577a9b44299b9c15c88cf3087f3b5544
    8. (4) 0x0d1d4e623d10f9fba5db95830f7d3839406c6af2
    9. (5) 0x2932b7a2355d6fecc4b5c0b6bd44cc31df247a2e
    10. (6) 0x2191ef87e392377ec08e7c08eb105ef5448eced5
    11. (7) 0x0f4f2ac550a1b4e2280d04c21cea7ebd822934b5
    12. (8) 0x6330a553fc93768f612722bb8c2ec78ac90b3bbc
    13. (9) 0x5aeda56215b167893e80b4fe645ba6d5bab767de
    14. Private Keys:
    15. (0) c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3
    16. (1) ae6ae8e5ccbfb04590405997ee2d52d2b330726137b875053c36d94e974d162f
    17. (2) 0dbbe8e4ae425a6d2687f1a7e3ba17bc98c673636790f1b8ad91193c05875ef1
    18. (3) c88b703fb08cbea894b6aeff5a544fb92e78a18e19814cd85da83b71f772aa6c
    19. (4) 388c684f0ba1ef5017716adb5d21a053ea8e90277d0868337519f97bede61418
    20. (5) 659cbb0e2411a44db63778987b1e22153c086a95eb6b18bdf89de078917abc63
    21. (6) 82d052c865f5763aad42add438569276c00d3d88a2d062d36b2bae914d58b8c8
    22. (7) aa3680d5d48a8283413f7a108367c7299ca73f553735860a87b08f39395618b7
    23. (8) 0f62d96d6675f32685bbdb8ac13cda7c23436f63efbb9d07700d8669ff12b7c4
    24. (9) 8d5366123cb560bb606379f90a0bfd4769eecc0557f1b362dcae9012b548b1e5
    25. Mnemonic: candy maple cake sugar pudding cream honey rich smooth crumble sweet treat
    26. ⚠️ Important ⚠️ : This mnemonic was created for you by Truffle. It is not secure.
    27. Ensure you do not use it on production blockchains, or else you risk losing funds.
    28. truffle(develop)>

    这为truffle运行合约提供了本地的区块链环境,默认生成10个账户,每个账户初始余额为100ether。 也可以使用Ganache提供的图形化界面应用,需要修改配置连接的端口。

  7. 在一个新的控制台执行truffle migrate移植部署合约(或者在truffle develop控制台执行 migrate)。

  8. 使用truffle develop测试合约代码。

    1. SimpleStorage.deployed().then(function(instance){return instance.get.call();}).then(function(value){return value.toNumber()})
    2. // 0
    3. SimpleStorage.deployed().then(function(instance){return instance.set(100);});
    4. // 输出transaction信息
    5. SimpleStorage.deployed().then(function(instance){return instance.get.call();}).then(function(value){return value.toNumber()});
    6. // 100
  9. 使用truffle test 测试合约 使用truffle create test SimpleStorage新建或直接新建文件test/SimpleStorage.test.js。

    1. const SimpleStorage = artifacts.require('SimpleStorage');
    2. contract('SimpleStorage', function(accounts) {
    3. it("should assert true", function(done) {
    4. var simpleStorage = SimpleStorage.deployed();
    5. var instance;
    6. simpleStorage.then(res => {
    7. instance = res;
    8. return instance.get()
    9. }).then(value => {
    10. assert.equal('0', value.toNumber(), 'not equal 0')
    11. }).then(() => {
    12. instance.set(100)
    13. }).then(() => {
    14. return instance.get()
    15. }).then(value => {
    16. assert.equal('100', value.toNumber(), 'not equal 100')
    17. })
    18. done();
    19. });
    20. });

    在新开的控制台里,输入truffle test ./test/SimpleStorage.test.js

  10. 使用remix测试合约 将使用truffle 开发的合约,放在remix里,可快速模拟合约的部署和调用。 remix提供了合约的编译运行环境,并可以在控制台看到合约每条交易的详细信息,如输入输出参数,签名后的方法data,交易hash等信息。支持调试。

    1. 使用compile detail,可以看到合约编译详情。包括bytecode,abi和使用web3.js快速部署的方法。 图片1.png

    2. 使用run来create 合约,控制台可查看创建合约的交易。 图片.png

4.2.4 使用truffle unbox创建可交互合约应用

上面的步骤使用基本的truffle init创建了一个可编译部署调试的合约环境。 下面使用truffle unbox创建一个新的工程,unbox为我们提供了truffle工程模板,内置了一些合约应用交互的环境依赖。 可以在truffle boxes里查看官方提供的各种模板boxes。 下面使用的是react模板.

  1. 新建工程 truf-react

    1. mkdir truf-react
    2. cd truf-react
    3. truffle unbox react

    unbox过程会下载解压模板,执行npm install等操作。

  2. 配置项目的truffle.js

    1. module.exports = {
    2. // See <http://truffleframework.com/docs/advanced/configuration>
    3. // to customize your Truffle configuration!
    4. networks: {
    5. development: {
    6. host: '127.0.0.1',
    7. port: '9545',
    8. network_id: '*' // Match any network id
    9. }
    10. }
    11. };
  3. 启动一个truffle develop

  4. 修改src/App.js

    1. import React, { Component } from 'react'
    2. import SimpleStorageContract from '../build/contracts/SimpleStorage.json'
    3. import getWeb3 from './utils/getWeb3'
    4. import './css/oswald.css'
    5. import './css/open-sans.css'
    6. import './css/pure-min.css'
    7. import './App.css'
    8. const contract = require('truffle-contract')
    9. const simpleStorage = contract(SimpleStorageContract)
    10. class App extends Component {
    11. constructor(props) {
    12. super(props)
    13. this.state = {
    14. storageValue: 0,
    15. web3: null,
    16. inputValue: 0,
    17. address: null
    18. }
    19. this.changeValueHandle = this.changeValueHandle.bind(this)
    20. this.setHandle = this.setHandle.bind(this)
    21. }
    22. componentWillMount() {
    23. // Get network provider and web3 instance.
    24. // See utils/getWeb3 for more info.
    25. getWeb3
    26. .then(results => {
    27. this.setState({
    28. web3: results.web3
    29. })
    30. // Instantiate contract once web3 provided.
    31. this.instantiateContract()
    32. })
    33. .catch(() => {
    34. console.log('Error finding web3.')
    35. })
    36. }
    37. instantiateContract() {
    38. /*
    39. * SMART CONTRACT EXAMPLE
    40. *
    41. * Normally these functions would be called in the context of a
    42. * state management library, but for convenience I've placed them here.
    43. */
    44. this.simpleStorageSet(5)
    45. }
    46. changeValueHandle(event) {
    47. this.setState({
    48. inputValue: Number(event.target.value)
    49. })
    50. }
    51. setHandle() {
    52. this.simpleStorageSet(this.state.inputValue)
    53. }
    54. simpleStorageSet(x) {
    55. simpleStorage.setProvider(this.state.web3.currentProvider)
    56. // Declaring this for later so we can chain functions on SimpleStorage.
    57. var simpleStorageInstance
    58. // Get accounts.
    59. this.state.web3.eth.getAccounts((error, accounts) => {
    60. simpleStorage.deployed().then((instance) => {
    61. simpleStorageInstance = instance
    62. this.setState({ address: instance.address })
    63. // Stores a given value, 5 by default.
    64. return simpleStorageInstance.set(x, {from: accounts[0]})
    65. }).then((result) => {
    66. // Get the value from the contract to prove it worked.
    67. return simpleStorageInstance.get.call(accounts[0])
    68. }).then((result) => {
    69. // Update state with the result.
    70. return this.setState({ storageValue: result.c[0] })
    71. })
    72. })
    73. }
    74. render() {
    75. return (
    76. <div className="App">
    77. <nav className="navbar pure-menu pure-menu-horizontal">
    78. <a href="#" className="pure-menu-heading pure-menu-link">Truffle Box</a>
    79. </nav>
    80. <main className="container">
    81. <div className="pure-g">
    82. <div className="pure-u-1-1">
    83. <h1>Good to Go!</h1>
    84. <p>Your Truffle Box is installed and ready.</p>
    85. <h2>Smart Contract Example</h2>
    86. <p>If your contracts compiled and migrated successfully, below will show a stored value of 5 (by default).</p>
    87. <p>Try changing the value stored on <strong>line 59</strong> of App.js.</p>
    88. <p>The stored value is: {this.state.storageValue}</p>
    89. <p>deployed contract address: {this.state.address}</p>
    90. </div>
    91. <div>
    92. <input type="number" onChange={this.changeValueHandle}/>
    93. <button onClick={this.setHandle}>set</button>
    94. </div>
    95. </div>
    96. </main>
    97. </div>
    98. );
    99. }
    100. }
    101. export default App

    新增了合约set方法的调用。并展示了合约的address。

  5. 新打开一个控制台,执行 npm run start

  6. 浏览器打开 http://lcoalhost:3000,可看到合约的结果

  7. 通过set和输入框设置合约storedData的值。

  8. 在trufle develop里输入

    1. //将xxx替换为address
    2. SimpleStorage.at('xxxx').then(res => {return res.get()})
    3. // 得到BigNUmber类型的返回值,c数组里的值即为设置的storedData的值。

4.2.5 使用浏览器插件 metamask 与区块链交互

参考 http://truffleframework.com/tutorials/pet-shop

4.2.6 合约开发参考文档与工具

solidity API
truffle文档
ganache 提供本地区块链环境的图形化界面
zeppelin-solidty 致力于安全的标准化的合约框架
web3.js 以太坊封装的与区块链交互的js
Smart Contract Security Best Practices(合约安全开发指南)