区块链智能合约:去中心化交易所Bancor业务合约分析(一)

Bancor的github里公布的代码算是我见过的项目里比较规范的了,而且还提供了很多test实例,确实花了很大的功夫。最近这段时间会关注分析一下这个项目的代码,跟大家分享一下Solidity这种语言是如何实现项目规范化的。

Github地址:https://github.com/bancorprotocol/contracts

如何入手?

正好这个项目用的是truffle框架,我们先解释一下truffle项目部署(仅涉及合约部分)的作业流程:

  1. truffle框架下一般有contracts, migration, test三个文件夹,另外还有一个truffle-config.js文件。contracts放的是智能合约文件*.sol;migration放的是合约部署的设定js文件;test放的是测试事件。
  2. 一般来说,合约写好之后会在项目目录下运行truffle compile,目录下会产生一个build文件夹,里面存放了编译后的bytecode和abi。
  3. 接着需要运行truffle migrate让合约上链(一般来说都会是本地的Ganache/旧版叫testrpc)
  4. 运行truffle test执行测试事件,terminal会实时输出结果。

Bancor的项目写得比较标准,我们可以直接去migration/2_deploy_contract.js看看Bancor一整套合约的部署流程,然后再逐一进行分析。

/* global artifacts */
/* eslint-disable prefer-reflect */

const Utils = artifacts.require('Utils.sol');
const Owned = artifacts.require('Owned.sol');
const Managed = artifacts.require('Managed.sol');
const TokenHolder = artifacts.require('TokenHolder.sol');
const ERC20Token = artifacts.require('ERC20Token.sol');
const EtherToken = artifacts.require('EtherToken.sol');
const SmartToken = artifacts.require('SmartToken.sol');
const SmartTokenController = artifacts.require('SmartTokenController.sol');
const BancorFormula = artifacts.require('BancorFormula.sol');
const BancorGasPriceLimit = artifacts.require('BancorGasPriceLimit.sol');
const BancorQuickConverter = artifacts.require('BancorQuickConverter.sol');
const BancorConverterExtensions = artifacts.require('BancorConverterExtensions.sol');
const BancorConverter = artifacts.require('BancorConverter.sol');
const CrowdsaleController = artifacts.require('CrowdsaleController.sol');

module.exports = async (deployer) => {
    deployer.deploy(Utils);
    deployer.deploy(Owned);
    deployer.deploy(Managed);
    deployer.deploy(TokenHolder);
    deployer.deploy(ERC20Token, 'DummyToken', 'DUM', 0);
    deployer.deploy(EtherToken);
    await deployer.deploy(SmartToken, 'Token1', 'TKN1', 2);
    deployer.deploy(SmartTokenController, SmartToken.address);
    deployer.deploy(BancorFormula);
    deployer.deploy(BancorGasPriceLimit, '22000000000');
    deployer.deploy(BancorQuickConverter);
    deployer.deploy(BancorConverterExtensions, '0x125463', '0x145463', '0x125763');
    deployer.deploy(BancorConverter, SmartToken.address, '0x124', 0, '0x0', 0);
    deployer.deploy(CrowdsaleController, SmartToken.address, 4102444800, '0x125', '0x126', 1);
};

从名称就可以看出来Bancor的合约分三类:

类别 名称
基础支持 Utils.sol, Owned.sol, Managed.sol
代币实现 TokenHolder.sol, ERC20Token.sol, EtherToken.sol, SmartToken.sol
交易逻辑 SmartTokenController.sol, BancorFormula.sol, BancorGasPriceLimit.sol, BancorQuickConverter.sol,
BancorConverterExtensions.sol, BancorConverter.sol, CrowdsaleController.sol

这次的第一节会快速过完第一类和第二类的一半XD

基础支持合约

Utils.sol

pragma solidity ^0.4.18;
/* Utilities & Common Modifiers */
contract Utils {
    /** constructor  **/
    function Utils() public {
    }
    // verifies that an amount is greater than zero
    modifier greaterThanZero(uint256 _amount) {
        require(_amount > 0);
        _;
    }
    // validates an address - currently only checks that it isn't null
    modifier validAddress(address _address) {
        require(_address != address(0));
        _;
    }
    // verifies that the address is different than this contract address
    modifier notThis(address _address) {
        require(_address != address(this));
        _;
    }
    // Overflow protected math functions
    /** @dev returns the sum of _x and _y, asserts if the calculation overflows
        @param _x   value 1
        @param _y   value 2
        @return sum
    */
    function safeAdd(uint256 _x, uint256 _y) internal pure returns (uint256) {
        uint256 z = _x + _y;
        assert(z >= _x);
        return z;
    }
    /** @dev returns the difference of _x minus _y, asserts if the subtraction results in a negative number
        @param _x   minuend
        @param _y   subtrahend
        @return difference
    */
    function safeSub(uint256 _x, uint256 _y) internal pure returns (uint256) {
        assert(_x >= _y);
        return _x - _y;
    }
    /** @dev returns the product of multiplying _x by _y, asserts if the calculation overflows
        @param _x   factor 1
        @param _y   factor 2
        @return product
    */
    function safeMul(uint256 _x, uint256 _y) internal pure returns (uint256) {
        uint256 z = _x * _y;
        assert(_x == 0 || z / _x == _y);
        return z;
    }
}

