美文网首页区块链资料
以太坊开发(二)Truffle示例项目拆解

以太坊开发(二)Truffle示例项目拆解

作者: 朱建涛 | 来源:发表于2018-08-18 22:52 被阅读326次

    本篇文章将通过分析Truffle官网提供的简单示例项目metacoin,webpack学习怎样写智能合约,迁移脚本和测试脚本,为开发自己的DApp打下基础。

    MetaCoin是一个简单的代币项目。下载代码来看看吧~

    zhujiantao@ubuntu:~$ mkdir truffle-examples
    zhujiantao@ubuntu:~$ cd truffle-examples/
    zhujiantao@ubuntu:~/truffle-examples$ mkdir MetaCoin
    zhujiantao@ubuntu:~/truffle-examples$ cd MetaCoin/
    zhujiantao@ubuntu:~/truffle-examples/MetaCoin$ truffle unbox metacoin
    Downloading...
    Unpacking...
    Setting up...
    Unbox successful. Sweet!
    
    Commands:
    
      Compile contracts: truffle compile
      Migrate contracts: truffle migrate
      Test contracts:    truffle test
    zhujiantao@ubuntu:~/truffle-examples/MetaCoin$ tree
    .
    ├── contracts
    │   ├── ConvertLib.sol
    │   ├── MetaCoin.sol
    │   └── Migrations.sol
    ├── migrations
    │   ├── 1_initial_migration.js
    │   └── 2_deploy_contracts.js
    ├── test
    │   ├── metacoin.js
    │   └── TestMetacoin.sol
    ├── truffle-config.js
    └── truffle.js
    
    3 directories, 9 files
    

    项目架构在上篇文章中也有介绍
    contracts目录下存放合约文件,其中Migrations.sol合约较为特殊,会在项目初始化的时候被创建,是Truffle框架里必须的文件,你运行的迁移历史记录会被他记录在区块链上。
    migrations下存放迁移部署脚本,用来帮助你把合约发布到以太坊网络中。
    test目录下存放测试脚本。Truffle内置自动化测试框架,让我们可以方便的测试自己的合约。该框架允许我们以两种不同的方式编写测试用例:1,使用Javascript编写测试用例,测试从外部执行合约 2,使用Solidity编写测试用例,测试从内部调用合约。
    truffle.js和truffle-config.js为Truffle配置文件,配置了运行网络等相关信息。

    合约Migrations.sol

    pragma solidity ^0.4.2;    //声明Solidity版本,兼容0.4.2以下版本
    
    contract Migrations {        //contract声明为合约,在Truffle中合约名称需和文件名一致
      address public owner;        //公开(外部可见),地址类型全局变量owner,用于保存合约所有者地址
      uint public last_completed_migration;    //公开,无符号整型全局变量last_completed_migration,在每次合约部署时更新
    
      modifier restricted() {        //修饰符,通常用于函数执行前检查某种前置条件是否满足
        if (msg.sender == owner) _;    //判断调用合约的是不是合约所有者
      }
    
      constructor() public {        //构造函数,在合约部署时执行
        owner = msg.sender;    //将合约所有者赋值给owner变量//msg为solidity中的全局变量,msg.sender表示合约发送者地址
      }
    
      function setCompleted(uint completed) public restricted {    //用于更新last_completed_migration变量的函数
        last_completed_migration = completed;
      }
    
      function upgrade(address new_address) public restricted {    //用于更新合约所有者
        Migrations upgraded = Migrations(new_address);
        upgraded.setCompleted(last_completed_migration);
      }
    }
    

    Solidity在全局命名空间中预设了一些特殊的变量和函数,用来提供关于区块链的信息和一些通用的工具函数

    • block.blockhash(uint blockNumber) returns (bytes32):指定区块的区块哈希——仅可用于最新的 256 个区块且不包括当前区块;而 blocks 从 0.4.22 版本开始已经不推荐使用,由 blockhash(uint blockNumber) 代替
    • block.coinbase (address): 挖出当前区块的矿工地址
    • block.difficulty (uint): 当前区块难度
    • block.gaslimit (uint): 当前区块 gas 限额
    • block.number (uint): 当前区块号
    • block.timestamp (uint): 自 unix epoch 起始当前区块以秒计的时间戳
    • gasleft() returns (uint256):剩余的 gas
    • msg.data (bytes): 完整的 calldata
    • msg.gas (uint): 剩余 gas - 自 0.4.21 版本开始已经不推荐使用,由 gesleft() 代替
    • msg.sender (address): 消息发送者(当前调用)
    • msg.sig (bytes4): calldata 的前 4 字节(也就是函数标识符)
    • msg.value (uint): 随消息发送的 wei 的数量
    • now (uint): 目前区块时间戳(block.timestamp)
    • tx.gasprice (uint): 交易的 gas 价格
    • tx.origin (address): 交易发起者(完全的调用链)

    合约MetaCoin.sol

    pragma solidity ^0.4.18;
    
    import "./ConvertLib.sol";        //导入外部类库
    
    contract MetaCoin {
            mapping (address => uint) balances;    //mapping类型变量,用于保存地址对应余额信息
    
            event Transfer(address indexed _from, address indexed _to, uint256 _value);//event事件,用于通知外部    //indexd修饰符可用于事件过滤
    
            constructor() public {
                    balances[tx.origin] = 10000;    //给交易发起者账号余额赋值
            }
    
            function sendCoin(address receiver, uint amount) public returns(bool sufficient) { //发送MetaCoin方法,参数为接收者地址和交易币数量,返回值为交易成功失败状态
                    if (balances[msg.sender] < amount) return false;    //需要合约发起者余额充足
                    balances[msg.sender] -= amount;
                    balances[receiver] += amount;
                    emit Transfer(msg.sender, receiver, amount);        //交易后触发事件,通知外部
                    return true;
            }
    
            function getBalanceInEth(address addr) public view returns(uint){    //获取某个地址余额,转换为Eth返回//view修饰符保证不修改状态
                    return ConvertLib.convert(getBalance(addr),2);    
            }
    
            function getBalance(address addr) public view returns(uint) {    //根据地址获取余额
                    return balances[addr];
            }
    }
    

    库ConvertLib.sol

    pragma solidity ^0.4.4;
    
    library ConvertLib{
            function convert(uint amount,uint conversionRate) public pure returns (uint convertedAmount)
            {
                    return amount * conversionRate;
            }
    }
    

    library是一种不同类型的合约,没有存储,不用有以太币。库中的代码可以被其他合约调用,而不需要重新部署,这样可以节省大量gas。该库提供了convert函数转换MetaCoin和ETH,一个MetaCoin值conversionRate个ETH。

    迁移脚本1_initial_migration.js

    var Migrations = artifacts.require("./Migrations.sol");    //告诉测试脚本要和哪个合约交互
    
    module.exports = function(deployer) {    
      deployer.deploy(Migrations);            //迁移脚本使用deployer对象部署合约
    };
    

    可以看到迁移脚本命名规则是以数字开头,后面加上描述性字符串。数字前缀是必须的,用于记录迁移记录,在前面Migration.sol中可以看到相关代码。

    迁移脚本2_deploy_contracts.js

    var ConvertLib = artifacts.require("./ConvertLib.sol");
    var MetaCoin = artifacts.require("./MetaCoin.sol");
    
    module.exports = function(deployer) {
      deployer.deploy(ConvertLib);
      deployer.link(ConvertLib, MetaCoin);        //把已部署好的库链接到MetaCoin合约
      deployer.deploy(MetaCoin);
    };
    

    测试脚本metacoin.js

    var MetaCoin = artifacts.require("./MetaCoin.sol");        //指明要测试的合约
    
    contract('MetaCoin', function(accounts) {        //contract 表示test sunit测试套件,表示一组相关测试,第一个参数MetaCoin为套件名称,第二个参数为执行函数
      it("should put 10000 MetaCoin in the first account", function() {    //it 表示test case测试用例,第一个参数为测试用例名称,第二个参数为执行函数
        return MetaCoin.deployed().then(function(instance) {
          return instance.getBalance.call(accounts[0]);
        }).then(function(balance) {
          assert.equal(balance.valueOf(), 10000, "10000 wasn't in the first account");
        });
      });
      it("should call a function that depends on a linked library", function() {
        var meta;
        var metaCoinBalance;
        var metaCoinEthBalance;
    
        return MetaCoin.deployed().then(function(instance) {
          meta = instance;
          return meta.getBalance.call(accounts[0]);
        }).then(function(outCoinBalance) {
          metaCoinBalance = outCoinBalance.toNumber();
          return meta.getBalanceInEth.call(accounts[0]);
        }).then(function(outCoinBalanceEth) {
          metaCoinEthBalance = outCoinBalanceEth.toNumber();
        }).then(function() {
          assert.equal(metaCoinEthBalance, 2 * metaCoinBalance, "Library function returned unexpected function, linkage may be broken");
        });
      });
      it("should send coin correctly", function() {
        var meta;
    
        // Get initial balances of first and second account.
        var account_one = accounts[0];
        var account_two = accounts[1];
    
        var account_one_starting_balance;
        var account_two_starting_balance;
        var account_one_ending_balance;
        var account_two_ending_balance;
    
        var amount = 10;
    
        return MetaCoin.deployed().then(function(instance) {
          meta = instance;
          return meta.getBalance.call(account_one);
        }).then(function(balance) {
          account_one_starting_balance = balance.toNumber();
          return meta.getBalance.call(account_two);
        }).then(function(balance) {
          account_two_starting_balance = balance.toNumber();
          return meta.sendCoin(account_two, amount, {from: account_one});
        }).then(function() {
          return meta.getBalance.call(account_one);
        }).then(function(balance) {
          account_one_ending_balance = balance.toNumber();
          return meta.getBalance.call(account_two);
        }).then(function(balance) {
          account_two_ending_balance = balance.toNumber();
    
          assert.equal(account_one_ending_balance, account_one_starting_balance - amount, "Amount wasn't correctly taken from the sender");
          assert.equal(account_two_ending_balance, account_two_starting_balance + amount, "Amount wasn't correctly sent to the receiver");
        });
      });
    });
    

    一大串,,,不过可以看到是从外部执行,在测试结束后可以看到各个测试用例测试结果及所用时间信息。

    测试脚本TestMetacoin.sol

    pragma solidity ^0.4.2;
    
    import "truffle/Assert.sol";            //导入Truffle内置断言库
    import "truffle/DeployedAddresses.sol";
    import "../contracts/MetaCoin.sol";
    
    contract TestMetacoin {
    
      function testInitialBalanceUsingDeployedContract() public {
        MetaCoin meta = MetaCoin(DeployedAddresses.MetaCoin());    //部署合约并获取合约抽象,记住这个写法就好啦
    
        uint expected = 10000;
    
        Assert.equal(meta.getBalance(tx.origin), expected, "Owner should have 10000 MetaCoin initially");
      }
    
      function testInitialBalanceWithNewMetaCoin() public {
        MetaCoin meta = new MetaCoin();
    
        uint expected = 10000;
    
        Assert.equal(meta.getBalance(tx.origin), expected, "Owner should have 10000 MetaCoin initially");
      }
    
    }
    

    这个就简洁多了,emmmm。。

    需要补充的是当执行编译后,在build文件夹下生成了对应的json文件,json文件由ABI(Application Binary Interface)应用二进制接口,bytecode合约编译后二进制内容等组成

    zhujiantao@ubuntu:~/truffle-examples/MetaCoin$ truffle compile
    Compiling ./contracts/ConvertLib.sol...
    Compiling ./contracts/MetaCoin.sol...
    Compiling ./contracts/Migrations.sol...
    Writing artifacts to ./build/contracts
    
    zhujiantao@ubuntu:~/truffle-examples/MetaCoin$ tree
    .
    ├── build
    │   └── contracts
    │       ├── ConvertLib.json
    │       ├── MetaCoin.json
    │       └── Migrations.json
    ├── contracts
    │   ├── ConvertLib.sol
    │   ├── MetaCoin.sol
    │   └── Migrations.sol
    ├── migrations
    │   ├── 1_initial_migration.js
    │   └── 2_deploy_contracts.js
    ├── test
    │   ├── metacoin.js
    │   └── TestMetacoin.sol
    ├── truffle-config.js
    └── truffle.js
    
    5 directories, 12 files
    

    哈,MetaCoin分析完毕。webpack相当于在MetaCoin项目的基础上进行了补充,增加了服务和前端界面,可以通过网页进行转账操作,厉害了。一起来玩一玩吧。
    先下载编译

    $ truffle unbox webpack
    $ truffle compile
    

    查看truffle.js可以看到webpack默认指定的网络是ganache。

    module.exports = {
      networks: {
        ganache: {
          host: '127.0.0.1',
          port: 7545,
          network_id: '*' // Match any network id
        }
      }
    }
    

    官网下载ganache并启动。


    image.png

    部署合约到ganache

    zhujiantao@ubuntu:~/truffle-examples/WebPack$ truffle migrate --network ganache
    Using network 'ganache'.
    
    Running migration: 1_initial_migration.js
      Deploying Migrations...
      ... 0xbe34f8f11c1dcf1706743fa3923f205a142a7abc85483fe7fba16e175229d9bc
      Migrations: 0xa0e137e3429607bf15eeb1fa9aa7add5c586f9b3
    Saving successful migration to network...
      ... 0x1c6690bb21c421d30a04c96d1fa2a56619c97b21e16dba31e42a079db2094557
    Saving artifacts...
    Running migration: 2_deploy_contracts.js
      Deploying ConvertLib...
      ... 0xd28c5de62b584b4a4cf926a3e03c49f2efe23b5c261d088cf26952a0914a53e0
      ConvertLib: 0x4fbaf174a9275377fd406433dc4a7d4c91ca9626
      Linking ConvertLib to MetaCoin
      Deploying MetaCoin...
      ... 0xadfe02879726a61ce7686bf69cdbbd382bf95e942a925a9c713bcc4e0669f31b
      MetaCoin: 0x6c43768bcf9760e3338bf60786c9d6be36d4876e
    Saving successful migration to network...
      ... 0xbe8fbc8e4fd8bd438452f220b77d812dc2299583d4b19790b40cfa1b0a5d709d
    Saving artifacts...
    

    把服务运行起来

    zhujiantao@ubuntu:~/truffle-examples/WebPack$ npm run dev
    
    > truffle-init-webpack@0.0.2 dev /home/zhujiantao/truffle-examples/WebPack
    > webpack-dev-server
    
    ℹ 「wds」: Project is running at http://localhost:8081/
    ℹ 「wds」: webpack output is served from /
    

    打开网页


    image.png

    哇,可以通过前端转账,接下来就自己去耍吧。

    希望看了文章的同学能对Solidity语言,Truffle项目架构以及迁移、测试脚本的编写有一定了解,当然,更全面的学习,请上Solidity官方网站Truffle官网

    接下来,还要去解锁更复杂的项目,并试着写出自己的Dapp。任重道远,加油加油!

    相关文章

      网友评论

        本文标题:以太坊开发(二)Truffle示例项目拆解

        本文链接:https://www.haomeiwen.com/subject/bdjqiftx.html