使用Truffle 部署智能合約


使用Truffle 部署智能合約

之前我們使用Geth,原生的以太坊Golang工具,分析了創世區塊的參數內容,在本地創建了私有以太坊區塊鏈,並使用兩個賬戶進行了挖礦和轉賬操作,對以太坊有了基本了解。

該篇章開始使用一個新的平台Truffle Suite,學習部署示例的智能合約,和一個稍微復雜一些的實用智能合約,學習Solidity語言的基本語法和智能合約的使用。

本文絕大多數參考資料來源於Solidity官方文檔Truffle官方文檔

1. 安裝Truffle

Truffle Suite套件包括三個組件:

  • Truffle:命令行工具,用來部署智能合約
  • Ganache:GUI工具,用來可視化查看區塊、賬戶、合約、交易等內容
  • drizzle:Javascript庫,用於前端開發

雖然Truffle套件在全平台通用,但在Windows上可能會出現莫名其妙的命名空間沖突問題,本次全部使用Ubuntu進行操作。

使用npm即可安裝Truffle:(如何安裝npm,換源等問題不在本篇的討論范圍內)

npm install -g truffle

后續使用Ganache做可視化瀏覽,Ganache為Linux提供了Appimage打包,下載后記得為其賦予可執行屬性才能打開:

下載地址:https://github.com/trufflesuite/ganache/releases

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,打開終端輸入:

truffle unbox metacoin

如果執行unbox命令時提示 RequestError: Error: connect ECONNREFUSED 錯誤,可以嘗試使用export https_proxy為bash設置代理,加快訪問速度

下載成功后,會看到文件夾內多出了一下內容:

image-20210128222856693

其中contracts文件夾內有三個sol文件,是Solidity語言編寫的只能合約文件;

migrations文件夾中的兩個js文件是truffle部署智能合約時的部署文件,用來管理和升級智能合約,而且這些文件執行是有順序的,必須以數字為開頭;

test文件夾中是測試文件,可以使用js或者solidity語言編寫測試腳本;

truffle-config.js 是truffle 的配置文件,包含truffle使用什么版本的編譯器,在什么端口開放區塊鏈的rpc協議等;

LICENSE為該代碼的許可證。

2.2 交互

我們首先演示一下這個智能合約的實際效果,之后觀察代碼思考其運行的方法。

要部署智能合約,我們首先需要生成一條區塊鏈。Truffle 可以快速幫我們生成開發環境的區塊鏈,並構造出10個賬戶:

truffle develop

image-20210129004439173

可以看到,一條新的區塊鏈已經生成,並在9545端口打開了http服務(rpc服務),並預先生成了10個賬戶,每個賬戶中默認存有100個ETH,當前的控制台使用的是默認的第0個賬戶。

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

truffle(develop)> web3.eth.getAccounts()
[ '0xB836A85f25f9Ab41290f2a63D1Ee83AEa9F53b94',
  '0x0fC57BdDf263df2C70A5468B15b6fD620a366Cb4',
  '0x9A2219312B49cd833650067427874204dC5e261c',
  '0xfB440A02DCE4Aea19374902b57bEDEb23342d38f',
  '0x78d551ECe5749D3453960460D337b283F6315174',
  '0x737173efe01E9B720A310535fa513a23099d6fa2',
  '0x39313f35e7549aEE9Df037936190a923a897B437',
  '0x6F12D8eaC6996ba70Ca12e44E47669FEEDFD7ED7',
  '0xDbe225FAc5F4CA0f74466af1b0625d2d7a4C7c75',
  '0x56c467638B135C8584d871b1F468B8bb2363Db1a' ]
truffle(develop)> web3.eth.getBalance('0xB836A85f25f9Ab41290f2a63D1Ee83AEa9F53b94')
'100000000000000000000'

部署智能合約之前,需要編譯sol文件:

truffle(develop)> compile

image-20210129004847133

同時我們會看到Metacoin文件夾內多出來一個build文件夾,其中存放了編譯好的智能合約。使用migrate命令部署之恩那個合約:

truffle(develop)> migrate

結果如下:

truffle(develop)> migrate

Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile.



Starting migrations...
======================
> Network name:    'develop'
> Network id:      5777
> Block gas limit: 6721975 (0x6691b7)


