使用Truffle 部署智能合约

之前我们使用Geth,原生的以太坊Golang工具,分析了创世区块的参数内容,在本地创建了私有以太坊区块链,并使用两个账户进行了挖矿和转账操作,对以太坊有了基本了解。

该篇章开始使用一个新的平台Truffle Suite,学习部署示例的智能合约,和一个稍微复杂一些的实用智能合约,学习Solidity语言的基本语法和智能合约的使用。

本文绝大多数参考资料来源于Solidity官方文档Truffle官方文档

1. 安装Truffle

Truffle Suite套件包括三个组件:

  • Truffle:命令行工具,用来部署智能合约
  • Ganache:GUI工具,用来可视化查看区块、账户、合约、交易等内容
  • drizzle:Javascript库,用于前端开发

虽然Truffle套件在全平台通用,但在Windows上可能会出现莫名其妙的命名空间冲突问题,本次全部使用Ubuntu进行操作。

使用npm即可安装Truffle:(如何安装npm,换源等问题不在本篇的讨论范围内)

  1. npm install -g truffle

后续使用Ganache做可视化浏览,Ganache为Linux提供了Appimage打包,下载后记得为其赋予可执行属性才能打开:

  1. 下载地址:https://github.com/trufflesuite/ganache/releases
  2. chmod +x ganache-<Version>-linux-x86_64.AppImage

2. 学习示例智能合约 MetaCoin

2.1 准备

Truffle作为集成平台,提供了类似npm的功能,使用truffle unbox <projectname>,可以下载其他人发布的智能合约。

Metacoin是一个非常简单的智能合约,他设计了一种新货币Metacoin(下文可能称其为代币),其汇率为 1 Metacoin = 2 ETH,使用Metacoin智能合约可以进行Metacoin转账等操作。

新建文件夹Metacoin,打开终端输入:

  1. truffle unbox metacoin

如果执行unbox命令时提示 RequestError: Error: connect ECONNREFUSED 错误,可以尝试使用export https_proxy为bash设置代理,加快访问速度

下载成功后,会看到文件夹内多出了一下内容:

其中contracts文件夹内有三个sol文件,是Solidity语言编写的只能合约文件;

migrations文件夹中的两个js文件是truffle部署智能合约时的部署文件,用来管理和升级智能合约,而且这些文件执行是有顺序的,必须以数字为开头;

test文件夹中是测试文件,可以使用js或者solidity语言编写测试脚本;

truffle-config.js 是truffle 的配置文件,包含truffle使用什么版本的编译器,在什么端口开放区块链的rpc协议等;

LICENSE为该代码的许可证。

2.2 交互

我们首先演示一下这个智能合约的实际效果,之后观察代码思考其运行的方法。

要部署智能合约,我们首先需要生成一条区块链。Truffle 可以快速帮我们生成开发环境的区块链,并构造出10个账户:

  1. truffle develop

可以看到,一条新的区块链已经生成,并在9545端口打开了http服务(rpc服务),并预先生成了10个账户,每个账户中默认存有100个ETH,当前的控制台使用的是默认的第0个账户。

在truffle控制台可以使用Web3进行交互,例如:

  1. truffle(develop)> web3.eth.getAccounts()
  2. [ '0xB836A85f25f9Ab41290f2a63D1Ee83AEa9F53b94',
  3. '0x0fC57BdDf263df2C70A5468B15b6fD620a366Cb4',
  4. '0x9A2219312B49cd833650067427874204dC5e261c',
  5. '0xfB440A02DCE4Aea19374902b57bEDEb23342d38f',
  6. '0x78d551ECe5749D3453960460D337b283F6315174',
  7. '0x737173efe01E9B720A310535fa513a23099d6fa2',
  8. '0x39313f35e7549aEE9Df037936190a923a897B437',
  9. '0x6F12D8eaC6996ba70Ca12e44E47669FEEDFD7ED7',
  10. '0xDbe225FAc5F4CA0f74466af1b0625d2d7a4C7c75',
  11. '0x56c467638B135C8584d871b1F468B8bb2363Db1a' ]
  12. truffle(develop)> web3.eth.getBalance('0xB836A85f25f9Ab41290f2a63D1Ee83AEa9F53b94')
  13. '100000000000000000000'

部署智能合约之前,需要编译sol文件:

  1. truffle(develop)> compile

同时我们会看到Metacoin文件夹内多出来一个build文件夹,其中存放了编译好的智能合约。使用migrate命令部署之恩那个合约:

  1. truffle(develop)> migrate

