美文网首页区块链资料
以太坊开发(二)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