1_initial_migration.js
======================

   Deploying 'Migrations'
   ----------------------
   > transaction hash:    0x9d236e01303e2fc44c0717733120fe28669d5f2dacdd2b66561170331e72ff35
   > Blocks: 0            Seconds: 0
   > contract address:    0x65ae7471c845a10049053a15Be43EE86E76cF1F5
   > block number:        1
   > block timestamp:     1611852614
   > account:             0xB836A85f25f9Ab41290f2a63D1Ee83AEa9F53b94
   > balance:             99.9967165
   > gas used:            164175 (0x2814f)
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.0032835 ETH


   > Saving migration to chain.
   > Saving artifacts
   -------------------------------------
   > Total cost:           0.0032835 ETH


2_deploy_contracts.js
=====================

   Deploying 'ConvertLib'
   ----------------------
   > transaction hash:    0xb2e3678a744446e6d3d98a43f3195994666b0e948f87b24eb5612ab20dcf08f9
   > Blocks: 0            Seconds: 0
   > contract address:    0xb59dBD1609f0982B0f7d64d3592D8390092442C7
   > block number:        3
   > block timestamp:     1611852614
   > account:             0xB836A85f25f9Ab41290f2a63D1Ee83AEa9F53b94
   > balance:             99.99396028
   > gas used:            95470 (0x174ee)
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.0019094 ETH


   Linking
   -------
   * Contract: MetaCoin <--> Library: ConvertLib (at address: 0xb59dBD1609f0982B0f7d64d3592D8390092442C7)

   Deploying 'MetaCoin'
   --------------------
   > transaction hash:    0xf9b71d6dca179dddeac7bc1fba8d52f0b1ba430ac6c623aa4aeb7ea5ece09110
   > Blocks: 0            Seconds: 0
   > contract address:    0x8Baf7f61EEBb19eB22cC165AC9291338bF857522
   > block number:        4
   > block timestamp:     1611852614
   > account:             0xB836A85f25f9Ab41290f2a63D1Ee83AEa9F53b94
   > balance:             99.98822922
   > gas used:            286553 (0x45f59)
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.00573106 ETH


   > Saving migration to chain.
   > Saving artifacts
   -------------------------------------
   > Total cost:          0.00764046 ETH


Summary
=======
> Total deployments:   3
> Final cost:          0.01092396 ETH

可以看到,由於剛剛編譯過sol文件,部署時跳過了編譯,直接使用1_initial_migration.js和2_deploy_contracts.js部署智能合約,最終消耗了0.01092396 ETH

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

Truffle控制台支持async/await 方法,我們新建變量時更加方便了。新建一個變量instance,為剛剛部署的合約的實例。

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

查看賬戶余額(代幣的余額,即Metacoin的余額):

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

查看以太坊匯率轉換后的余額:

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

向第1個賬戶發送一些代幣:

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

查看其余額:

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

2.3 解析

接下來我們詳細分析三個sol文件:

  1. Migrations.sol

    Migrations文件是使用truffle部署智能合約時必要的文件,其內容一般不會變。

  2. MetaCoin.sol

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.25 <0.7.0;
// 第一行聲明了solidity的編譯器版本

import "./ConvertLib.sol";
// 表示引用了當前目錄下的庫文件ConvertLib.sol

// 定義了一個合約,名為MetaCoin
contract MetaCoin {
// 變量balances本身時一個address類型,但被映射為無符號整型
mapping (address => uint) balances;

// 事件用來記錄日志
event Transfer(address indexed _from, address indexed _to, uint256 _value);

// constructor函數為構造函數,在合約部署時運行
// tx.origin 是一個特殊的全局變量,意味最初調用合約的賬戶地址
// 向部署調用合約的人的余額添加10000個代幣
constructor() public {
	balances[tx.origin] = 10000;
}

// sendCoin函數接收兩個參數(收件人和代幣數量),返回布爾值
function sendCoin(address receiver, uint amount) public returns(bool sufficient) {
	// 首先判斷調用合約的人(發件人)的余額,如果余額小於要發送的代幣數量,則返回false
	// msg.sender是一個特殊的變量,意味調用合約的賬戶的地址
	if (balances[msg.sender] < amount) return false;
	// 修改發件人和收件人的余額
	balances[msg.sender] -= amount;
	balances[receiver] += amount;
	// 記錄這個event
	emit Transfer(msg.sender, receiver, amount);
	// 最終返回 true
	return true;
}

// 查看代幣轉換為以太幣后的價值
function getBalanceInEth(address addr) public view returns(uint){
	// 調用了ConvertLib中的匯率轉換函數
	return ConvertLib.convert(getBalance(addr),2);
}


// 查看代幣的數量
function getBalance(address addr) public view returns(uint) {
	return balances[addr];
}
}

  1. ConvertLib.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.25 <0.7.0;