结果如下:

  1. truffle(develop)> migrate
  2. Compiling your contracts...
  3. ===========================
  4. > Everything is up to date, there is nothing to compile.
  5. Starting migrations...
  6. ======================
  7. > Network name: 'develop'
  8. > Network id: 5777
  9. > Block gas limit: 6721975 (0x6691b7)
  10. 1_initial_migration.js
  11. ======================
  12. Deploying 'Migrations'
  13. ----------------------
  14. > transaction hash: 0x9d236e01303e2fc44c0717733120fe28669d5f2dacdd2b66561170331e72ff35
  15. > Blocks: 0 Seconds: 0
  16. > contract address: 0x65ae7471c845a10049053a15Be43EE86E76cF1F5
  17. > block number: 1
  18. > block timestamp: 1611852614
  19. > account: 0xB836A85f25f9Ab41290f2a63D1Ee83AEa9F53b94
  20. > balance: 99.9967165
  21. > gas used: 164175 (0x2814f)
  22. > gas price: 20 gwei
  23. > value sent: 0 ETH
  24. > total cost: 0.0032835 ETH
  25. > Saving migration to chain.
  26. > Saving artifacts
  27. -------------------------------------
  28. > Total cost: 0.0032835 ETH
  29. 2_deploy_contracts.js
  30. =====================
  31. Deploying 'ConvertLib'
  32. ----------------------
  33. > transaction hash: 0xb2e3678a744446e6d3d98a43f3195994666b0e948f87b24eb5612ab20dcf08f9
  34. > Blocks: 0 Seconds: 0
  35. > contract address: 0xb59dBD1609f0982B0f7d64d3592D8390092442C7
  36. > block number: 3
  37. > block timestamp: 1611852614
  38. > account: 0xB836A85f25f9Ab41290f2a63D1Ee83AEa9F53b94
  39. > balance: 99.99396028
  40. > gas used: 95470 (0x174ee)
  41. > gas price: 20 gwei
  42. > value sent: 0 ETH
  43. > total cost: 0.0019094 ETH
  44. Linking
  45. -------
  46. * Contract: MetaCoin <--> Library: ConvertLib (at address: 0xb59dBD1609f0982B0f7d64d3592D8390092442C7)
  47. Deploying 'MetaCoin'
  48. --------------------
  49. > transaction hash: 0xf9b71d6dca179dddeac7bc1fba8d52f0b1ba430ac6c623aa4aeb7ea5ece09110
  50. > Blocks: 0 Seconds: 0
  51. > contract address: 0x8Baf7f61EEBb19eB22cC165AC9291338bF857522
  52. > block number: 4
  53. > block timestamp: 1611852614
  54. > account: 0xB836A85f25f9Ab41290f2a63D1Ee83AEa9F53b94
  55. > balance: 99.98822922
  56. > gas used: 286553 (0x45f59)
  57. > gas price: 20 gwei
  58. > value sent: 0 ETH
  59. > total cost: 0.00573106 ETH
  60. > Saving migration to chain.
  61. > Saving artifacts
  62. -------------------------------------
  63. > Total cost: 0.00764046 ETH
  64. Summary
  65. =======
  66. > Total deployments: 3
  67. > Final cost: 0.01092396 ETH

可以看到,由于刚刚编译过sol文件,部署时跳过了编译,直接使用1_initial_migration.js和2_deploy_contracts.js部署智能合约,最终消耗了0.01092396 ETH

  1. truffle(develop)> web3.eth.getBalance('0xB836A85f25f9Ab41290f2a63D1Ee83AEa9F53b94')
  2. '99987682400000000000'

Truffle控制台支持async/await 方法,我们新建变量时更加方便了。新建一个变量instance,为刚刚部署的合约的实例。

  1. truffle(development)> let instance = await MetaCoin.deployed()

查看账户余额(代币的余额,即Metacoin的余额):

  1. truffle(develop)> let balance = await instance.getBalance('0xB836A85f25f9Ab41290f2a63D1Ee83AEa9F53b94')
  2. undefined
  3. truffle(develop)> balance.toNumber()
  4. 10000

查看以太坊汇率转换后的余额:

  1. truffle(develop)> let ether = await instance.getBalanceInEth('0xB836A85f25f9Ab41290f2a63D1Ee83AEa9F53b94')
  2. undefined
  3. truffle(develop)> ether.toNumber()
  4. 20000

向第1个账户发送一些代币:

  1. truffle(develop)> instance.sendCoin('0x0fC57BdDf263df2C70A5468B15b6fD620a366Cb4', 500)

