通过发币学习以太坊智能合约

点进来我就把你当成一个会写代码,玩过 ICO 的人,ICO 通过发币融资的方式分分钟走向人生巅峰,迎娶白富美,那么到底是如何发币,发币难不难,我们只知道炒币,不知道发币,那你怎么敢买的?

一个简单的 token 合约包括:代码部署,转账,上传代码到etherscan并验证,代币管理,代币增发,冻结,销毁,到我们熟知的 ICO。

开发环境,在线IDE

remix

首先来看一下最简单并且可以转账的代币长什么样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
pragma solidity ^0.4.24;

contract SimpleToken {
mapping(address => uint256) public balanceOf;

constructor (uint256 init) public{
balanceOf[msg.sender] = init;
}

function transfer(address _to, uint256 _value) public{
//防溢出
require(balanceOf[msg.sender] >= _value);
require(balanceOf[_to] + _value >= balanceOf[_to]);

balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
}
}

那我们去 etherscan 上看一下,正经的代币长什么样子,都包含哪些东西。

BNB 为例,有 name,totalSupply,decimals,balanceOf,owner,symbol,freezeOf,allowance。

其中name,totalSupply,decimals,symbol 是ERC20标准代币规范,每个代币都必须要有的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
contract erc20Interface{
string public name;
string public symbol;
uint8 public decimal;
uint public totalSupply;

function transfer(address _to,uint256 _value) public returns (bool succ);

function transferFrom(address _from,address _to,uint256 _value) public returns (bool succ);

function approve(address _spender,uint256 _value) public returns (bool succ);

function allowance(address _owner,address _spender) public view returns (uint256 remaining);

event Transfer(address indexed _from,address indexed _to,uint256 _value);

event Approve(address indexed _owner,address indexed _spender,uint256 _value);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
pragma solidity ^0.4.24;

import './erc20Interface.sol';

contract ERC20 is erc20Interface{

mapping(address => uint256) balanceOf;

mapping(address => mapping(address => uint256)) allowed;

constructor(string _name,string _symbol,uint8 _dec,uint _total) public{
name = _name;
symbol = _symbol;
decimal = _dec;
totalSupply = _total;
balanceOf[msg.sender] = totalSupply;
}
function transfer(address _to,uint256 _value) public returns (bool succ){
require(_to != address(0));
require(balanceOf[msg.sender] >= _value);
require(balanceOf[_to]+_value > balanceOf[_to]);

balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;

emit Transfer(msg.sender, _to, _value);

return true;
}

function transferFrom(address _from,address _to,uint256 _value) public returns (bool succ){
require(_to != address(0));
require(allowed[_from][msg.sender] >= _value);
require(balanceOf[_from] >= _value);
require(balanceOf[_to]+_value > balanceOf[_to]);

balanceOf[_from] -= _value;
balanceOf[_to] += _value;

allowed[_from][msg.sender] -= _value;
emit Transfer(msg.sender, _to, _value);

return true;
}

function approve(address _spender,uint256 _value) public returns (bool succ){
allowed[msg.sender][_spender] = _value;
emit Approve(msg.sender,_spender,_value);
return true;
}

function allowance(address _owner,address _spender) public view returns (uint256 remaining){
return allowed[_owner][_spender];
}
}

所有的 erc20 继承erc20Interface接口,代币规范中的几个在创建的时候就要实例化完成,所以我们写一个构造函数constructor声明,从外面传过来。实现接口中的方法,transfer就是一个发送地址做减法,接收地址做加法的过程。require检查各种异常情况,包括溢出,接收地址是不是无效之类的。如果失败,则会被 revert 掉。反之用emit触发事件。

我们在 log 中可以看到,emit 触发的事情返回长这个样子

1
2
3
4
5
6
7
8
9
10
11
12
13
"from": "0x5e72914535f202659083db3a02c984188fa26e9f",
"topic": "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"event": "Transfer",
"args": {
"0": "0x0000000000000000000000000000000000000000",
"1": "0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c",
"2": "1000",
"_from": "0x0000000000000000000000000000000000000000",
"_to": "0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c",
"_value": "1000",
"length": 3
}
}

部署

下面我们将 erc20部署到测试网络,remix 小狐狸插件metamask,将合约代码部署到测试网络。metamask 安装使用方法自行Google。
我们将 remix 右边栏 run 选项中的Environment切换成Injected Web3,每次执行 deploy 的时候都会启动狐狸插件,第一次安装是没有 eth 的,小狐狸会提示你 BUY,去测试水龙头自行购买,速度取决于测试环境 eth网络。购买成功,我们将 gas 调大一点,部署合约速度会更快。平时我们自行测试用JSVM 环境就好了,调试都一样,唯一不一样的就是是否上链。部署合约,触发函数都会消耗 gas。

当我们合约部署成功之后 https://ropsten.etherscan.io/token/你的地址,
这个地址就是 remix 中deploy 时合约名字后面的地址,直接访问可以看到当时你设置的名字之类的,在readContract这个 tab 下面会让你 please Verify Your Contract Source Code here.验证代码,那我们就把代码拷贝进去然后,合约名字,编译环境都要和我们部署的时候一致,不要搞错了,点击验证并发布,不出意外等一会成功,然后我们就看到了我们刚开始 BNB 页面一样的效果了。

增发:

主要两步代币的管理者 Owner,然后修改我们的totalSupply总量。这里我们会用到一个函数修改器modifier,先看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pragma solidity ^0.4.20;

contract Owned {
address public owner;

constructor () public{
owner = msg.sender;
}

modifier onlyOwner{
require(msg.sender == owner);
_;
}
function transferOwnerShip(address newOwner) public onlyOwner {
owner = newOwner;
}
}

