本文详细分析一个自带分发锁仓功能的ERC20智能合约。
ERC20智能合约的行为非常类似于传统的加密货币,例如在不同账户之间发送和接收、 查看通证总供应量或者查看某个地址可用的通证余额等。
1.合约分析
1.1 基础参数
string public constant name = "DAB Token";
string public constant symbol = "DABT";
uint public constant decimals = 18;
uint256 _totalSupply = 60000000 * 10**decimals;
标准erc20合约包含四个基础参数:
1.2 balance()
function balanceOf(address _owner) constant returns (uint256 balance) {
return balances[_owner];
}
该函数接受一个地址作为参数,允许智能合约返回该地址的token余额。所以任何地址的token余额都是公开的。
1.3 approve()
//创建映射表记录通证持有者、被授权者以及授权数量
mapping(address => mapping (address => uint256)) allowed;
function approve(address _spender, uint256 _value) returns (bool success) {
allowed[msg.sender][_spender] = _value;
//当授权时触发Approval事件
Approval(msg.sender, _spender, _value);
return true;
}
此函数的调用方授权给定的地址可以从其地址中提款。
在这里,以及后面的代码片段中,你可能会看到一个变量msg 。 这是由外部应用程序提供的隐含字段,以便更好地与合约进行交互。EVM允许使用该字段来存储和处理由外部应用程序提供的数据。
在这个例子中,msg.sender是合约方法调用方的地址。
1.4 transfer()
mapping(address => uint256) balances; //list of balance of each address
//ERC 20 Standard Token interface transfer function
//Prevent transfers until freeze period is over.
//返回值为true时,表示转账成功
function transfer(address _to, uint256 _value) returns (bool success) {
//如果还没开始,则返回
if (now < baseStartTime) revert();
//Default assumes totalSupply can't be over max (2^256 - 1).
//If your token leaves out totalSupply and can issue more tokens as time goes on, you need to check if it doesn't wrap.
//如果发送方有足够的资金并且发送数量非0 ,则发送给指定地址
if (balances[msg.sender] >= _value && balances[_to] + _value > balances[_to]) {
//计算账号的已解锁资金
uint _freeAmount = freeAmount(msg.sender);
if (_freeAmount < _value) {
return false;
}
balances[msg.sender] -= _value;
balances[_to] += _value;
//触发Transfer事件
Transfer(msg.sender, _to, _value);
return true;
} else {
return false;
}
}
该函数让调用方将指定数量的token发送到另一个地址。
1.5 transferFrom()
mapping(address => uint256) balances;
mapping(address => mapping (address => uint256)) allowed;
//ERC 20 Standard Token interface transfer function
//Prevent transfers until freeze period is over.
function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {
if (msg.sender != founder) revert();
//same as above. Replace this line with the following if you want to protect against wrapping uints.
if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && balances[_to] + _value > balances[_to]) {
uint _freeAmount = freeAmount(_from);
if (_freeAmount < _value) {
return false;
}
balances[_to] += _value;
balances[_from] -= _value;
allowed[_from][msg.sender] -= _value;
Transfer(_from, _to, _value);
return true;
} else { return false; }
}
该函数允许智能合约自动执行转账流程并代表所有者发送指定数量的token。
为什么有了transfer(),还需要transferFrom()呢?
以日常生活通过转账来支付账单的例子说明他们的区别:
1.6 distribute()
mapping(address => uint256) balances; //list of balance of each address
mapping(address => uint256) distBalances; //list of distributed balance of each address to calculate restricted amount
//Distribute tokens out.
function distribute(uint256 _amount, address _to) {
if (msg.sender!=founder) revert();
if (distributed + _amount > _totalSupply) revert();
distributed += _amount;
balances[_to] += _amount;
distBalances[_to] += _amount;
}
该函数允许合约管理者分发token。
刚开始的时候,所有token在合约里,而不在管理者地址,管理者通过distribute分发token。
1.7 setStartTime
function setStartTime(uint _startTime) {
if (msg.sender!=founder) revert();
baseStartTime = _startTime;
}
设置开始时间,可以根据需要修改时间。用于计算解锁token的数量。
1.8 freeAmount()
mapping(address => uint256) balances; //list of balance of each address
mapping(address => uint256) distBalances; //list of distributed balance of each address to calculate restricted amount
function freeAmount(address user) returns (uint256 amount) {
//0) no restriction for founder
if (user == founder) {
return balances[user];
}
//1) no free amount before base start time;
if (now < baseStartTime) {
return 0;
}
//2) calculate number of months passed since base start time;
uint monthDiff = (now - baseStartTime) / (30 days);
//3) if it is over 15 months, free up everything.
if (monthDiff > 15) {
return balances[user];
}
//4) calculate amount of unrestricted within distributed amount.
uint unrestricted = distBalances[user] / 10 + distBalances[user] * 6 / 100 * monthDiff;
if (unrestricted > distBalances[user]) {
unrestricted = distBalances[user];
}
//5) calculate total free amount including those not from distribution
if (unrestricted + balances[user] < distBalances[user]) {
amount = 0;
} else {
amount = unrestricted + (balances[user] - distBalances[user]);
}
return amount;
}
计算解锁token数量,规则为:
1.9 changeFounder()
//Change founder address (where ICO is being forwarded).
function changeFounder(address newFounder) {
if (msg.sender!=founder) revert();
founder = newFounder;
}
转移合约管理权限。
2.部署使用
2.1 部署合约
参考《第四篇 在墨客区块链(MOAC BlockChain) 部署ERC-20合约》进行合约部署。
合约部署完成后主账号并不显示拥有token。
2.2 设置开始时间
如果不想计算,可以直接通过命令:
> mc.getBlock(616325)
得到最新区块的信息,里面包含了时间戳,是从1970-1-1开始计时的,单位“秒”。如果设置到未来的时间,请自行计算。
比如我把开始时间设置到当前这个块。
该操作需要founder权限,完成后BaseStartTime就变成刚才的设置值了。
2.3 分发token
该合约的所有token都是通过Distribute分发的。如果分发给founder,所有token可以自由交易;如果分发给其他地址,则按照规则计算锁仓情况。
只有释放过的token,才能通过transfer进行交易。
注意:该步骤中的数量(amount)是带18位小数位的。
其他功能与普通ERC20合约相同,比如transfer、TransferFrom、approve、balanceOf等。本文不详细解释。
3.合约的调用
var Chain3 = require('chain3');
var chain3 = new Chain3(new Chain3.providers.HttpProvider('http://localhost:8545'));
var contractAddress = "0x792a0762Fd251eEB722C4836Ca5a1cF005958249";
var abiString = '[ { "constant": true, ...... "type": "event" } ]';
//调用erc20合约
//查询余额
var address = "0x794E32311857411d05910bf04019Ba4c4fe2703C";
callContract1(chain3, contractAddress, address, abiString);
console.log("address: ",address);
function callContract1(chain3, contractAddress, address, abiString){
var abi = JSON.parse(abiString);
var contract = chain3.mc.contract(abi);
var token = contract.at(contractAddress);
token.balanceOf.call(address, function(err, result){
console.log(err, JSON.stringify(result));
});
}
//查询free数量
callContract2(chain3, contractAddress, address, abiString);
function callContract2(chain3, contractAddress, address, abiString){
var abi = JSON.parse(abiString);
var contract = chain3.mc.contract(abi);
var token = contract.at(contractAddress);
token.freeAmount.call(address, function(err, result){
console.log(err, JSON.stringify(result));
});
}
balance - free就是被锁住的token数量。
附件 合约源码
//ERC 20 token
pragma solidity ^0.4.11;
contract DABToken {
string public constant name = "DAB Token";
string public constant symbol = "DABT";
uint public constant decimals = 18;
uint256 _totalSupply = 60000000 * 10**decimals;
function totalSupply() constant returns (uint256 supply) {
return _totalSupply;
}
function balanceOf(address _owner) constant returns (uint256 balance) {
return balances[_owner];
}
function approve(address _spender, uint256 _value) returns (bool success) {
allowed[msg.sender][_spender] = _value;
Approval(msg.sender, _spender, _value);
return true;
}
function allowance(address _owner, address _spender) constant returns (uint256 remaining) {
return allowed[_owner][_spender];
}
mapping(address => uint256) balances; //list of balance of each address
mapping(address => uint256) distBalances; //list of distributed balance of each address to calculate restricted amount
mapping(address => mapping (address => uint256)) allowed;
uint public baseStartTime; //All other time spots are calculated based on this time spot.
// Initial founder address (set in constructor)
// All deposited will be instantly forwarded to this address.
address public founder;
uint256 public distributed = 0;
event AllocateFounderTokens(address indexed sender);
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
//constructor
function DABToken() {
founder = msg.sender;
}
function setStartTime(uint _startTime) {
if (msg.sender!=founder) revert();
baseStartTime = _startTime;
}
//Distribute tokens out.
function distribute(uint256 _amount, address _to) {
if (msg.sender!=founder) revert();
if (distributed + _amount > _totalSupply) revert();
distributed += _amount;
balances[_to] += _amount;
distBalances[_to] += _amount;
}
//ERC 20 Standard Token interface transfer function
//Prevent transfers until freeze period is over.
function transfer(address _to, uint256 _value) returns (bool success) {
if (now < baseStartTime) revert();
//Default assumes totalSupply can't be over max (2^256 - 1).
//If your token leaves out totalSupply and can issue more tokens as time goes on, you need to check if it doesn't wrap.
if (balances[msg.sender] >= _value && balances[_to] + _value > balances[_to]) {
uint _freeAmount = freeAmount(msg.sender);
if (_freeAmount < _value) {
return false;
}
balances[msg.sender] -= _value;
balances[_to] += _value;
Transfer(msg.sender, _to, _value);
return true;
} else {
return false;
}
}
function freeAmount(address user) returns (uint256 amount) {
//0) no restriction for founder
if (user == founder) {
return balances[user];
}
//1) no free amount before base start time;
if (now < baseStartTime) {
return 0;
}
//2) calculate number of months passed since base start time;
uint monthDiff = (now - baseStartTime) / (30 days);
//3) if it is over 15 months, free up everything.
if (monthDiff > 15) {
return balances[user];
}
//4) calculate amount of unrestricted within distributed amount.
uint unrestricted = distBalances[user] / 10 + distBalances[user] * 6 / 100 * monthDiff;
if (unrestricted > distBalances[user]) {
unrestricted = distBalances[user];
}
//5) calculate total free amount including those not from distribution
if (unrestricted + balances[user] < distBalances[user]) {
amount = 0;
} else {
amount = unrestricted + (balances[user] - distBalances[user]);
}
return amount;
}
//Change founder address (where ICO is being forwarded).
function changeFounder(address newFounder) {
if (msg.sender!=founder) revert();
founder = newFounder;
}
//ERC 20 Standard Token interface transfer function
//Prevent transfers until freeze period is over.
function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {
if (msg.sender != founder) revert();
//same as above. Replace this line with the following if you want to protect against wrapping uints.
if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && balances[_to] + _value > balances[_to]) {
uint _freeAmount = freeAmount(_from);
if (_freeAmount < _value) {
return false;
}
balances[_to] += _value;
balances[_from] -= _value;
allowed[_from][msg.sender] -= _value;
Transfer(_from, _to, _value);
return true;
} else { return false; }
}
function() payable {
if (!founder.call.value(msg.value)()) revert();
}
}