一、前言部分
最近研究了许多CVE文章,发现其内容均涉及到某些团队或者个人搭建的私人以太坊代币合约。而在研读他们合约详细内容的时候,我发现巨大多数代币系统均是在ERC20的基础上搭建而成的。所以能够对ERC20的内容有深刻的理解能够帮助我们更好的去研究代币的安全性。在本文中,我们就要对ERC20的关键代码进行详细的个人解读,之后我们会针对部分函数结合区块链的特性进行安全解读。
二、ERC20分析
1 ERC-20概念
在详细分析ERC20代码之前,我们先向新人普及一下什么是ERC20 。
在2015年11月份,ERC20标准被区块链行业内推出。写过代码的同志都了解,程序一旦写完,它们就会按照既定的规则执行,在没有外界干扰的情况下,代码执行多少次都会得到同样的结果。而在一个团队中,每个人在进行工作之前都会被Boss拉过去提前召开一些会议。这些会议的目的就是制定某些标准,令所有参与工作的员工能够按照一定规则进行工作。以达到忙中有序的目的。
接触过数字货币的人都应该知道,以太坊是一个分布式的智能合约平台,可以分发代币(Token)。而ERC-20就是这些代币的统一规则,表现出一种通用的和可预测的方式。
简单地说,任何 ERC-20 代币都能立即兼容以太坊钱包,由于交易所已经知道这些代币是如何操作的,它们可以很容易地整合这些代币。这就意味着,在很多情况下,这些代币都是可以立即进行交易的。
2 ERC-20代码详细分析
下面,我们对ERC20的代码进行分析。而我们知道,对于一个代币来说,其本质就类似于货币的概念。对于货币来说,我们需要它有价值,能进行转账,能被用作交换等等。而对于一个大型的货币规则市场来说,我们还需要类似于人类法律等武器,简单来说我需要能够阻止某些不安全账户对我的代币的使用。能够令代币与现实货币进行交换等等。
下面我们放上标准ERC20代币的链接。
本文中我们对openzeppelin-solidity相关的ERC20进行分析。
首先我们来看起始部分:
pragma solidity ^0.4.24;
import "./IERC20.sol";
import "../../math/SafeMath.sol";
开始的时候,合约声明了当前版本以及引入的包。这两个包分别是一个接口包:
这里我们就要讲述一下接口的使用方式了。
对于面向对象的编程语言来说,接口的概念类似于多态的概念。在父类合约中定义一些接口,子类合约在实现其自身代码的时候需要对这些接口中定义的方法进行具体实现。而接口的书写入图所示,开始时的定义为interface
,其内部的函数不需要有函数体。之后,我们能看到合约还定义了两个事件Transfer与Approval
。而事件会在后面的函数执行过程中调用,用于快速打印出这些事件的记录。在我个人看来,事件类似于一个记录本,将当前执行此函数过程中使用到的变量保存并打印出来。
然而在我对接口
进行研究的时候,我也发现了其中存在一些小的问题。我会在下一个部分给读者讲述。
代码如下:
pragma solidity ^0.4.24;
/**
* @title ERC20 interface
* @dev see https://github.com/ethereum/EIPs/issues/20
*/
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address who) external view returns (uint256);
function allowance(address owner, address spender)
external view returns (uint256);
function transfer(address to, uint256 value) external returns (bool);
function approve(address spender, uint256 value)
external returns (bool);
function transferFrom(address from, address to, uint256 value)
external returns (bool);
event Transfer(
address indexed from,
address indexed to,
uint256 value
);
event Approval(
address indexed owner,
address indexed spender,
uint256 value
);
}
让我们继续回到ERC20.sol
中来。在对接口进行了查看后,我们大致能够猜测这些接口属于一种规范化的提醒。它为ERC20在实现各种函数的时候指明了一些方向。哪些函数是必须要有的,需要传入哪些参数等等。
之后ERC20中还引用了import "../../math/SafeMath.sol";
。
对于我们研究安全的同学来说,这个包是十分经典的。在我们前期对整数溢出问题的分析中,我们知道这些整数就是因为不正确的使用了+ - * /
才导致的安全事件发生。所以现在开发人员在对以太坊开发的时候一般都会使用saveMath来进行安全计算。
pragma solidity ^0.4.24;
/**
* @title SafeMath
* @dev Math operations with safety checks that revert on error
*/
library SafeMath {
/**
* @dev Multiplies two numbers, reverts on overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b);
return c;
}
/**
* @dev Integer division of two numbers truncating the quotient, reverts on division by zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0); // Solidity only automatically asserts when dividing by 0
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend).
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a);
uint256 c = a - b;
return c;
}
/**
* @dev Adds two numbers, reverts on overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a);
return c;