总的来说也没什么特别的,modifier中确认的都是数字格式和地址问题,加减乘方法都用internal pure封装,也没有外来攻击可言。

Owned.sol和Managed.sol
这两个文件内容基本一样,具体负责的功能不同所以分开了,我这里只贴一份。

pragma solidity ^0.4.18;
/*    Provides support and utilities for contract management   */
contract Managed {
    address public manager;
    address public newManager;
    event ManagerUpdate(address indexed _prevManager, address indexed _newManager);

    /**        @dev constructor    */
    function Managed() public {
        manager = msg.sender;
    }
    // allows execution by the manager only
    modifier managerOnly {
        assert(msg.sender == manager);
        _;
    }
    /** @dev allows transferring the contract management
        the new manager still needs to accept the transfer
        can only be called by the contract manager
        @param _newManager    new contract manager
    */
    function transferManagement(address _newManager) public managerOnly {
        require(_newManager != manager);
        newManager = _newManager;
    }
    /** @dev used by a new manager to accept a management transfer    */
    function acceptManagement() public {
        require(msg.sender == newManager);
        ManagerUpdate(manager, newManager);
        manager = newManager;
        newManager = address(0);
    }
}

这个合约实际上跟OpenZeppelin (https://github.com/OpenZeppelin/zeppelin-solidity) 项目中的Owner.sol很像,基本功能就是在创建的时候把创建人自动设置成合约主人,增加ownerOnly modifier在后续函数中提供交易提出方的身份校验。唯一的区别就是转移ownership的方式不同。

  • OpenZeppelin:直接设定newOwner address后转移,一步到位。
  • Bancor:current owner更新newOwner地址,newOwner再通过acceptOwnership去接收,保证了newOwner的合法性,在acceptOwnership发生前current owner都可以再更新地址,有更高的容错性。但消耗的gas肯定更高了。

代币实现合约:接口(interface)篇

到这里事情就开始有点复杂了,我们需要引入一个新的概念:interface。
interface在编译中其实并不产生任何字节码,但是在coding过程中会帮忙起到一个代码规范的效果。Bancor的interface文件夹中一共有10个interface,跟代币实现相关的interface有4个(或者说5个,因为IOwned.sol也算在里面):

/*    Owned contract interface    */
contract IOwned {
    // this function isn't abstract since the compiler emits automatically generated getter functions as external
    function owner() public view returns (address) {}
    function transferOwnership(address _newOwner) public;
    function acceptOwnership() public;
}

/*   Token Holder interface    */
contract ITokenHolder is IOwned {
    function withdrawTokens(IERC20Token _token, address _to, uint256 _amount) public;
}

/*    ERC20 Standard Token interface    */
contract IERC20Token {
    // these functions aren't abstract since the compiler emits automatically generated getter functions as external
    function name() public view returns (string) {}
    function symbol() public view returns (string) {}
    function decimals() public view returns (uint8) {}
    function totalSupply() public view returns (uint256) {}
    function balanceOf(address _owner) public view returns (uint256) { _owner; }
    function allowance(address _owner, address _spender) public view returns (uint256) { _owner; _spender; 
    function transfer(address _to, uint256 _value) public returns (bool success);
    function transferFrom(address _from, address _to, uint256 _value) public returns (bool success);
    function approve(address _spender, uint256 _value) public returns (bool success);
}

/*    Ether Token interface    */
contract IEtherToken is ITokenHolder, IERC20Token {
    function deposit() public payable;
    function withdraw(uint256 _amount) public;
    function withdrawTo(address _to, uint256 _amount) public;
}

/*    Smart Token interface    */
contract ISmartToken is IOwned, IERC20Token {
    function disableTransfers(bool _disable) public;
    function issue(address _to, uint256 _amount) public;
    function destroy(address _from, uint256 _amount) public;
}

根据这几个interface就可以画出一个简单的结构图,图中可以看出,SmartToken是ERC20Token的一类,加入了Owned功能封装;而EtherToken中附加的功能包括:提取token,eth充值,一看即知,它承担了token与外部eth转接的功能,可以在后面的众筹合约中直接应用。


TokenStruc.png

下一篇将开始实际的代币合约分析,敬请期待!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容