官宣:https://www.ethereum.org/token
核心逻辑演进按照官网的示例,个人的工作就是从我自己的视角讲清楚这个逻辑。图文仅作为参考。
首先我们需要知道,标准的发币合约是比较复杂的,我们这里先从最简单的开始入手。
pragma solidity ^0.4.20;
contract MyToken {
/* 一个类似字典的结构,链上存储--存储的是所有账户的余额,key是地址,value是余额 */
mapping (address => uint256) public balanceOf;
/*初始化:初始发行总量全归创建者所有*/
function MyToken(
uint256 initialSupply
) public {
balanceOf[msg.sender] = initialSupply; // Give the creator all initial tokens
}
/* 发送代币的函数 */
function transfer(address _to, uint256 _value) public returns (bool success) {
require(balanceOf[msg.sender] >= _value); // Check if the sender has enough
require(balanceOf[_to] + _value >= balanceOf[_to]); // Check for overflows
balanceOf[msg.sender] -= _value; // Subtract from the sender
balanceOf[_to] += _value; // Add the same to the recipient
return true;
}
}
contract MyToken {
/* This creates an array with all balances */
mapping (address => uint256) public balanceOf;
}
mapping
数据结构是个字典,存储键值到值的映射,这里是从地址到余额的映射。
如果就把上面这个合约部署到链上,啥用都没有。所以我们接着增加一些功能进来:
contract MyToken {
mapping (address => uint256) public balanceOf;
function MyToken() {
balanceOf[msg.sender] = 21000000; // 比特币的总量~
}
}
显式增加一个构造函数,加上一行代码,往msg.sender
这个账户里新增2100万个ETH。
这里的数字没有其他特别含义,可以任意指定,而更好的方式则是,把它作为变量,将来可以改动。
contract MyToken {
mapping (address => uint256) public balanceOf;
function MyToke(uint initialSupply) {
balanceOf[msg.sender] = initialSupply;
}
}
至此,我们创建的智能合约,就一个构造函数,创建者地址里多出来初始发行量的代币了,但是还没有转移代币的功能。时刻记住,智能合约编译后放在链上自动运行,像个智能体一样,功能是我们定义好的,暴露出来,交给事件自动触发执行。
现在我们为它加上转移代币的功能:
contract MyToken() {
mapping(address => uint256) public balanceOf;
function MyToken(uint256 initialSupply) {
balanceOf[msg.sender] = initialSupply;
}
function transfer(address _to, uint256 _value) public {
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
}
}
这里添加的是直接的转账逻辑,从发送者msg.sender
账户里减去_value
数量的代币,并将其加到_to
账户地址里。
这是有问题的,问题在哪里呢?发送者钱够不够转呢?接收者收到代币后会不会值溢出?
在transfer
函数里,添加一行:
require(balanceOf[msg.sender] >= _value && balanceOf[_to] + _value >= balanceOf[_to] )
上面这个简单合约里还没有代币的基本信息,先设定好保存基本信息的容器:
string public name;
string public symbol;
uint8 public decimals;
这些信息在构造器函数中使用:
function MyToken(uint256 initialSupply, string tokenSymbol, uint8 decimalUnits) {
require(balanceOf[msg.sender] >= _value && balanceOf[_to] + _value >= balanceOf[_to] )
balanceOf[msg.sender] = initialSupply;
name = tokenSymbol;
decimals = decimalUnits;
}
现在是时候创建一些事件了。
所谓事件呢,就是一些特殊的空函数,我们可以调用这些函数来帮助以太坊客户端,比如以太坊钱包来跟踪合约上发生的活动。事件需要以大写字母开头。
事件的使用分成两个步骤:
event
关键字emit
关键字声明事件的方式
event Transfer(address indexed from, address indexed to, uint256 value);
注意到这里声明时,地址类型后面跟着一个修饰符indexed
,现在我还不知道为啥要这个,但先记着。
使用事件的方式
事件定义完成之后,需要在合适的地方发射出去,让这个世界知道。这里的转账事件需要在transfer
函数后面加上。
function transfer(address _to, uint256 _value) public {
require(balanceOf[msg.sender] >= _value && balanceOf[_to] + _value >= balanceOf[_to] )
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
// 发射事件: 告知在监听的所有人,事件发生了
emit Transfer(msg.sender, _to, _value);
}
事件很像是C++里面的函数,先声明后调用。事件本质上是个函数,所以在emit
时,也是按照调用函数的逻辑来运行。
基础篇到此为止,具体如何部署,由于本人没有用基于Mist的客户端跑通,而基于Remix
的之前写过,有些地方不太一样,大体还是可用的:https://bihu.com/article/16183
下一篇讲进阶。
END.