查看其余额:

  1. truffle(development)> let received = await instance.getBalance('0x0fC57BdDf263df2C70A5468B15b6fD620a366Cb4')
  2. undefined
  3. truffle(development)> received.toNumber()
  4. 500

2.3 解析

接下来我们详细分析三个sol文件:

  1. Migrations.sol

    Migrations文件是使用truffle部署智能合约时必要的文件,其内容一般不会变。

  2. MetaCoin.sol

  1. // SPDX-License-Identifier: MIT
  2. pragma solidity >=0.4.25 <0.7.0;
  3. // 第一行声明了solidity的编译器版本
  4. import "./ConvertLib.sol";
  5. // 表示引用了当前目录下的库文件ConvertLib.sol
  6. // 定义了一个合约,名为MetaCoin
  7. contract MetaCoin {
  8. // 变量balances本身时一个address类型,但被映射为无符号整型
  9. mapping (address => uint) balances;
  10. // 事件用来记录日志
  11. event Transfer(address indexed _from, address indexed _to, uint256 _value);
  12. // constructor函数为构造函数,在合约部署时运行
  13. // tx.origin 是一个特殊的全局变量,意味最初调用合约的账户地址
  14. // 向部署调用合约的人的余额添加10000个代币
  15. constructor() public {
  16. balances[tx.origin] = 10000;
  17. }
  18. // sendCoin函数接收两个参数(收件人和代币数量),返回布尔值
  19. function sendCoin(address receiver, uint amount) public returns(bool sufficient) {
  20. // 首先判断调用合约的人(发件人)的余额,如果余额小于要发送的代币数量,则返回false
  21. // msg.sender是一个特殊的变量,意味调用合约的账户的地址
  22. if (balances[msg.sender] < amount) return false;
  23. // 修改发件人和收件人的余额
  24. balances[msg.sender] -= amount;
  25. balances[receiver] += amount;
  26. // 记录这个event
  27. emit Transfer(msg.sender, receiver, amount);
  28. // 最终返回 true
  29. return true;
  30. }
  31. // 查看代币转换为以太币后的价值
  32. function getBalanceInEth(address addr) public view returns(uint){
  33. // 调用了ConvertLib中的汇率转换函数
  34. return ConvertLib.convert(getBalance(addr),2);
  35. }
  36. // 查看代币的数量
  37. function getBalance(address addr) public view returns(uint) {
  38. return balances[addr];
  39. }
  40. }
  1. ConvertLib.sol
  1. // SPDX-License-Identifier: MIT
  2. pragma solidity >=0.4.25 <0.7.0;
  3. library ConvertLib{
  4. // convert函数接受两个参数:amount和conversionRate,即货币数量和汇率,返回uint转换后的货币数量
  5. // 该文件主要是为我们演示了如何在Solidity中引用Library
  6. function convert(uint amount,uint conversionRate) public pure returns (uint convertedAmount)
  7. {
  8. return amount * conversionRate;
  9. }
  10. }

convert函数接受两个参数:amount和conversionRate,即货币数量和汇率,返回uint转换后的货币数量

该文件主要是为我们演示了如何在Solidity中引用Library

深入理解Truffle部署配置

上述三个文件完成了智能合约内容的编写,要部署合约,需要使用migrations中的两个js文件:

//TODO: 有关合约迁移的具体内容,会在后续补充

  1. 1_initial_migration.js
  1. const Migrations = artifacts.require("Migrations");
  2. module.exports = function(deployer) {
  3. deployer.deploy(Migrations);
  4. };
  1. 2_deploy_constracts.js
  1. const ConvertLib = artifacts.require("ConvertLib");
  2. const MetaCoin = artifacts.require("MetaCoin");
  3. module.exports = function(deployer) {
  4. deployer.deploy(ConvertLib);
  5. deployer.link(ConvertLib, MetaCoin);
  6. deployer.deploy(MetaCoin);
  7. };

最后来看一下truffle-config.js 文件:

  1. module.exports = {
  2. // Uncommenting the defaults below
  3. // provides for an easier quick-start with Ganache.
  4. // You can also follow this format for other networks;
  5. // see <http://truffleframework.com/docs/advanced/configuration>
  6. // for more details on how to specify configuration options!
  7. //
  8. //networks: {
  9. // development: {
  10. // host: "127.0.0.1",
  11. // port: 7545,
  12. // network_id: "*"
  13. // },
  14. // test: {
  15. // host: "127.0.0.1",
  16. // port: 7545,
  17. // network_id: "*"
  18. // }
  19. //}
  20. //
  21. };

