Bancor的github里公布的代码算是我见过的项目里比较规范的了,而且还提供了很多test实例,确实花了很大的功夫。最近这段时间会关注分析一下这个项目的代码,跟大家分享一下Solidity这种语言是如何实现项目规范化的。
Github地址:https://github.com/bancorprotocol/contracts
如何入手?
正好这个项目用的是truffle框架,我们先解释一下truffle项目部署(仅涉及合约部分)的作业流程:
- truffle框架下一般有contracts, migration, test三个文件夹,另外还有一个truffle-config.js文件。contracts放的是智能合约文件*.sol;migration放的是合约部署的设定js文件;test放的是测试事件。
- 一般来说,合约写好之后会在项目目录下运行truffle compile,目录下会产生一个build文件夹,里面存放了编译后的bytecode和abi。
- 接着需要运行truffle migrate让合约上链(一般来说都会是本地的Ganache/旧版叫testrpc)
- 运行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转接的功能,可以在后面的众筹合约中直接应用。
下一篇将开始实际的代币合约分析,敬请期待!