飞行的蜗牛

一个正在觉醒的无名氏修行者,略懂编程,略懂音乐。

ERC20 智能合约

阅读本文你需要先了解以下知识:

  1. solidity 语言,点击这里去看教程
  2. 了解什么是以太坊和智能合约, 不明白的可以看看我的这篇博客 以太坊开发入门指南

什么是代币

简单的说,代币其实就是数字货币,跟主链币不同的是,代币是通过智能合约发出的, 也就是说代币是通过智能合约管理的数字货币

我们先来看看以太坊上发的代币是什么样子的:

今天我们就来详细讲一讲怎样创建一个这样的代币。

ERC20 代币

也许你经常看到ERC20和代币一同出现, ERC20是以太坊定义的一个代币标准。
要求我们在实现代币的时候必须要遵守的协议,如指定代币名称、总量、实现代币交易函数等,只有支持了协议才能被以太坊钱包支持。

其接口如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
contract ERC20Interface {

string public constant name = "Token Name";
string public constant symbol = "SYM";
uint8 public constant decimals = 18; // 18 is the most common number of decimal places

function totalSupply() public constant returns (uint);
function balanceOf(address tokenOwner) public constant returns (uint balance);
function allowance(address tokenOwner, address spender) public constant returns (uint remaining);
function transfer(address to, uint tokens) public returns (bool success);
function approve(address spender, uint tokens) public returns (bool success);
function transferFrom(address from, address to, uint tokens) public returns (bool success);

event Transfer(address indexed from, address indexed to, uint tokens);
event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}

简单说明一下:

  • name : 代币名称;
  • symbol: 代币符号;
  • decimals: 代币小数点位数,代币的最小单位, 18表示我们可以拥有 .0000000000000000001单位个代币;
  • totalSupply() : 发行代币总量;
  • balanceOf(): 查看对应账号的代币余额;
  • transfer(): 实现代币交易,用于给用户发送代币(从我们的账户里);
  • transferFrom(): 实现代币用户之间的交易;
  • allowance(): 控制代币的交易,如可交易账号及资产;
  • approve(): 允许用户可花费的代币数;

编写代币合约代码

我这里就直接使用以太官网的 token 合约模板, 加上了一些注释。想看官方原版模板的请移步这里:

https://ethereum.org/token

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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
pragma solidity ^0.4.16;


interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) public; }

//noinspection ALL contract TokenERC20 {
string public name;
string public symbol;
uint8 public decimals = 18; // decimals 可以有的小数点个数,最小的代币单位。18 是建议的默认值
uint256 public totalSupply;

// 用mapping保存每个地址对应的余额
mapping (address => uint256) public balanceOf;
// 存储对账号的控制
mapping (address => mapping (address => uint256)) public allowance;

// 事件,用来通知客户端交易发生
event Transfer(address indexed from, address indexed to, uint256 value);

// 事件,用来通知客户端代币被消费
event Burn(address indexed from, uint256 value);

/**
* 初始化构造
*/
function TokenERC20(uint256 initialSupply, string tokenName, string tokenSymbol) public {

totalSupply = initialSupply * 10 ** uint256(decimals); // 供应的份额,份额跟最小的代币单位有关,份额 = 币数 * 10 ** decimals。
balanceOf[msg.sender] = totalSupply; // 创建者拥有所有的代币
name = tokenName; // 代币名称
symbol = tokenSymbol; // 代币符号
}

/**
* 代币交易转移的内部实现
*/
function _transfer(address _from, address _to, uint _value) internal {
// 确保目标地址不为0x0,因为0x0地址代表销毁
require(_to != 0x0);
// 检查发送者余额
require(balanceOf[_from] >= _value);
// 溢出检查
require(balanceOf[_to] + _value > balanceOf[_to]);

// 以下用来检查交易,
uint previousBalances = balanceOf[_from] + balanceOf[_to];
// Subtract from the sender
balanceOf[_from] -= _value;
// Add the same to the recipient
balanceOf[_to] += _value;
Transfer(_from, _to, _value);

// 用assert来检查代码逻辑。
assert(balanceOf[_from] + balanceOf[_to] == previousBalances);
}