MetaCoin的truffle-config.js文件是一个简陋版本,只定义了两种network(development和test),并且默认时被注释掉的。

默认情况下,使用truffle develop会开放一个9545端口,我们也可以在配置文件中写好配置并在使用时指定:

  1. truffle develop --network <network_config_name>

实际上完整的truffle-config.js文件应该长这样:

  1. /**
  2. * Use this file to configure your truffle project. It's seeded with some
  3. * common settings for different networks and features like migrations,
  4. * compilation and testing. Uncomment the ones you need or modify
  5. * them to suit your project as necessary.
  6. *
  7. * More information about configuration can be found at:
  8. *
  9. * trufflesuite.com/docs/advanced/configuration
  10. *
  11. * To deploy via Infura you'll need a wallet provider (like @truffle/hdwallet-provider)
  12. * to sign your transactions before they're sent to a remote public node. Infura accounts
  13. * are available for free at: infura.io/register.
  14. *
  15. * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate
  16. * public/private key pairs. If you're publishing your code to GitHub make sure you load this
  17. * phrase from a file you've .gitignored so it doesn't accidentally become public.
  18. *
  19. */
  20. // const HDWalletProvider = require('@truffle/hdwallet-provider');
  21. // const infuraKey = "fj4jll3k.....";
  22. //
  23. // const fs = require('fs');
  24. // const mnemonic = fs.readFileSync(".secret").toString().trim();
  25. module.exports = {
  26. /**
  27. * Networks define how you connect to your ethereum client and let you set the
  28. * defaults web3 uses to send transactions. If you don't specify one truffle
  29. * will spin up a development blockchain for you on port 9545 when you
  30. * run `develop` or `test`. You can ask a truffle command to use a specific
  31. * network from the command line, e.g
  32. *
  33. * $ truffle test --network <network-name>
  34. */
  35. networks: {
  36. // Useful for testing. The `development` name is special - truffle uses it by default
  37. // if it's defined here and no other network is specified at the command line.
  38. // You should run a client (like ganache-cli, geth or parity) in a separate terminal
  39. // tab if you use this network and you must also set the `host`, `port` and `network_id`
  40. // options below to some value.
  41. //
  42. development: {
  43. host: "127.0.0.1", // Localhost (default: none)
  44. port: 7545, // Standard Ethereum port (default: none)
  45. network_id: "*", // Any network (default: none)
  46. },
  47. // Another network with more advanced options...
  48. // advanced: {
  49. // port: 8777, // Custom port
  50. // network_id: 1342, // Custom network
  51. // gas: 8500000, // Gas sent with each transaction (default: ~6700000)
  52. // gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei)
  53. // from: <address>, // Account to send txs from (default: accounts[0])
  54. // websocket: true // Enable EventEmitter interface for web3 (default: false)
  55. // },
  56. // Useful for deploying to a public network.
  57. // NB: It's important to wrap the provider as a function.
  58. // ropsten: {
  59. // provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/YOUR-PROJECT-ID`),
  60. // network_id: 3, // Ropsten's id
  61. // gas: 5500000, // Ropsten has a lower block limit than mainnet
  62. // confirmations: 2, // # of confs to wait between deployments. (default: 0)
  63. // timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50)
  64. // skipDryRun: true // Skip dry run before migrations? (default: false for public nets )
  65. // },
  66. // Useful for private networks
  67. // private: {
  68. // provider: () => new HDWalletProvider(mnemonic, `https://network.io`),
  69. // network_id: 2111, // This network is yours, in the cloud.
  70. // production: true // Treats this network as if it was a public net. (default: false)
  71. // }
  72. },
  73. // Set default mocha options here, use special reporters etc.
  74. mocha: {
  75. // timeout: 100000
  76. },
  77. // Configure your compilers
  78. compilers: {
  79. solc: {
  80. version: "0.7.1", // Fetch exact version from solc-bin (default: truffle's version)
  81. // docker: true, // Use "0.5.1" you've installed locally with docker (default: false)
  82. // settings: { // See the solidity docs for advice about optimization and evmVersion
  83. // optimizer: {
  84. // enabled: false,
  85. // runs: 200
  86. // },
  87. // evmVersion: "byzantium"
  88. // }
  89. }
  90. }
  91. };

我们还可以定义构造区块链时的gasLimit,使用from字段定义使用的账户(默认使用第0个账户),在compilers中,还可以指定编译器版本。

学习另一个实用智能合约 Ballot