library ConvertLib{
	// convert函數接受兩個參數:amount和conversionRate,即貨幣數量和匯率,返回uint轉換后的貨幣數量
	// 該文件主要是為我們演示了如何在Solidity中引用Library
	function convert(uint amount,uint conversionRate) public pure returns (uint convertedAmount)
	{
		return amount * conversionRate;
	}
}

convert函數接受兩個參數:amount和conversionRate,即貨幣數量和匯率,返回uint轉換后的貨幣數量
該文件主要是為我們演示了如何在Solidity中引用Library

深入理解Truffle部署配置

上述三個文件完成了智能合約內容的編寫,要部署合約,需要使用migrations中的兩個js文件:

//TODO: 有關合約遷移的具體內容,會在后續補充

  1. 1_initial_migration.js
const Migrations = artifacts.require("Migrations");

module.exports = function(deployer) {
 deployer.deploy(Migrations);
};

  1. 2_deploy_constracts.js
const ConvertLib = artifacts.require("ConvertLib");
const MetaCoin = artifacts.require("MetaCoin");

module.exports = function(deployer) {
 deployer.deploy(ConvertLib);
 deployer.link(ConvertLib, MetaCoin);
 deployer.deploy(MetaCoin);
};

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

module.exports = {
  // Uncommenting the defaults below 
  // provides for an easier quick-start with Ganache.
  // You can also follow this format for other networks;
  // see <http://truffleframework.com/docs/advanced/configuration>
  // for more details on how to specify configuration options!
  //
  //networks: {
  //  development: {
  //    host: "127.0.0.1",
  //    port: 7545,
  //    network_id: "*"
  //  },
  //  test: {
  //    host: "127.0.0.1",
  //    port: 7545,
  //    network_id: "*"
  //  }
  //}
  //
};

MetaCoin的truffle-config.js文件是一個簡陋版本,只定義了兩種network(development和test),並且默認時被注釋掉的。

默認情況下,使用truffle develop會開放一個9545端口,我們也可以在配置文件中寫好配置並在使用時指定:

truffle develop --network <network_config_name>

實際上完整的truffle-config.js文件應該長這樣:

/**
 * Use this file to configure your truffle project. It's seeded with some
 * common settings for different networks and features like migrations,
 * compilation and testing. Uncomment the ones you need or modify
 * them to suit your project as necessary.
 *
 * More information about configuration can be found at:
 *
 * trufflesuite.com/docs/advanced/configuration
 *
 * To deploy via Infura you'll need a wallet provider (like @truffle/hdwallet-provider)
 * to sign your transactions before they're sent to a remote public node. Infura accounts
 * are available for free at: infura.io/register.
 *
 * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate
 * public/private key pairs. If you're publishing your code to GitHub make sure you load this
 * phrase from a file you've .gitignored so it doesn't accidentally become public.
 *
 */

// const HDWalletProvider = require('@truffle/hdwallet-provider');
// const infuraKey = "fj4jll3k.....";
//
// const fs = require('fs');
// const mnemonic = fs.readFileSync(".secret").toString().trim();

