在《第四篇 在墨客区块链(MOAC BlockChain) 部署ERC-20合约》中,附件为标准erc20合约,在实际使用部署中,有很多项目需要用到扩展功能。本文在该erc20标准的基础上扩展合约功能。
1. 定义合约部署账号
从安全角度出发,本文的扩展功能大多需要合约部署账号权限。
本文将其定义为minter,并在合约部署时得到minter。
contract TokenDemo is Token {
address minter; //定义合约部署账号
function TokenDemo(uint256 initialAmount, string tokenName, uint8 decimalUnits, string tokenSymbol) public {
minter = msg.sender; //在合约部署时得到
}
//只能通过智能合约的部署账号才能调用的方法
modifier onlyOwner() {
require(msg.sender == minter);
_;
}
}
使用者需要将这两行代码分别增加到contract TokenDemo和function TokenDemo。
2.合约接收mc
默认情况下,合约是不能接收mc的。
function() payable public {}
加上这个函数后,合约地址就可以接收mc了。
payable关键词是编译器支持的,合约里的函数是否带有payable关键词,编译器会生成不一样的代码。如果对应的函数不支持转账,对应的函数的代码会通过revert指令撤销前面的转账操作。
如果函数没有任何参数,只能在合约销毁时(详见第8节),合约里的mc才会自动回到minter账号。
3.修改token基础参数
//修改name
function setName (string _value) public returns (bool) {
require(msg.sender == minter);
name = _value;
return true;
}
//修改symbol
function setSymbol (string _value) public returns (bool) {
require(msg.sender == minter);
symbol = _value;
return true;
}
4. 增加token总量
在标准erc20合约中,token总量在部署合约的时候确定,且不能做任何更改。
如果需要增加token总量,比较简单的方法是写一个增量的函数。
function addTokenTotal(uint256 _addAmount) public returns (bool success){
require(msg.sender == minter); //只有合约部署账号得到授权
require(_addAmount > 0); //增量必须大于0
totalSupply += _addAmount * 10 ** decimals; //增发后总量增加
balances[msg.sender] += _addAmount * 10 ** decimals; //增发量自动归到合约部署账号
return true;
}
直接添加该函数到合约即可。
5. 减少token总量
有增就有减,erc20 token也可以通过烧毁(burn)实现总量的减少。
contract Token{
//事件,用来通知token被消耗(这里就不是转移, 是token用了就没了)
event Burn(address indexed fromAddr, uint256 value);
}
contract TokenDemo is Token {
//正如其名, 这个是烧币的.. ,用于把自己的 token 烧掉
function burn(uint256 _value) public returns (bool success) {
require(msg.sender == minter); //合约创建者才有该功能
require(balances[msg.sender] >= _value); //必须要有这么多
balances[msg.sender] -= _value;
totalSupply -= _value;
Burn(msg.sender, _value);
return true;
}
//这个是用户被授权后销毁token.....
function burnFrom(address _from, uint256 _value) public returns (bool success) {
require(msg.sender == minter); //合约创建者才有该功能
require(balances[_from] >= _value); //一样要有这么多
require(_value <= allowed[_from][msg.sender]); //授权后才能操作
balances[_from] -= _value;
allowed[_from][msg.sender] -= _value;
totalSupply -= _value;
Burn(_from, _value);
return true;
}
}
代码写了说明,就不用解释了。直接添加事件和函数到合约即可。
6.修改合约管理地址
有些情况下,需要转移合约管理地址的权限。
function changeAdmin(address _newAdmin) public returns (bool) {
require(msg.sender == minter);
require(_newAdmin != address(0));
minter = _newAdmin;
return true;
//如果需要转移资产,下面两行enable
//balances[_newAdmin] = balances[_newAdmin].add(balances[minter]);
//balances[admin] = 0;
}
7. 锁定某个地址的token
此功能实现指定地址所有该合约token的锁定。既可以锁定,也可以解锁。
contract TokenDemo is Token {
mapping(address => bool) freezed;
function transfer(address _to, uint256 _value) public returns (bool success) {
require(!freezed[msg.sender]); //交易需要判断地址是否被锁定
require(balances[msg.sender] >= _value && balances[_to] + _value > balances[_to]);
require(_to != 0x0);
balances[msg.sender] -= _value; //从消息发送者账户中减去token数量_value
balances[_to] += _value; //往接收账户增加token数量_value
Transfer(msg.sender, _to, _value); //触发转币交易事件
return true;
}
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
require(!freezed[_from]); //交易需要判断地址是否被锁定
require(balances[_from] >= _value && allowed[_from][msg.sender] >= _value);
balances[_to] += _value; //接收账户增加token数量_value
balances[_from] -= _value; //支出账户_from减去token数量_value
allowed[_from][msg.sender] -= _value;//消息发送者可以从账户_from中转出的数量减少_value
Transfer(_from, _to, _value); //触发转币交易事件
return true;
}
//冻结账户
function freezenAccount(address _freezen) public returns (bool success) {
require(msg.sender == minter); //合约创建者才能冻结账户
require(_freezen != minter); //不能冻结合约创建者账户
freezed[_freezen] = true;
return true;
}
//解冻被冻结的账户
function unFreezenAccount(address _freezen) public returns (bool success) {
require(msg.sender == minter); //合约创建者才能解冻账户
freezed[_freezen] = false;
return true;
}
//查询账户冻结状态
function getFrozenAccount(address _target) public view returns (bool) {
require(_target != address(0));
return freezed[_target];
}
}
8. 销毁合约
function kill() public {
require(msg.sender == minter);
selfdestruct(minter);
}
在其他一些比较老的教程里面,你可能会看到suicide()方法,但是为了语言更好的可读性,这个方法目前已经重新命名,以后如有需要,大家直接调用selfdestruct()方法就好。
如果合约写了payable(见第2节),且接收了mc,执行销毁合约后,合约的mc会自动转到minter账号。
合约销毁之后转给该合约地址的mc,就不能再转移出来了。
9.判断地址是合约地址还是普通账号地址
既然给合约地址发送mc,依赖于合约是否有接收mc功能,说明普通的发送mc操作,可能成功,也可能失败。
那怎么判断一个地址是合约地址还是普通账号地址呢?
其实是根据该地址是否包含代码来判断的。以下为accountOrContractAddr.js,填入地址,直接node运行,可以得到结果。
var Chain3 = require('chain3');
var chain3 = new Chain3(new Chain3.providers.HttpProvider('http://localhost:8545'));
//getCode()方法返回指定地址上代码的16进制字符串,由于普通账户地址处没有代码,因此将仅返回16进制前缀0x。
var code = chain3.mc.getCode("0xbe1b5b49fc5cf4300d0ee8699089XXXXXXXXXXXX");
if(code == '0x')
console.log('普通账户');
else
console.log('合约账户');