在 Solidity 的文档中给出了一个实现投票的智能合约,请注意,这个 sol 文件需要 0.7.0 以上的编译器版本才能编译:

  1. // SPDX-License-Identifier: GPL-3.0
  2. pragma solidity >=0.7.0 <0.9.0;
  3. // 请注意,这个sol文件需要0.7.0以上的编译器版本才能编译
  4. /// @title Voting with delegation.
  5. contract Ballot {
  6. // This declares a new complex type which will
  7. // be used for variables later.
  8. // It will represent a single voter.
  9. struct Voter {
  10. uint weight; // weight is accumulated by delegation
  11. bool voted; // if true, that person already voted
  12. address delegate; // person delegated to
  13. uint vote; // index of the voted proposal
  14. }
  15. // This is a type for a single proposal.
  16. struct Proposal {
  17. bytes32 name; // short name (up to 32 bytes)
  18. uint voteCount; // number of accumulated votes
  19. }
  20. address public chairperson;
  21. // This declares a state variable that
  22. // stores a `Voter` struct for each possible address.
  23. mapping(address => Voter) public voters;
  24. // A dynamically-sized array of `Proposal` structs.
  25. Proposal[] public proposals;
  26. /// Create a new ballot to choose one of `proposalNames`.
  27. constructor(bytes32[] memory proposalNames) {
  28. chairperson = msg.sender;
  29. voters[chairperson].weight = 1;
  30. // For each of the provided proposal names,
  31. // create a new proposal object and add it
  32. // to the end of the array.
  33. for (uint i = 0; i < proposalNames.length; i++) {
  34. // `Proposal({...})` creates a temporary
  35. // Proposal object and `proposals.push(...)`
  36. // appends it to the end of `proposals`.
  37. proposals.push(Proposal({
  38. name: proposalNames[i],
  39. voteCount: 0
  40. }));
  41. }
  42. }
  43. // Give `voter` the right to vote on this ballot.
  44. // May only be called by `chairperson`.
  45. function giveRightToVote(address voter) public {
  46. // If the first argument of `require` evaluates
  47. // to `false`, execution terminates and all
  48. // changes to the state and to Ether balances
  49. // are reverted.
  50. // This used to consume all gas in old EVM versions, but
  51. // not anymore.
  52. // It is often a good idea to use `require` to check if
  53. // functions are called correctly.
  54. // As a second argument, you can also provide an
  55. // explanation about what went wrong.
  56. require(
  57. msg.sender == chairperson,
  58. "Only chairperson can give right to vote."
  59. );
  60. require(
  61. !voters[voter].voted,
  62. "The voter already voted."
  63. );
  64. require(voters[voter].weight == 0);
  65. voters[voter].weight = 1;
  66. }
  67. /// Delegate your vote to the voter `to`.
  68. function delegate(address to) public {
  69. // assigns reference
  70. Voter storage sender = voters[msg.sender];
  71. require(!sender.voted, "You already voted.");
  72. require(to != msg.sender, "Self-delegation is disallowed.");
  73. // Forward the delegation as long as
  74. // `to` also delegated.
  75. // In general, such loops are very dangerous,
  76. // because if they run too long, they might
  77. // need more gas than is available in a block.
  78. // In this case, the delegation will not be executed,
  79. // but in other situations, such loops might
  80. // cause a contract to get "stuck" completely.
  81. while (voters[to].delegate != address(0)) {
  82. to = voters[to].delegate;
  83. // We found a loop in the delegation, not allowed.
  84. require(to != msg.sender, "Found loop in delegation.");
  85. }
  86. // Since `sender` is a reference, this
  87. // modifies `voters[msg.sender].voted`
  88. sender.voted = true;
  89. sender.delegate = to;
  90. Voter storage delegate_ = voters[to];
  91. if (delegate_.voted) {
  92. // If the delegate already voted,
  93. // directly add to the number of votes
  94. proposals[delegate_.vote].voteCount += sender.weight;
  95. } else {
  96. // If the delegate did not vote yet,
  97. // add to her weight.
  98. delegate_.weight += sender.weight;
  99. }
  100. }
  101. /// Give your vote (including votes delegated to you)
  102. /// to proposal `proposals[proposal].name`.
  103. function vote(uint proposal) public {
  104. Voter storage sender = voters[msg.sender];
  105. require(sender.weight != 0, "Has no right to vote");
  106. require(!sender.voted, "Already voted.");
  107. sender.voted = true;
  108. sender.vote = proposal;
  109. // If `proposal` is out of the range of the array,
  110. // this will throw automatically and revert all
  111. // changes.
  112. proposals[proposal].voteCount += sender.weight;
  113. }
  114. /// @dev Computes the winning proposal taking all
  115. /// previous votes into account.
  116. function winningProposal() public view
  117. returns (uint winningProposal_)
  118. {
  119. uint winningVoteCount = 0;
  120. for (uint p = 0; p < proposals.length; p++) {
  121. if (proposals[p].voteCount > winningVoteCount) {
  122. winningVoteCount = proposals[p].voteCount;
  123. winningProposal_ = p;
  124. }
  125. }
  126. }
  127. // Calls winningProposal() function to get the index
  128. // of the winner contained in the proposals array and then
  129. // returns the name of the winner
  130. function winnerName() public view
  131. returns (bytes32 winnerName_)
  132. {
  133. winnerName_ = proposals[winningProposal()].name;
  134. }
  135. // 以下为作者新添加的两个函数
  136. function getProposalName(uint index) public view returns (bytes32) {
  137. require(index < proposals.length, "No This Proposal");
  138. require(index >= 0, "Not a positive Number");
  139. return proposals[index].name;
  140. }
  141. function getProposalVoteCount(uint index) public view returns (uint) {
  142. require(index < proposals.length, "No this Proposal");
  143. require(index >= 0, "Not a positive Number");
  144. return proposals[index].voteCount;
  145. }
  146. }