上面的代码等价于

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pragma solidity ^0.4.20;

contract Owned {
address public owner;

constructor () public{
owner = msg.sender;
}

function transferOwnerShip(address newOwner) public {
require(msg.sender == owner);
owner = newOwner;
}
}

一下子就看懂了吧。

我们想要的效果就是只有owner才可以执行transferOwnerShip,其他地址用户访问会被拒绝。
owner 就是我们部署合约的主账号。

代币管理者 owner 已实现,下面我们来修改totalSupply。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pragma solidity ^0.4.20;

import "./erc20.sol";

import "./owned.sol";

contract AdvanceToken is ERC20,Owned {

event AddSupply(uint amount);

constructor (string _name,string _symbol,uint8 _dec,uint _total) ERC20(_name,_symbol,_dec,_total) public {

}

function mine(address _target,uint _amount) public onlyOwner{
totalSupply += _amount;
balanceOf[_target] += _amount;

emit AddSupply(_amount);
emit Transfer(0,_target,_amount);
}
}

实现代币冻结

资产在钱包但是不能交易。

1
2
3
4
5
6
7
mapping(address => bool) public frozenAccount;
event FrozenFunds(address _target,bool frozen);
//资产冻结
function freezeAccount(address _target,bool freeze) public onlyOwner{
frozenAccount[_target] = freeze;
emit FrozenFunds(_target,freeze);
}

上面已讲资产冻结但是还可以交易,下面我们对交易函数做一下判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function transfer(address _to,uint256 _value) public returns (bool succ){
succ = _transfer(msg.sender,_to,_value);
}

function transferFrom(address _from,address _to,uint256 _value) public returns (bool succ){
require(allowed[_from][msg.sender] >= _value);
succ = _transfer(_from,_to,_value);
allowed[_from][msg.sender] -= _value;
}

function _transfer(address _from,address _to,uint256 _value) internal returns (bool succ){
require(_to != address(0));
require(!frozenAccount[_from]);

require(balanceOf[_from] >= _value);
require(balanceOf[_to]+_value > balanceOf[_to]);

balanceOf[_from] -= _value;
balanceOf[_to] += _value;


emit Transfer(_from, _to, _value);

return true;
}

代币冻结并不能交易也 OK 了。下面来实现代币销毁。

代币销毁

账户金额减少,总发行量减少,两个功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
event Burn(address _target,uint amount);
//资产销毁
function burn(uint256 _value) public returns (bool success){

require(balanceOf[msg.sender] > _value);

totalSupply -= _value;
balanceOf[msg.sender] -= _value;

emit Burn(msg.sender,_value);

return true;
}

function burnFrom(address _from,uint256 _value) public returns (bool success){
require(balanceOf[_from] >= _value);
require(allowed[_from][msg.sender] >= _value);

totalSupply -= _value;
balanceOf[_from] -= _value;
allowed[_from][msg.sender] -= _value;

emit Burn(_from,_value);

return true;
}

上面我们讲过冻结,他是冻结某个账号所有token。现在市面上很多 ICO 都是token 锁定并分批释放解锁。

分批释放解锁

同样我们可以用 mapping 来保存账号锁定的token,我们在 transfer 的时候对锁定的token进行判断然后转账,我们还要考虑到分批解锁,与时间相关,随着时间的推移,锁定的token会越来越少。

ICO

一 :包括众筹时间,目标,兑换价格,受益人几个参数。
二 :实现代币与以太币的兑换。
三:众筹成功下发 token,失败退款相应 eth

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
pragma solidity ^0.4.20;

import './Owned.sol';

interface token {
function transfer(address _to,uint _amount) external;
}

contract ico is Owned{
uint public fundingGoal;
uint public deadline; //截止日期
uint public price;
uint public fundAmount;
token public tokenReward;
address public beneiciary;//受益人

mapping (address => uint) public balanceOf;

event FundTranfer(address backer,uint amount);
event FundReached(bool sucess);

constructor (uint fundingGoalInEther,uint durationInMinu,uint etherCostofEachToken,address addressOfToken) public{
fundingGoal = fundingGoalInEther * 1 ether;
deadline = now + durationInMinu * 1 minutes;
price = etherCostofEachToken * 1 ether; //1eth = 10 ** 18 wei
tokenReward = token(addressOfToken);
beneiciary = msg.sender;
}

function () public payable{

require (now < deadline);

uint amount = msg.value; //wei
balanceOf[msg.sender] += amount;
fundAmount += amount;
uint tokenAmount = 0;
//实现空投
if (amount == 0){
tokenAmount = 10;
}else{
tokenAmount = amount / price;
}

tokenReward.transfer(msg.sender,tokenAmount);

emit FundTranfer(msg.sender,amount);
}

//只有 owner 才可以设置阶梯价格
function setPrice(uint etherCostofEachToken) public onlyOwner{
price = etherCostofEachToken * 1 ether; //1eth = 10 ** 18 wei
}

//检查众筹是否结束
modifier afterDeadLine{
require(now>=deadline);
_;
}

function checkFundReachd() public afterDeadLine{
if(fundAmount > fundingGoal){
emit FundReached(true);
}
}

function withdrawal() public afterDeadLine{
if(fundAmount > fundingGoal){
//众筹成功
if(beneiciary == msg.sender){
beneiciary.transfer(fundAmount);
}
}else{
uint amount = balanceOf[msg.sender];
if(amount > 0){
msg.sender.transfer(amount);
msg.sender = 0;
}
}
}
}
坚持原创技术分享,您的支持将鼓励我继续创作!