module.exports = {
  /**
   * Networks define how you connect to your ethereum client and let you set the
   * defaults web3 uses to send transactions. If you don't specify one truffle
   * will spin up a development blockchain for you on port 9545 when you
   * run `develop` or `test`. You can ask a truffle command to use a specific
   * network from the command line, e.g
   *
   * $ truffle test --network <network-name>
   */

  networks: {
    // Useful for testing. The `development` name is special - truffle uses it by default
    // if it's defined here and no other network is specified at the command line.
    // You should run a client (like ganache-cli, geth or parity) in a separate terminal
    // tab if you use this network and you must also set the `host`, `port` and `network_id`
    // options below to some value.
    //
    development: {
     host: "127.0.0.1",     // Localhost (default: none)
     port: 7545,            // Standard Ethereum port (default: none)
     network_id: "*",       // Any network (default: none)
    },
    // Another network with more advanced options...
    // advanced: {
    // port: 8777,             // Custom port
    // network_id: 1342,       // Custom network
    // gas: 8500000,           // Gas sent with each transaction (default: ~6700000)
    // gasPrice: 20000000000,  // 20 gwei (in wei) (default: 100 gwei)
    // from: <address>,        // Account to send txs from (default: accounts[0])
    // websocket: true        // Enable EventEmitter interface for web3 (default: false)
    // },
    // Useful for deploying to a public network.
    // NB: It's important to wrap the provider as a function.
    // ropsten: {
    // provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/YOUR-PROJECT-ID`),
    // network_id: 3,       // Ropsten's id
    // gas: 5500000,        // Ropsten has a lower block limit than mainnet
    // confirmations: 2,    // # of confs to wait between deployments. (default: 0)
    // timeoutBlocks: 200,  // # of blocks before a deployment times out  (minimum/default: 50)
    // skipDryRun: true     // Skip dry run before migrations? (default: false for public nets )
    // },
    // Useful for private networks
    // private: {
    // provider: () => new HDWalletProvider(mnemonic, `https://network.io`),
    // network_id: 2111,   // This network is yours, in the cloud.
    // production: true    // Treats this network as if it was a public net. (default: false)
    // }
  },

  // Set default mocha options here, use special reporters etc.
  mocha: {
    // timeout: 100000
  },

  // Configure your compilers
  compilers: {
    solc: {
      version: "0.7.1",    // Fetch exact version from solc-bin (default: truffle's version)
      // docker: true,        // Use "0.5.1" you've installed locally with docker (default: false)
      // settings: {          // See the solidity docs for advice about optimization and evmVersion
      //  optimizer: {
      //    enabled: false,
      //    runs: 200
      //  },
      //  evmVersion: "byzantium"
      // }
    }
  }
};

我們還可以定義構造區塊鏈時的gasLimit,使用from字段定義使用的賬戶(默認使用第0個賬戶),在compilers中,還可以指定編譯器版本。

學習另一個實用智能合約 Ballot

在 Solidity 的文檔中給出了一個實現投票的智能合約,請注意,這個 sol 文件需要 0.7.0 以上的編譯器版本才能編譯:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
// 請注意,這個sol文件需要0.7.0以上的編譯器版本才能編譯
/// @title Voting with delegation.
contract Ballot {
    // This declares a new complex type which will
    // be used for variables later.
    // It will represent a single voter.
    struct Voter {
        uint weight; // weight is accumulated by delegation
        bool voted;  // if true, that person already voted
        address delegate; // person delegated to
        uint vote;   // index of the voted proposal
    }

    // This is a type for a single proposal.
    struct Proposal {
        bytes32 name;   // short name (up to 32 bytes)
        uint voteCount; // number of accumulated votes
    }

    address public chairperson;

    // This declares a state variable that
    // stores a `Voter` struct for each possible address.
    mapping(address => Voter) public voters;

    // A dynamically-sized array of `Proposal` structs.
    Proposal[] public proposals;

    /// Create a new ballot to choose one of `proposalNames`.
    constructor(bytes32[] memory proposalNames) {
        chairperson = msg.sender;
        voters[chairperson].weight = 1;

        // For each of the provided proposal names,
        // create a new proposal object and add it
        // to the end of the array.
        for (uint i = 0; i < proposalNames.length; i++) {
            // `Proposal({...})` creates a temporary
            // Proposal object and `proposals.push(...)`
            // appends it to the end of `proposals`.
            proposals.push(Proposal({
                name: proposalNames[i],
                voteCount: 0
            }));
        }
    }

    // Give `voter` the right to vote on this ballot.
    // May only be called by `chairperson`.
    function giveRightToVote(address voter) public {
        // If the first argument of `require` evaluates
        // to `false`, execution terminates and all
        // changes to the state and to Ether balances
        // are reverted.
        // This used to consume all gas in old EVM versions, but
        // not anymore.
        // It is often a good idea to use `require` to check if
        // functions are called correctly.
        // As a second argument, you can also provide an
        // explanation about what went wrong.
        require(
            msg.sender == chairperson,
            "Only chairperson can give right to vote."
        );
        require(
            !voters[voter].voted,
            "The voter already voted."
        );
        require(voters[voter].weight == 0);
        voters[voter].weight = 1;
    }

    /// Delegate your vote to the voter `to`.
    function delegate(address to) public {
        // assigns reference
        Voter storage sender = voters[msg.sender];
        require(!sender.voted, "You already voted.");

        require(to != msg.sender, "Self-delegation is disallowed.");

        // Forward the delegation as long as
        // `to` also delegated.
        // In general, such loops are very dangerous,
        // because if they run too long, they might
        // need more gas than is available in a block.
        // In this case, the delegation will not be executed,
        // but in other situations, such loops might
        // cause a contract to get "stuck" completely.
        while (voters[to].delegate != address(0)) {
            to = voters[to].delegate;

            // We found a loop in the delegation, not allowed.
            require(to != msg.sender, "Found loop in delegation.");
        }

        // Since `sender` is a reference, this
        // modifies `voters[msg.sender].voted`
        sender.voted = true;
        sender.delegate = to;
        Voter storage delegate_ = voters[to];
        if (delegate_.voted) {
            // If the delegate already voted,
            // directly add to the number of votes
            proposals[delegate_.vote].voteCount += sender.weight;
        } else {
            // If the delegate did not vote yet,
            // add to her weight.
            delegate_.weight += sender.weight;
        }
    }

    /// Give your vote (including votes delegated to you)
    /// to proposal `proposals[proposal].name`.
    function vote(uint proposal) public {
        Voter storage sender = voters[msg.sender];
        require(sender.weight != 0, "Has no right to vote");
        require(!sender.voted, "Already voted.");
        sender.voted = true;
        sender.vote = proposal;

        // If `proposal` is out of the range of the array,
        // this will throw automatically and revert all
        // changes.
        proposals[proposal].voteCount += sender.weight;
    }

    /// @dev Computes the winning proposal taking all
    /// previous votes into account.
    function winningProposal() public view
            returns (uint winningProposal_)
    {
        uint winningVoteCount = 0;
        for (uint p = 0; p < proposals.length; p++) {
            if (proposals[p].voteCount > winningVoteCount) {
                winningVoteCount = proposals[p].voteCount;
                winningProposal_ = p;
            }
        }
    }

    // Calls winningProposal() function to get the index
    // of the winner contained in the proposals array and then
    // returns the name of the winner
    function winnerName() public view
            returns (bytes32 winnerName_)
    {
        winnerName_ = proposals[winningProposal()].name;
    }
    
    // 以下為作者新添加的兩個函數
    function getProposalName(uint index) public view returns (bytes32) {
        require(index < proposals.length, "No This Proposal");
        require(index >= 0, "Not a positive Number");
        return proposals[index].name;
    }

    function getProposalVoteCount(uint index) public view returns (uint) {
        require(index < proposals.length, "No this Proposal");
        require(index >= 0, "Not a positive Number");
        return proposals[index].voteCount;
    }
}