/**
* 代币交易转移
* 从自己(创建交易者)账号发送`_value`个代币到 `_to`账号
*
* @param _to 接收者地址
* @param _value 转移数额
*/
function transfer(address _to, uint256 _value) public {
_transfer(msg.sender, _to, _value);
}

/**
* 账号之间代币交易转移
* @param _from 发送者地址
* @param _to 接收者地址
* @param _value 转移数额
*/
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
require(_value <= allowance[_from][msg.sender]); // Check allowance
allowance[_from][msg.sender] -= _value;
_transfer(_from, _to, _value);
return true;
}

/**
* 设置某个地址(合约)可以创建交易者名义花费的代币数。
*
* 允许发送者`_spender` 花费不多于 `_value` 个代币
*
* @param _spender The address authorized to spend
* @param _value the max amount they can spend
*/
function approve(address _spender, uint256 _value) public
returns (bool success) {
allowance[msg.sender][_spender] = _value;
return true;
}

/**
* 设置允许一个地址(合约)以我(创建交易者)的名义可最多花费的代币数。
*
* @param _spender 被授权的地址(合约)
* @param _value 最大可花费代币数
* @param _extraData 发送给合约的附加数据
*/
function approveAndCall(address _spender, uint256 _value, bytes _extraData)
public
returns (bool success) {
tokenRecipient spender = tokenRecipient(_spender);
if (approve(_spender, _value)) {
// 通知合约
spender.receiveApproval(msg.sender, _value, this, _extraData);
return true;
}
}

/**
* 销毁我(创建交易者)账户中指定个代币
*/
function burn(uint256 _value) public returns (bool success) {
require(balanceOf[msg.sender] >= _value); // Check if the sender has enough
balanceOf[msg.sender] -= _value; // Subtract from the sender
totalSupply -= _value; // Updates totalSupply
Burn(msg.sender, _value);
return true;
}

/**
* 销毁用户账户中指定个代币
*
* Remove `_value` tokens from the system irreversibly on behalf of `_from`.
*
* @param _from the address of the sender
* @param _value the amount of money to burn
*/
function burnFrom(address _from, uint256 _value) public returns (bool success) {
require(balanceOf[_from] >= _value); // Check if the targeted balance is enough
require(_value <= allowance[_from][msg.sender]); // Check allowance
balanceOf[_from] -= _value; // Subtract from the targeted balance
allowance[_from][msg.sender] -= _value; // Subtract from the sender's allowance
totalSupply -= _value; // Update totalSupply
Burn(_from, _value);
return true;
}
}

部署代币合约

部署代币合约有两种方式,一种是使用客户端的钱包,一种是使用 Remix + MetaMask 钱包,我们今天采用后者, 如果你还没有安装 MetaMask 钱包。请先到
Chrome 浏览器插件中心去安装, 点击 这里 直接去到
钱包安装页面。安装完成之后选择 Ropsten Test Net 测试网络。

然后点击 “Buy” 按钮去免费获取 ETH

然后拷贝合约代码到 Remix,选择测试网络和发币账号,传入参数之后点击 “create”

点击创建之后,MetaMask 钱包会弹出一个让你确认的弹框, 你点击 SUBMIT (确认发送交易)

确认发送交易之后,控制台会有输出,表示你发送了一个交易,右边也出现一条待确认的交易

合约发布成之后会返回合约的 API

这里需要提一下的是,测试网络一般返回的快一些,一般一分钟之内会返回创建结果。但是如果你是在主网发布合约的话,堵塞个半天都有可能。

合约创建之后,点击右边的按钮复制和合约地址,然后打开你的 MetaMask 钱包,选择 TOKENS 菜单,添加 TOKEN

在弹出的页面输入刚刚复制的合约地址,然后它会自动加载到你代币的标志(Symbol), 点击添加就 OK 了

再返回到 MetaMask 的 TOKENS 栏,发现已经有代币了。其他两个是我之前测试的时候发的。

最后贴出一张开头的那张图,只不过这个代币是我们自己发的。

需要说明的是,由于是刚刚创建,所以没有任何代币发送记录,接下来你就可以拿着这代币去 I(割)C(韭)O(菜) 了。

以太坊众筹合约

阅读本文你需要先了解以下知识:

  1. solidity 语言,点击这里去看 solidtiy 中文教程
  2. 了解什么是以太坊和智能合约, 不明白的可以看看我的这篇博客 以太坊开发入门指南
  3. 如果还不知到怎么发自己的代币的同学请移步这里 发行自己的 ERC20 标准代币

什么是代币众筹

简单的说,代币其实就是数字货币,跟主链币不同的是,代币是通过智能合约发出的。
在众筹之前,你得先有个代币,也就是 token. 简单来说,代币其实就是你参与一个项目的凭证。
举个栗子,我想发起一个项目,需要筹集 100w 的资金,那如何证明你给了我投资了呢,很简单,你给我投入多少资金,我返还给你一定比例的代币,等我项目
成功上线之后你就可以凭借代币来分享项目分红,你拥有的代币数量越多,分红也就越多。
传统的众筹不仅操作手续复杂,而且在参与之后通常不容易交易(参与之后无法转给其他人),而通过用代币来参与众筹,
则很容易进行交易,众筹的参与人可随时进行买卖,待众筹项目实施完成的时候,完全根据代币持有量进行回馈。

众筹智能合约代码

首先导入一些数字运算安全库,这样在进行 uint 的加减乘除的时候能够避免溢出而给黑客留下攻击的漏洞

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
library SafeMath {

/* 加法 */
function add(uint a, uint b) internal pure returns (uint) {
uint256 c = a + b;
assert(c >= a);
return c;
}

/* 减法 */
function sub(uint a, uint b) internal pure returns (uint) {
assert(b <= a);
return a - b;
}

/* 乘法 */
function mul(uint a, uint b) internal pure returns (uint) {
if (a == 0) {
return 0;
}
uint c = a * b;
assert(c / a == b);
return c;
}

/* 除法 */
function div(uint a, uint b) internal pure returns (uint) {
uint c = a / b;
return c;
}

}

然后在声明 Token 的接口

1
2
3
4
5
// 声明 token 合约接口
interface token {
function transfer(address receiver, uint amount) external;
function decimals() external returns(uint);
}

这里我们声明了两个方法:

  • transfer(address, amount), 代币转账方法
  • decimals(), 获取代币的精确位数

最后加上众筹的合约代码

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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
/**
* crowd funds contract
*/
contract CrowdFunds {

/* 导入安全运算库 */
using SafeMath for uint;

// 受益人地址
address public beneficiary;
// 众筹的目标金额,单位为 1 ehter
uint public targetFunds;
// 募资截止日期
uint public deadline;
// 代币价格,单位为 1 ether
uint public price;
// 预售代币合约地址
token public tokenReward;
// 是否完成众筹目标
bool public reachedGoal = false;
// 众筹是否结束
bool public isCrowdClosed = false;
// 已经募集的资金数量
uint public fundsRaisedTotal = 0;
// 记录每个投资者贡献了多少资金,单(wei)
mapping(address => uint) public balanceOf;

/**
* 事件可以用来跟踪信息
**/
event GoalReached(address recipient, uint totalAmountRaised);
event FundTransfer(address investor, uint amount, bool isContribution);
event InvestorWithdraw(address investor, uint amount, bool success); // 投资人提现事件
event BeneficiaryWithdraw(address beneficiary, uint amount, bool success); // 受益人提现事件



modifier afterDeadline {
require(now > deadline);
_;
}

modifier beforeDeadline {
require(now <= deadline);
_;
}

constructor(
address _beneficiary,
uint _targetFunds,
uint _duration,
uint _price,
address _tokenAddress
) public {

beneficiary = _beneficiary;
targetFunds = _targetFunds * 1 ether;
deadline = now + _duration * 1 minutes;
price = _price * 1 finney;
tokenReward = token(_tokenAddress);
}

/**
* 无函数名的Fallback函数,这里必须在众筹截止日期之前充值才有效
* 在向合约转账时,这个函数会被调用
*/
function () payable beforeDeadline public {
require(!isCrowdClosed); // 判断众筹是否结束
require(!reachedGoal); // 判断众筹是否达标

// 计算购买的 token 数量
uint amount = msg.value;
// 这里需要注意要乘以 token 的 decimals, 否则会发现众筹得到的代币数量不对
uint tokenDecimal = tokenReward.decimals();
uint tokenNum = (amount / price) * 10 ** tokenDecimal;
balanceOf[msg.sender] = balanceOf[msg.sender].add(amount);
fundsRaisedTotal = fundsRaisedTotal.add(amount);

// 众筹达标
if (fundsRaisedTotal >= targetFunds) {
reachedGoal = true;
isCrowdClosed = true; //关闭众筹
// 发送事件,记录日志
emit GoalReached(beneficiary, fundsRaisedTotal);
}
// 发送代币
tokenReward.transfer(msg.sender, tokenNum);
emit FundTransfer(msg.sender, amount, true);
}


/**
* 提现
*/
function withdraw() afterDeadline public {

//众筹没有成功,则把投资人款项退还
if (!reachedGoal) {
uint amout = balanceOf[msg.sender];

if (amout > 0 && msg.sender.send(balanceOf[msg.sender])) {
balanceOf[msg.sender] = 0;
emit InvestorWithdraw(msg.sender, amout, true);
// 发送事件, 记录日志
} else {
balanceOf[msg.sender] = amout;
emit InvestorWithdraw(msg.sender, amout, false);
}
} else { //众筹成功 ,受益人把钱提走

if (msg.sender == beneficiary && beneficiary.send(fundsRaisedTotal)) {
emit BeneficiaryWithdraw(beneficiary, fundsRaisedTotal, true);
} else {
emit BeneficiaryWithdraw(msg.sender, fundsRaisedTotal, false);
}

}

}

}

合约代码比较简单,而且我又都加了注释,我这里就不多做解释了,但是有个地方需要额外注意一下,就是在 Fallback 函数里面计算投资人兑换代币数量的时候要注意
单位换算:

1
2
3
4
5
6
7
// 计算购买的 token 数量
uint amount = msg.value;
// 这里需要注意要乘以 token 的 decimals, 否则会发现众筹得到的代币数量不对
uint tokenDecimal = tokenReward.decimals();
uint tokenNum = (amount / price) * 10 ** tokenDecimal;
balanceOf[msg.sender] = balanceOf[msg.sender].add(amount);
fundsRaisedTotal = fundsRaisedTotal.add(amount);

这里需要乘以你的代币的精确度,否则你就会发现你命名是购买了 1000 个 token, 到最后发现是 0.0000000000000001 个,所以如果你的代币有精度的话,这里计算代币
数量的时候就需要加上精度。

写好合约代码之后我们就可以开始测试了。

由于线上的测试环境比较卡,所以我就使用 ganache 在本地启动了一个模拟环境,里面有10个账号,每个账号有 100 ETH, 足够测试了。

先用第一个账户创建一个代币合约,发行一个代币,代币名称 PPB, 这一步略过,不知道怎么操作的同学请移步这里 发行自己的 ERC20 标准代币

第二步,发布众筹合约,需要传入 5 个参数

  • address _beneficiary, 受益人,我们这里设置为第一账号,也就是发布众筹合约的人, 0x2eabca3c9ee38a3896cba2e2e74e613f80332194
  • uint _targetFunds, 募资金额, 我们这里设置为 3
  • uint _duration, 募资周期,由于是本地环境,我们设置为 10 分钟就够了
  • uint _price, 代币价格,我们这里默认 1 EHT = 1000 PPB
  • address _tokenAddress 代币合约地址,把我们刚刚创建的代币合约地址传入就可以了

本人使用的参数为: “0x2eabca3c9ee38a3896cba2e2e74e613f80332194”,3,10,1,”0x13e59dbcc6c962db73ee4dcf1ec41da1a7c1752b”, 仅供参考,不过地址要替换成你的。

给合约充入代币

下一步,需要给众筹合约充入代币,否则调用 token.transfer() 转移代币的方法就会出错,因为此时合约拥有的代币数为0, 就没法转移代币给投资人.

这里我们使用 https://www.myetherwallet.com 这个以太坊官方的轻钱包来进行充值操作,myetherwallet 默认是连接主网的,所以首先你需要添加一个本地网络,让
它连接我们 ganache 提供的私有网络 127.0.0.1:8545

点击右上角的网络选项,选择下面的 add custom net

然后在接下来的弹框中填入本地网络的参数信息

添加完网络之后,导入私钥解锁账户,在 ganache 复制第一账户(发币账户)的私钥

在 myetherwallet 网页上选择发送代币,然后选择下面的通过私钥解锁账户

解锁账户之后发送代币,复制众筹合约的地址,转入 3000 代币

如果发现没发选择你的代币,或者看不到你的代币,先在右边添加

创建完之后,在转账那里你就能选择你的代币了,接下来我们需要众筹,在 remix 调用 fallback 函数,注意需要附加以太币, 因为你要向合约打币,
然后合约就会自动向你发送代币

我们先检查一下看看代币充值是否成功,在 ERC20 这个合约中调用 balanceOf() 方法,传入合约地址

然后我们就可以开始通过向合约地址打入 ETH 来参与众筹了。你可以通过钱包, myetherwallet, 我是直接通过 remix 调用 fallback 函数来实现。先切换账号,因为不能
自己给自己众筹,我们选择第二个账号

然后调用函数 

返回调用结果

检验众筹是否成功,第一是查询打款人的代币余额,应该是 2000 (如果你打了2个 ETH), 第二个是看看众筹的资金是否有增加

也可以通过 myetherwallet 查看(这里采用同样的方法,先到 ganache 复制私钥)

接下来就等众筹结束之后,受益人提款了。受益人直接调用合约的 withdraw() 方法就可以提走众筹募集的资金了

至此,整个流程就跑完了,多看无益,赶紧去自己实操一下把.

vscode 常用插件配置

vscode 是微软开发一款非常好用有轻量级的代码编辑器,强大的插件功能可以使它支持各种语言,从 C, C++ 到 java, php, 当然大部分同学认知它是因为它是最好前端编码
工具之一。下面记录一下我常用的一些插件,备忘。

如何在以太坊上验证你的智能合约代码

我们知道一般来说你如果你要用你在以太坊上发布的 ERC20 代币进行众筹, 或者需要发布一款 DApp 游戏,你就必须开源你的合约代码。

所谓开源合约代码就是把你的合约代码在以太坊官网 https://etherscan.io 进行合约验证(Verify Contract Code).

至于为什么要开源,是因为要想有足够多的人参与你的这个项目, 你就必须向公众证明以下两点:

1、所有游戏规则都是公开透明的,童嫂无欺

2、我们不是来割韭菜的

谈谈 EOS 的钱包,账户和权限

本文主要介绍 EOS 的钱包,公私钥,账户之间的关系. 以及深入剖析一下 EOS 的账户权限模型。

在介绍 EOS 账户钱包和账户体系之前,我们先来看看 BTC 和 ETH 的账户体系。

比特币的钱包最为简单,它使用的是 Address + privateKey + UTXO 模型

  • Address: 钱包地址,由公钥生成,也是钱包的唯一标志,用来对外收款
  • privateKey: 钱包私钥,非常重要,拥有私钥就等于拥有了整个钱包的控制权,用户发起交易都需要用私钥签名才能生效。
  • UTXO: 未花费的交易输出,在比特币系统中,除了挖矿交易之外,每笔交易都需要有交易输入(来源)和输出(去向),你可以理解 UTXO 在比特币系统中扮演的角色相当
    于银行账户的余额。关于 UTXO 的更多信息,请阅读我的另一篇博客 比特币中的 UTXO 和智能合约

和比特币的钱包的 UTXO 不同的是,以太坊的钱包是基于账户体系的,这看起来更像我们传统的账户体系,有账户和余额的概念。当然以太坊的钱包中同样有
Address(钱包地址) 以及私钥。值得一提的是,以太坊的账户分为两种,一种是个人账户,一种是合约账户。个人账户又拥有私钥的个人控制,包括转账,发布合约,
调用合约,而合约账户由合约代码控制,你需要通过调用合约代码来对合约账户进行操作,比如向合约充值,将合约资产转出等。

下面我们看看 EOSIO 中对于钱包,账户,公钥私钥之间的关系是怎么定义的:

客户端: 也可以叫节点,在任何一台 PC 机上运行 nodeos 程序就自动运行了一个节点,每个节点可以创建多个钱包

通过下面脚本创建钱包

1
cleos wallet creat -n {wallet_name}

秘钥:分为公钥和私钥,其中私钥用来签名,公钥用来创建账户,一个公钥可以创建多个账户。

1
2
3
4
cleos create key

Private key: 5KUvPZsZHPjvKaakkrHMR36xNXsErPmM1h1nD3NwjCAaCfNDxQj
Public key: EOS5VUHtJb2PTpgvxEhjJ9r6pvgHfFuKeDNPHiwjDu9ZBsgCYnYM8

这里需要注意的是 EOS 的公钥都是以 “EOS” 开头

钱包: 钱包用来存储账户的私钥,一个钱包可以导入多个私钥,同时钱包自身还有一个密码,用来解锁钱包。没有钱包密码,你就无法解锁钱包,也就无法获取私钥
去操作账户。

通过下面脚本导入私钥到钱包

1
cleos wallet import -n {wallet_name} {private_key}

账户:和比特币,以太坊账户体系不同的是,EOS 账户是由两个公钥生成,分别代表 Owner 权限和 Active 权限。这也是 EOS 账户能够实现复杂的权限控制的原因
(这个我们后面会详细讲解 EOS 账户的权限模型)。需要注意的是,EOS 账户和钱包没有从属关系,它们是平行的,各司其职,账户用来转账,发布和调用合约,
而钱包知识用来存储账户的私钥而已。钱包是存储在本地节点的,而账户是存储在区块链上的。

通过下面脚本创建账户

1
cleos create account {creater} {account_name} {key_1} {key_2}

其中 {creater} 是为这个创建动作支付 EOS 的账户,公钥1和公钥2分别是两个不同权限的密钥对的公钥。

综上可知,不管是比特币,以太坊还是 EOSIO,他们在生成钱包和账户之前都必须要先生成一对 key, 只不过他们生成的算法可能个有差异。
所以秘钥是区块链账户体系的核心。

不过大家可能也发现了,EOS 的账户体系明显比 BTC 和 ETH 的账户体系复杂很多。

下面贴上一张 EOS 账户和钱包的关系图

EOS 账户权限控制

下面我们一起来看看 EOS 是如何实现复杂的权限控制模型的。在了解账户权限之前我们先要了解 什么是 EOS 的智能合约。

我们先看看官方对 EOSIO 的智能合约的定义

The combination of Actions and automated action handlers is how EOS.IO defines smart contracts.

由此可见,EOS.IO 的智能合约其实就是,一系列的动作以及对这些动作的自动处理的组合。

从编程的角度上看,动作(Action)就是接口, 处理(Handler) 就是实现(程序) 那其实这跟以太坊的智能合约也是很相似的。所以在 EOS 中其实账户(Account) 也
只是某种特殊的合约而已,也是由 Actions 和 Handlers 组成。

回归到 EOS 账号的权限控制,EOS 是通过以下三步去实现对账户权限的精准和灵活的控制的。

  • 对账户权限分级
  • 对智能合约的Action(动作)分组
  • 将用户权限和智能合约的 Action 之间做映射

1.账户权限的的分级

EOS 把账户(Account)权限分为以下几级

  1. Owner : 最高权限,可以修改其他级别的权限
  2. Active : 合约权限,可以执行合约层面的所有权限
  3. Recovery : 用于恢复账户使用权。
  4. Others: 其他自定义的权限级别。

下面是 EOS 白皮书上对 EOS 账户权限级别的示意图

![](/images/2018/09/permission-group-1.png” style=”max-width:500px;)

你可以在 Active 下面再新建自己的自定义权限分组,比如 FAMILY 和 LAWYER, FAMILY 下面又分了 FRIEND 权限组…

需要注意的是 EOS 权限级别具有从属关系,低等级是高等级的子集,比如说 Active 具有 FAMILY 的所有权限,而 FAMILY 又具有 FRIEND 所有权限, 以此类推。

EOS 采用权限阈值来判断某个操作是否满足权限要求,如下表所示:

对这张图我做个简单的讲解,首先我们可以看到 EOS 为每个权限组都设定了阈值,也就是说阈值,当一个操作(Action)获取的权限(在 EOSIO 系统中表示为签名的个数)
满足了某个权限组(如 Active) 的阈值要求,则表示该操作是被授权成功的。

已上图为例子,比如 Owner 的权限阈值是 2,Owner 权限组下有两个账户 @user1 和 @user2, 他们所占的权限权重分别都是1,以为要想获取 Owner 的授权,必须同时
获得 @user1 和 @user2 的签名。

同样的 Active 的权限阀值是 1, 而 @user1 和 @user2 在 Active 组中所占的权重分别都为 1, 他们两任何一个人都具备单独获取 Active 权限的能力。

对于 Recovery 权限组, 它的权限阀值是 2, @user1, @user2, @user3 所占的权重均为 1, 意味者如果想要恢复账户,必须获得他们中三分之二的人同意才行。

下面还有一个交易权限组 Trade, 同理,如果想要获取交易权限,有两种方法,一种是获取 @user1 的同意(签名),因为它的权重是 3, 而 Trade 的权限阀值也是 3,
刚好满足,而 @user2 和 @user3 的权重都小于三。另一种方法是同时获取 @user2 和 @user3 的同意(签名), 这样 @user2 的权重为1,@user3 的权重为2,
1+2 = 3 也刚好满足 Trade 的权重阀值。

由此可见,EOS 的权限配置可以十分精准和灵活,实现很小的颗粒度控制。

2.智能合约 Action 的分组

同样的,我们还是先来看看 EOS 官方白皮书上的一张图:

![](/images/2018/09/action-group.png” style=”max-width:500px;)

这个图非常容易看懂,就是把合约里面定义的 Actions 分组,比如这个合约总共定义了 4 个动作(Action)

  • 提现操作
  • 买入操作
  • 卖出操作
  • 取消交易操作

我们可以把 买入, 卖出, 取消交易这三个操作划分到一个交易组(Trade Group), 把提现单独分一组,然后把他们映射到不同的权限级别下面。

3.用户权限与智能合约 Action 之间的映射

在对账户 Permision 分级以对及智能合约的 Action 进行分组以后,我们需要把对应的操作(Action)需要什么权限(Permission)给映射出来, 这样才能对账户的权限进行
控制。这里我们还是从 EOS 官方白皮书给出的图来分析

从图上可以看出,合约做了两组权限映射:

  1. 把整个合约的所有 Action(@EXCHANGE.CONTRACT) 都映射到 @user / FAMILY 权限级别下面
  2. 将 @EXCHANGE.contracts/WITHDRAW(提现) 的权限映射到了 @user/LAWYER 下面.

这样 @user/LAWYER 就可以提现,但是不能交易,而 @user/FAMILY 既可以提现也可以交易。

需要注意的是对于某个 Action 的权限的映射判断也是自下而上的,比如我要判断 @user/FAMILY 是否有 @EXCHANGE.CONTRACT/BUY 权限,判断看流程如下:

  1. 查看 @EXCHANGE.CONTRACT/BUY 有没有映射到 @user/FAMILY 或者 @user/FAMILY/FRIEND
  2. 如果 1 返回 false, 则继续查看 @EXCHANGE.CONTRACT.TRADE 组是否有映射到 @user/FAMILY 或者 @user/FAMILY/FRIEND
  3. 如果 2 返回 false,则继续查找 @EXCHANGE.CONTRACT 有没有映射到 @user/FAMILY 或者 @user/FAMILY/FRIEND
  4. 如果还是没有找到,则说明没有权限

至此我们就把 EOS 的账户权限,以及钱包,秘钥,账户之间的关系梳理清楚了,当然这些都是个人理解,如有不同的理解,欢迎不吝赐教,邮件交流。

yangjian102621@gmail.com

搭建主网以太坊全节点钱包

最近公司需要开发以一个基于以太坊的 DApp, 使用 ETH 作为中转介质,需要开发一个简易版的以太坊的钱包组件。考虑到 API 的并发,为了保证 DApp 的稳定运行,
不能使用 Infura 的免费接口,所以还是决定自己搭建钱包节点。本文就是记录了整个节点搭建的过程,供有需要的同学参考。