Truffle 框架开发 Dapp 中的注意事项

Author Avatar
patrickcty 5月 01, 2018

介绍

Truffle 是开发 Ethereum 的一个框架。

编写合约

概述

智能合约使用的是 solidity 语言,这个语言在一些地方与平常使用的高级语言不同,因而可能会遇到一些坑。

注意事项

变量

  • solidity 中变量分为两类:memory 和 storage,前者就类似于我们平常语言中的变量,存放在内存中,在调用结束后就会释放掉。
  • 而 storage 类型的变量则会存放在区块链中,因而它实际表示的是指针,也正是因为这个原因,这两种变量不能混合使用。
  • 另外,定义在 Contract 函数之外的变量默认是 storage 类型的。

事件

  • solidity 中 event 的作用就是把发生的事件记录在区块链上成为日志。事件可以通过 web3 来监听。
// 定义事件
event transfer(address indexed _from, address indexed _to, uint indexed value);

// 事件发生,新版本需要加入 emit 关键词
emit transfer(msg.sender, current, value);

映射

mapping(address => User) userPool;
  • solidty 中的映射不需要初始化,而且对于不在映射中的内容,通过 m[key] 来调用并不会返回类似于 null 的内容,而是会返回这个类型的默认值,比如 uint 返回的就是 0,因而要检查是否在映射中的时候可能需要辅助的属性:
struct Num {
    uint count,
    bool inMap
}

mapping(uint => Num) numMap;

if (numMap[1].inMap) { 在映射中 }
else { 不在映射中 }

字符串比较

  • solidity 中不能直接使用 == 来比较字符串,需要一个一个字符来比较或者来比较二者的 hash 值:
keccak256(str1) == keccak256(str2)

msg.sender && tx.origin

  • 在用户调用合约的时候,msg.sender 和 tx.origin 都是用户的地址
  • 在合约调用合约的时候,msg.sender 是 合约的地址,而 tx.origin 是用户

这个一定要注意,我就在这里卡了很久。

三个常用方法

require()

里面是一个 bool 表达式,为真的时候才会执行下面的内容。用来检查不严重的错误,可以退回 gas。

assert()

这个方法和 require 类似,但是一般是用来检查严重的错误,会拿走 gas。

revert()

这个方法里面没有参数,会终止并且恢复之前的状态。

数组

  • solidity 数组可以定义不定长的数组,但是要初始化空的不定长数组则要使用 new
// 这里默认是长度为一的数组
// 但是可以通过两种方法来改变:
// 1. 直接改变长度:list.length++
// 2. 通过 push 来增加元素 list.push(123)
uint[] list = new int[](1);

truffle 常用命令

compile

truffle compile

这个命令用来把合约编译成 EVM 可以读懂的语言

migrate

truffle migrate

这个命令用来把合约部署到区块链上,在修改合约之后就不用再次编译,直接使用这个命令部署即可

如果想清除掉之前的数据而不像删除掉编译已生成的 build 则可以加一个 --reset 参数

test

truffle test

这个命令会运行 test 文件夹中的单元测试,其中单元测试文件要以 Test 作为开头,单元测试方法需要以 test 开头

在 js 中调用合约

创建 web3 对象

if (typeof web3 !== 'undefined') {
    App.web3Provider = web3.currentProvider;
    web3 = new Web3(web3.currentProvider);
} else {
    App.web3Provider = new Web3(new Web3.providers.HttpProvider('http://localhost:7545'));
    web3 = new Web3(web3.web3Provider);
}

如果已经存在就直接绑定,不存在的话就根据本地的内容来创建再绑定。

读取合约

$.getJSON('TeamMarket.json', function(data) {
    var TMArtifact = data;
    App.contracts.TeamMarket = TruffleContract(TMArtifact);

    App.contracts.TeamMarket.setProvider(App.web3Provider);

    App.initCompetitions();
    
    return App.markJoined();
});

这里的 json 文件是编译所产生的,其中要注意 getJSON 中的 JSON 是大写的,不然就会出现傻逼的错误(说的就是我……)。

另外在 HTML 部分记得要引入 truffle 相应的 js 文件。

和合约交互

web3.eth.getAccounts(function(error, accounts) {
    if (error) {
        console.log(error);
    }

    var account = accounts[0];

    App.contracts.TeamMarket.deployed().then(function(instance) {
        TMInstance = instance;

        return TMInstance.createUser(name, email, major);
    }).then(function () {
        alert("创建账号成功,等待写入区块链");
    }).catch(function(err) {
        alert("有错误发生,创建账号失败");
        console.log(err.message);
    });
});

这里是通过 web3 接口来读取用户信息,返回的是一个 list,一般第一个就是当前用户

然后获取合约的实例App.contracts.TeamMarket.deployed(),这里是一个 Promise,在 then 里面就已经获取到了相应的实例,然后再通过这个实例来调用合约里面的方法就可以了。

不过在调用合约之后的 then 中不知道为什么有时候不会执行里面的语句……还得研究研究……

配置文件

truffle.js

module.exports = {
  networks: {
    development: {
      host: "127.0.0.1",
      port: 7545,
      network_id: "*",
    }
  }
};

这里是配置区块链网络

package.json

{
  "name": "teamseeker",
  "version": "1.0.0",
  "description": "",
  "main": "truffle.js",
  "directories": {
    "test": "test"
  },
  "scripts": {
    "dev": "lite-server",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "lite-server": "^2.3.0"
  },
  "dependencies": {
    "web3": "^1.0.0-beta.34"
  }
}

这里更多是运行的 js 的一系列配置了,比如在这里配置了成序运行所使用的服务器