ERC20 ERC677 代幣標準

pickmea發表於2022-05-10

背景

區塊鏈 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);
	}
}

相關文章