这个智能合约实现了基本的投票功能,分析构造函数,我们知道该合约部署时需要传入一个bytes32[]参数,是一个由被选举人构成的列表,构造函数还将msg.sender设置为新变量chairperson;

结构体Voter代表一个投票人,其中包含权重、是否已投票、该投票人的委托投票人,以及投票投给了谁;

结构体Proposal代表一个被选举人,包含名字、得票数量;

函数giveRightToVote只能被 chairperson 调用,接受一个参数 voter,如果这个 voter 还没有投过票,并且这个 voter 还没有投票权,则赋予其投票权;

函数delegate是一个委托投票权的函数,允许投票人将自己的投票权委托给另一个人;

函数vote 是投票函数,拥有投票权的投票人可以为被选举人投票;

函数winningProposal 计算得票数最高的被选举人,返回其编号;

函数winnerName通过上个函数的编号,返回被选举人的名字;

函数getProposalNamegetProposalVoteCount 返回被选举人的名字和其当前得票数量。

3.1 准备

新建一个Vote文件夹,要创建一个空的truffle项目,在终端内运行:

  1. truffle init

可以看到文件夹内产生了一些变化:

我们在contracts文件夹内新建Ballot.sol,复制上述的投票智能合约代码;

在migrations文件夹内新建2_deploy_contracts.js文件:

  1. const Ballot = artifacts.require("Ballot");
  2. module.exports = function(deployer) {
  3. deployer.deploy(
  4. Ballot,
  5. [
  6. "0x0000000000000000000000000000000000000000000000000000000000000000",
  7. "0x0000000000000000000000000000000000000000000000000000000000000001",
  8. "0x0000000000000000000000000000000000000000000000000000000000000002",
  9. "0x0000000000000000000000000000000000000000000000000000000000000003"
  10. ]
  11. );
  12. };

部署Ballot智能合约时,其构造函数需要传入一个bytes32[] 类型的参数,代表被选举人。Truffle会在部署智能合约时为其传入这个参数。

打开truffle-config.js文件,修改配置,接下来我们使用可视化工具Ganache观察区块链变化。首先设置网络,新建一个ganache网络,为了与之后的Ganache做适配:

同时为了匹配0.7.0以上的编译器版本,修改compiler字段:

最后的目录长这样:

3.2 交互

打开 Ganache,选 Quickstart,可以看到 Ganache 也会帮我们生成一条区块链,并预先设置 10 个账户,每个账户内含 100 ETH。

我们点击右上角的齿轮按钮进入设置:

点击ADD PROJECT,选择truffle-config.js 文件,加载我们的Truffle项目

在 Server 菜单中,可以看到 Ganache 生成的区块链的地址、开放的 RPC 端口,NetworkID 等,这些值与我们刚刚创建好的 Ganache 网络配置匹配,稍后可以使用 truffle 命令部署智能合约:

点击SAVE AND RESTART 保存更改。在 CONTRACTS 菜单中,提示我们需要使用 Truffle 部署智能合约:

打开终端,输入以下命令:

  1. truffle migrate --network ganache

合约成功部署:

部署合约需要消耗 ETH,查看ACCOUNTS可以看到默认的第 0 个账户消耗掉 0.02380284 个 ETH:

BLOCKTRANSACTIONS 记录了区块链和交易,可以查看学习。

Ganache 本身不具备 web3 交互,因此要使用合约,还需要进入 Truffle 控制台进行操作:

  1. truffle console --network ganache

第一步依然是获得刚刚部署过的智能合约的实例:

  1. truffle(ganache)> let instance = await Ballot.deployed()
  2. undefined
  3. truffle(ganache)> instance.address
  4. '0xb4e42257053866c9746a807910086A848406ABB8'

可以看到这个智能合约实例地址与 Ganache 显示的地址是一致的。

由于目前第0个账户是部署合约的账户,因此 chairperson 的地址应该为第0个账户的地址。同时,由于我们在进入控制台之前没有设置使用的账户,因此默认控制台正在使用的也是第0个账户。

现在我们有权利为其他账户赋予投票权利,我们当然可以直接在 Ganache 内抄下某个账户地址,也可以使用 web3 获得账户地址:

  1. truffle(ganache)> let allAccounts = web3.eth.getAccounts()
  2. undefined
  3. truffle(ganache)> allAccounts
  4. [ '0x78087a3fDd3Ad30Dc23dF8a80eA6fE81Db1b7fbb',
  5. '0xB7D53a71a1a8A45C9F1DF152cf6DB0F5805261B6',
  6. '0x605227a90d1566EEeC77AE2e36Ad48dcAe5d6CD4',
  7. '0x8c4491074a1623A96D62288FCA0aFeD73Ab710e3',
  8. '0xaB750d95277e2Cd67bA1Effd00d2cb8319170620',
  9. '0xd619b30e8f019569D59fe6aD557e52E5302F227f',
  10. '0x5BB81474c351a28507DD5317F4023088b8912f41',
  11. '0xb5528106D4c92262C3da2d3E29282fd1687eAAA6',
  12. '0xf79a3C4a0881F879Ddf5D18beB37e5B5767aEFED',
  13. '0xf3e67be6A334CB438282BCB09A57d7A92eacE03f' ]

将投票权赋予账户'0xB7D53a71a1a8A45C9F1DF152cf6DB0F5805261B6':

  1. instance.giveRightToVote('0xB7D53a71a1a8A45C9F1DF152cf6DB0F5805261B6')

此时发生了一笔交易,在Ganache内也可以同步查看:

目前账户 '0xB7D53a71a1a8A45C9F1DF152cf6DB0F5805261B6' 获得了投票权,现在怎么使用该账户为某个被选举人投票呢?

刚才我们提到,默认进入truffle控制台会使用第0个账户,要切换账户,需要修改网络配置文件。我们首先退出目前的控制台,修改truffle-config.js文件:

新建一个 ganacheUser1 配置,指定 from 地址,使用此网络配置文件重新进入控制台:

  1. truffle console --network ganacheUser1

使用投票函数 vote,为某个被选举人投票,当然首先还是需要获得智能合约实例:

投票成功了。我们同时可以在 Ganache 内看到交易信息和区块信息:

使用 winnerName 函数查看得票数最高的被选举人:

  1. truffle(ganacheUser1)> instance.winnerName()
  2. '0x0000000000000000000000000000000000000000000000000000000000000002'

总结

相比于Remin编辑器,Truffle套件为我们提供了完整的以太坊区块链智能合约开发系统,其自带的develop模块可以直接生成容易上手的区块链,比geth更加简单。Ganache是Truffle套件内的可视化应用程序,帮助我们直观地查看交易和区块变化。

通过亲自部署智能合约,与智能合约进行交互,可以快速理解Solidity语言的用法。

其他参考文档:

详解 Solidity 事件Event - 完全搞懂事件的使用 - Tiny熊 - 博客园 (cnblogs.com)

testrpc - truffle always says ".my_function is not a function" - Ethereum Stack Exchange

智能合约概述 — Solidity develop 文档 (solidity-cn.readthedocs.io)

Solidity by Example — Solidity 0.8.1 documentation (soliditylang.org)

快速入门 Truffle | Truffle 中文文档 - DApp 开发框架 | 深入浅出区块链 (learnblockchain.cn)

How to switch account from default in testrpc - Ethereum Stack Exchange