這個智能合約實現了基本的投票功能,分析構造函數,我們知道該合約部署時需要傳入一個bytes32[]參數,是一個由被選舉人構成的列表,構造函數還將msg.sender設置為新變量chairperson;

結構體Voter代表一個投票人,其中包含權重、是否已投票、該投票人的委托投票人,以及投票投給了誰;

結構體Proposal代表一個被選舉人,包含名字、得票數量;

函數giveRightToVote只能被 chairperson 調用,接受一個參數 voter,如果這個 voter 還沒有投過票,並且這個 voter 還沒有投票權,則賦予其投票權;

函數delegate是一個委托投票權的函數,允許投票人將自己的投票權委托給另一個人;

函數vote 是投票函數,擁有投票權的投票人可以為被選舉人投票;

函數winningProposal 計算得票數最高的被選舉人,返回其編號;

函數winnerName通過上個函數的編號,返回被選舉人的名字;

函數getProposalNamegetProposalVoteCount 返回被選舉人的名字和其當前得票數量。

3.1 准備

新建一個Vote文件夾,要創建一個空的truffle項目,在終端內運行:

truffle init

可以看到文件夾內產生了一些變化:

image-20210129011906617

我們在contracts文件夾內新建Ballot.sol,復制上述的投票智能合約代碼;

在migrations文件夾內新建2_deploy_contracts.js文件:

const Ballot = artifacts.require("Ballot");

module.exports = function(deployer) {
    deployer.deploy(
        Ballot,
        [
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000001",
            "0x0000000000000000000000000000000000000000000000000000000000000002",
            "0x0000000000000000000000000000000000000000000000000000000000000003"
        ]
    );
};

部署Ballot智能合約時,其構造函數需要傳入一個bytes32[] 類型的參數,代表被選舉人。Truffle會在部署智能合約時為其傳入這個參數。

打開truffle-config.js文件,修改配置,接下來我們使用可視化工具Ganache觀察區塊鏈變化。首先設置網絡,新建一個ganache網絡,為了與之后的Ganache做適配:

image-20210129143546097

同時為了匹配0.7.0以上的編譯器版本,修改compiler字段:

image-20210129141924053

最后的目錄長這樣:

image-20210129152300955

3.2 交互

打開 Ganache,選 Quickstart,可以看到 Ganache 也會幫我們生成一條區塊鏈,並預先設置 10 個賬戶,每個賬戶內含 100 ETH。

image-20210129143804496

我們點擊右上角的齒輪按鈕進入設置:

image-20210129143835021

點擊ADD PROJECT,選擇truffle-config.js 文件,加載我們的Truffle項目

在 Server 菜單中,可以看到 Ganache 生成的區塊鏈的地址、開放的 RPC 端口,NetworkID 等,這些值與我們剛剛創建好的 Ganache 網絡配置匹配,稍后可以使用 truffle 命令部署智能合約:

image-20210129144016165

點擊SAVE AND RESTART 保存更改。在 CONTRACTS 菜單中,提示我們需要使用 Truffle 部署智能合約:

image-20210129144215249

打開終端,輸入以下命令:

truffle migrate --network ganache

image-20210129144846248

合約成功部署:

image-20210129144939541

部署合約需要消耗 ETH,查看ACCOUNTS可以看到默認的第 0 個賬戶消耗掉 0.02380284 個 ETH:

image-20210129144920804

BLOCKTRANSACTIONS 記錄了區塊鏈和交易,可以查看學習。

Ganache 本身不具備 web3 交互,因此要使用合約,還需要進入 Truffle 控制台進行操作:

truffle console --network ganache

第一步依然是獲得剛剛部署過的智能合約的實例:

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

可以看到這個智能合約實例地址與 Ganache 顯示的地址是一致的。

由於目前第0個賬戶是部署合約的賬戶,因此 chairperson 的地址應該為第0個賬戶的地址。同時,由於我們在進入控制台之前沒有設置使用的賬戶,因此默認控制台正在使用的也是第0個賬戶。

現在我們有權利為其他賬戶賦予投票權利,我們當然可以直接在 Ganache 內抄下某個賬戶地址,也可以使用 web3 獲得賬戶地址:

truffle(ganache)> let allAccounts = web3.eth.getAccounts()
undefined
truffle(ganache)> allAccounts
[ '0x78087a3fDd3Ad30Dc23dF8a80eA6fE81Db1b7fbb',
  '0xB7D53a71a1a8A45C9F1DF152cf6DB0F5805261B6',
  '0x605227a90d1566EEeC77AE2e36Ad48dcAe5d6CD4',
  '0x8c4491074a1623A96D62288FCA0aFeD73Ab710e3',
  '0xaB750d95277e2Cd67bA1Effd00d2cb8319170620',
  '0xd619b30e8f019569D59fe6aD557e52E5302F227f',
  '0x5BB81474c351a28507DD5317F4023088b8912f41',
  '0xb5528106D4c92262C3da2d3E29282fd1687eAAA6',
  '0xf79a3C4a0881F879Ddf5D18beB37e5B5767aEFED',
  '0xf3e67be6A334CB438282BCB09A57d7A92eacE03f' ]

將投票權賦予賬戶'0xB7D53a71a1a8A45C9F1DF152cf6DB0F5805261B6':

instance.giveRightToVote('0xB7D53a71a1a8A45C9F1DF152cf6DB0F5805261B6')

此時發生了一筆交易,在Ganache內也可以同步查看:

image-20210129151213677

目前賬戶 '0xB7D53a71a1a8A45C9F1DF152cf6DB0F5805261B6' 獲得了投票權,現在怎么使用該賬戶為某個被選舉人投票呢?

剛才我們提到,默認進入truffle控制台會使用第0個賬戶,要切換賬戶,需要修改網絡配置文件。我們首先退出目前的控制台,修改truffle-config.js文件:

image-20210129151500952

新建一個 ganacheUser1 配置,指定 from 地址,使用此網絡配置文件重新進入控制台:

truffle console --network ganacheUser1

使用投票函數 vote,為某個被選舉人投票,當然首先還是需要獲得智能合約實例:

image-20210129151753348

投票成功了。我們同時可以在 Ganache 內看到交易信息和區塊信息:

image-20210129151848911

使用 winnerName 函數查看得票數最高的被選舉人:

truffle(ganacheUser1)> instance.winnerName()
'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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM