ERC20 ERC677 代币标准
背景
区块链 1.0 btc
2.0 eth 可编程的时代
代币是以太坊这类通用编程区块链之上最显著和有用的显著形态
- 代币标准(ERC20、ERC721主要标准)是实现的最小化标准
- 使用已存在的标准,可完成合约之前的互操作性
- 成熟度带来的安全性,代币标准经过实战验证
参考资料:
https://eips.ethereum.org/EIPS/eip-20
https://eips.ethereum.org/erc
https://github.com/ethereum/EIPs
ERC20 代币标准
最小单元
6个函数 function
// 获取代币发行总量
function totalSupply() public view returns (uint256)
// 获取_owner用户代币余额数量
function balanceOf(address _owner) public view returns (uint256 balance)
// _owner给_to账户转账_value
function transfer(address _to, uint256 _value) public returns (bool success)
// 通过地址给_to账户转账_value (_from账户必须授权给调用者权限去操作)
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)
// 授权_spender提款,最高金额_value
function approve(address _spender, uint256 _value) public returns (bool success)
// 返回 _spender 仍然被允许从 _owner 提取的金额。
function allowance(address _owner, address _spender) public view returns (uint256 remaining)
2个事件 event
需要在对应的函数中状态变更时,对事件进行相关调用
// 转账事件
event Transfer(address indexed _from, address indexed _to, uint256 _value)
// 授权提款事件
event Approval(address indexed _owner, address indexed _spender, uint256 _value)
可选单元
3个函数 function
// name 代币名称
function name() public view returns (string)
// symbol 代币符号
function symbol() public view returns (string)
// decimals 精度
function decimals() public view returns (uint8)
注意
- 合约contract关键字之前需要加上abstract
- 在抽象的函数内,添加virtual关键字
- 继承函数时,需要对继承的函数加上override修饰符
没有函数体的函数被称为抽象函数
合约实例
ERC20.sol
pragma solidity ^0.6.0;
//ERC20标准
abstract contract ERC20 {
function symbol() virtual public view returns (string memory);
//六个函数
function totalSupply() virtual public view returns (uint256 totalSupply);
function balanceOf(address _owner) virtual public view returns (uint256 balance);
function transfer(address _to, uint256 _value) virtual public returns (bool success);
function transferFrom(address _from, address _to, uint256 _value) virtual public returns (bool success);
function approve(address _spender, uint256 _value) virtual public returns (bool success);
function allowance(address _owner, address _spender) virtual public view returns (uint256 remaining);
// 2个事件event
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
}
pkCoin
pragma solidity ^0.6.0;
import "./ERC20.sol";
contract pkCoin is ERC20 {
uint256 _totalSupply;
string public _symbol;
address public _owner;
mapping(address=>uint256) _balances;
mapping(address => mapping(address => uint256) ) _approve;
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
constructor(uint256 totalSupply, string memory symbol) public {
_totalSupply = totalSupply;
_symbol = symbol;
_owner = msg.sender;
}
// 自定义修饰符
modifier onlyOwner(){
require(msg.sender == _owner);
// _代指的是使用函数修改器的函数体
_;
}
function symbol() virtual override public view returns (string memory){
return _symbol;
}
// 初始化空投
function airDrop(address _to, uint256 _value) onlyOwner public{
require(_to != address(0) && _value > 0 );
_balances[_to] = _value;
_totalSupply += _value;
}
// override复写关键字
function totalSupply() override public view returns (uint256 totalSupply) {
totalSupply = _totalSupply;
return totalSupply;
}
function balanceOf(address _owner) override public view returns (uint256 balance){
require(_owner != address(0), "owner should not be empty !");
return _balances[_owner];
}
function transfer(address _to, uint256 _value) override public returns (bool success){
require( _to != address(0), "_to should be not empty!");
require( _balances[msg.sender] >= _value || _value >0, "value should be valid!");
_balances[msg.sender] -= _value;
_balances[_to] += _value;
success = true;
emit Transfer(msg.sender, _to, _value);
return success;
}
function transferFrom(address _from, address _to, uint256 _value) override public returns (bool success){
require(_from != address(0) && _to != address(0));
require(_balances[msg.sender] >= _value);
// require( _approve[_from][msg.sender] >= _value);
_approve[_from][msg.sender] -= _value;
_balances[_to] += _value;
_balances[_from] -= _value;
success = true;
emit Transfer(_from, _to, _value);
return success;
}
function approve(address _spender, uint256 _value) override public returns (bool success){
require(_spender != address(0));
require(_balances[msg.sender] >= _value && _value >0 );
_approve[msg.sender][_spender] += _value;
_balances[msg.sender] -= _value;
success = true;
emit Approval(msg.sender, _spender, _value);
return success;
}
function allowance(address _owner, address _spender) override public view returns (uint256 remaining) {
remaining = _approve[_owner][_spender];
return remaining;
}
}
ERC677 代币标准
前述
ERC677 标准是 ERC20 的一个扩展,它继承了 ERC20 的所有方法和事件。
差异
transferAndCall
增加了一个transferAndCall 方法
function transferAndCall(address receiver, uint amount, bytes data) returns (bool success)
这个方法比 ERC20 中的 transfer 方法多了一个 data 字段,这个字段用于在转账的同时,携带用户自定义的数据。在方法调用的时候,还会触发Transfer(address,address,uint256,bytes) 事件,记录下发送方、接收方、转账金额以及附带数据。
onTokenTransfer
完成转账和记录日志之后,代币合约还会调用接收合约的onTokenTransfer方法,用来触发接收合约的逻辑。这就要就接收ERC677代币的合约必须实现onTokenTransfer方法,用来给代币合约调用
function onTokenTransfer(address from, uint256 amount, bytes data) returns (bool success)
接收合约就可以在这个方法中定义自己的业务逻辑,可以在发生转账的时候自动触发。换句话说,智能合约中的业务逻辑,可以通过代币转账的方式来触发自动运行。这就给了智能合约的应用场景有了很大的想象空间。
参考:
LINK 代币合约 https://etherscan.io/address/0x514910771af9ca656af840dff83e8264ecf986ca#code
https://github.com/ethereum/EIPs/issues/677
漏洞案例-整数溢出
溢出原理
类型
- 乘法溢出
- 加法溢出
- 减法溢出
demo
- 缺陷代码
pragma solidity ^0.6.0;
contract overFlow{
//加法溢出
//如果uint256 类型的变量达到了它的最大值(2**256 - 1),如果在加上一个大于0的值便会变成0
function add_overflow() public view returns (uint256 _overflow) {
uint256 max = 2**256 - 1;
return max + 1;
}
//减法溢出
//如果uint256 类型的变量达到了它的最小值(0),如果在减去一个小于0的值便会变成2**256-1(uin256类型的最大值)
function sub_underflow() public view returns (uint256 _underflow) {
uint256 min = 0;
return min - 1;
}
//乘法溢出
//如果uint256 类型的变量超过了它的最大值(2**256 - 1),最后它的值就会回绕变成0
function mul_overflow() public view returns (uint256 _underflow) {
uint256 mul = 2**255;
return mul * 2;
}
}
整数溢出防护
- 修复代码
pragma solidity ^0.6.0;
library SafeMath {
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a * b;
// assert 和 require 区别在于,require 若失败则会返还给用户剩下的 gas, assert 则不会
assert(a == 0 || c / a == b);
return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a / b;
return c;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b <= a);
return a - b;
}
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
assert(c >= a);
return c;
}
}
contract safeOverflow{
using SafeMath for uint256;
//加法溢出
//如果uint256 类型的变量达到了它的最大值(2**256 - 1),如果在加上一个大于0的值便会变成0
function add_overflow() public view returns (uint256 _overflow) {
uint256 max = 2**256 - 1;
return max.add(1);
}
//减法溢出
//如果uint256 类型的变量达到了它的最小值(0),如果在减去一个小于0的值便会变成2**256-1(uin256类型的最大值)
function sub_underflow() public view returns (uint256 _underflow) {
uint256 min = 0;
return min.sub(1);
}
//乘法溢出
//如果uint256 类型的变量超过了它的最大值(2**256 - 1),最后它的值就会回绕变成0
function mul_overflow() public view returns (uint256 _underflow) {
uint256 mul = 2**255;
return mul.mul(2);
}
}
Pickmea,lets do it!