使用Truffle 部署智能合约的更多相关文章

  1. nodejs部署智能合约的方法-web3 0.20版本

    参考:https://www.jianshu.com/p/7e541cd67be2 部署智能合约的方法有很多,比如使用truffle框架,使用remix-ide等,在这里的部署方法是使用nodejs一 ...

  2. 在testrpc以太坊测试环境部署智能合约

    2018年03月13日 09:20:54 思无邪-machengyu 阅读数 2683   版权声明:本文为博主原创文章,转载请务必注明出处,否则追究法律责任 https://blog.csdn.ne ...

  3. Eth 部署智能合约

    首先要开发以太坊的智能合约,需要EVM(以太坊虚拟机),也就是需要运行的环境,我们可以通过 geth 来设置开发环境: geth --datadir testNet --dev console 2&g ...

  4. 利用truffle与智能合约进行交互

    先了解相关指令,再观看比较合适:http://truffle.tryblockchain.org/ 安装: 先完成上一条博客的安装,再来进行下面的操作:http://www.cnblogs.com/t ...

  5. truffle 发布 智能合约

    参考 这篇https://www.codeooze.com/blockchain/ethereum-geth-private-blockchain/ 说的已经很详细了 genesis.json 过时了 ...

  6. web3部署智能合约碰到的一个奇怪问题

    都是gasLimit惹的祸 解决一个奇怪问题Error: Number can only safely store up to 53 bits 原来好好的node endpointtest.js ,结 ...

  7. 如何用web3部署智能合约

    合约示例 pragma solidity ^0.4.18; contract CallMeChallenge { bool public isComplete = false; function ca ...

  8. BOOM -- 智能合约编程

    译注:原文首发于ConsenSys开发者博客,原作者为Eva以及ConsenSys的开发团队.如果您想要获取更多及时信息,可以访问ConsenSys首页点击左下角Newsletter订阅邮件.本文的翻 ...

  9. Truffle 4.0、Geth 1.7.2、TestRPC在私有链上搭建智能合约

    目录 目录 1.什么是 Truffle? 2.适合 Truffle 开发的客户端 3.Truffle的源代码地址 4.如何安装? 4.1.安装 Go-Ethereum 1.7.2 4.2.安装 Tru ...

随机推荐

  1. byte溢出栗子

    原创:转载需注明原创地址 https://www.cnblogs.com/fanerwei222/p/11634402.html byte溢出测试: byte b1 = (byte) 127; byt ...

  2. array_multisort array_merge 排序

    前段时间遇到一个排序问题,大致是这样的:$demo = array(        0        =>        array(                'name'         ...

  3. Vue项目中实现文件下载到本地的功能

    公司业务需求,我需要实现一个合同模板,自定义输入内容后生成合同随后导出下载合同.(自定义部分用到的是) 为了实现这个文件下载到本地的功能,真的是废了九牛二虎之力,以至于差点放弃(主要还是自己菜).刚开 ...

  4. sql与数据库

    sql的优化: 1.对查询进行优化,要尽量避免全表扫描,首先应考虑在进行条件判断的字段上创建索引 2.尽量避免在WHERE字句中对字段进行NULL值判断,否则将导致引擎放弃使用索引而进行全表扫描 3. ...

  5. 基于GDAL库,读取.nc文件(以海洋表温数据为例)C++版

    对于做海洋数据处理的同学,会经常遇到nc格式的文件,nc文件的格式全称是NetCDF,具体的详细解释请查询官网[https://www.unidata.ucar.edu/software/netcdf ...

  6. Python基础—文件操作(Day8)

    一.文件操作参数 1.文件路径 1)绝对路径:从根目录开始一级一级查找直到找到文件. f=open('e:\文件操作笔记.txt',encoding='utf-8',mode='r') content ...

  7. Linux之history使用技巧

    背景: 正常情况下,Linux系统中输入 history  只显示序号和历史命令如下图,但是当我们想要根据历史命令来排查一些故障问题时,无法精确获取该命令执行的详细信息,包括执行时间.执行的用户.是哪 ...

  8. 如何用zabbix监控mysql多实例

    agent上起了多了 mysql实例,占用不同的端口,agent 仅在初始状况下,塞入脚本和 键配置,然后重启. 以后维护的时候(mysql端口变动),要做到 不能 动agent,力争 只在 web端 ...

  9. C# Event 内核构造 |EventWaitHandle、AutoResetEvent、 ManualResetEvent

    EventWaitHandle 继承:Object->WaitHandle-> EventWaitHandle派生:System.Threading.AutoResetEvent\Syst ...

  10. .net 技术大全

    我常说C#的入门技术是委托.事件.消息.只有当你可以纯熟运用这三个技能的时候,才刚刚入门,此时C#的大门才算正式为你打开.很多人在学了一些语法编写一些项目后就觉得C#精通了,其实你们还没入门呢(对日开 ...