一.实验内容
-
修改ERC20合约代码,创建管理员账户,使得管理员账户可以给任意地址增发任意数量的代币
-
创建管理员账户,使得管理员账户可以销毁任意持有该代币的地址不小于该地址余额的币
-
在ERC20合约里新增字段,记录所有持币地址转出的代币数量总和
二.合约分析
在本次实验中,一个完整的ERC20合约有四个合约构成,接下来我会依次介绍。
1.IERC20
IERC20合约是一个接口合约,由事件,方法组成。方法是需要重写的。
interface IERC20 {
event Approval(
address indexed owner,
address indexed spender,
uint256 amount
);
event Transfer(address indexed from, address indexed to, uint256 amount);
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
function totalSupply() external view returns (uint256);
function balanceOf(address owner) external view returns (uint256);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 value) external returns (bool);
function transfer(address to, uint256 value) external returns (bool);
function transferFrom(
address from,
address to,
uint256 value
) external returns (bool);
}
- 重要方法理解
1)该方法是owner授予spender权益,返回可以使用owner的余额。
function allowance(address owner, address spender) external view returns (uint256);
2)该方法是在第一个方法基础上使用。作用是owner允许spender可以使用自己多少代币。spender调用者可以给其他账户转账或给spender自己转账。
function approve(address spender, uint256 value) external returns (bool);
3)该方法是在前两个方法基础上使用。spender调用者调用该方法将value值的代币从from地址转移到to地址。to地址可以是其他账户地址,也可以是自己。注意:在remix上调用该方法时要切换成spender地址。
function transferFrom( address from, address to, uint256 value) external returns (bool);
2.SafeMath
SafeMath是solidity合约中最常见的一个库,是著名的OpenZeppelin智能合约安全开发库的其中之一,用于安全的算术运算的一个库。
library SafeMath {
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return sub(a, b, "SafeMath: subtraction overflow");
}
function sub(
uint256 a,
uint256 b,
string memory errorMessage
) internal pure returns (uint256) {
require(b <= a, errorMessage);
uint256 c = a - b;
return c;
}
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-contracts/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
return div(a, b, "SafeMath: division by zero");
}
function div(
uint256 a,
uint256 b,
string memory errorMessage
) internal pure returns (uint256) {
// Solidity only automatically asserts when dividing by 0
require(b > 0, errorMessage);
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
return mod(a, b, "SafeMath: modulo by zero");
}
function mod(
uint256 a,
uint256 b,
string memory errorMessage
) internal pure returns (uint256) {
require(b != 0, errorMessage);
return a % b;
}
}
实际上,就是require语句在算术运算时进行了校验,若运算存在错误则会进行回滚并抛出错误信息。在SafeMath库的作用下,有效防止数值的溢出。
3.ERC20
3.1 导入safemath库
using SafeMath for uint256;
在合约中导入外部库,都是这个格式。
3.2 创建属性,管理员账户等
string public override name;
string public override symbol;
uit8 public override decimals;
address public contractOwner;// 创建管理员账户
uint256 private _totalSupply;// 记录转出的代币数量总和
3.3 创建映射
mapping(address => uint256) private _balances; // 可通过地址得出该地址还剩多少代币
// 账户A地址允许账户B地址使用多少代币
mapping(address => mapping(address => uint256)) private _allowances;
mapping(address => uint256) public totalTransfer;
3.4 构造器
constructor(string memory _name, string memory _symbol, uint8 _decimals) {
name = _name;
symbol = _symbol;
decimals = _decimals;
contractOwner = msg.sender; //表明在部署合约的时,部署合约地址就是合约管理员
}
3.5 新增方法
- 管理员可以给任意地址增发任意数量的代币
function addBalance(address _account, uint256 _amount) public {
//判断调用者是否是管理员,不是则报错停止进行。
require(msg.sender == contractOwner, "only owner");
// 使用SafeMath库给地址发_amount数量的代币
_balances[_account] = _balances[_account].add(_amount);
//更新_totalSupply
_totalSupply = _totalSupply.add(_amount);
}
- 管理员可以销毁任意代币
function reduceBalance(address _account, uint256 _amount) public {
//判断调用者是否是管理员,不是则报错停止进行。
require(msg.sender == contractOwner, "only owner");
//判断要销毁的代币是否小于该地址现有的代币值,不是则报错停止。
require(_amount < _balances[_account], "amount is larger than balance");
_balances[_account] = _balances[_account].sub(_amount);
//更新_totalSupply值
_totalSupply = _totalSupply.sub(_amount);
}
3.6重要方法理解
- transferFrom()方法
function transferFrom(address sender, address recipient, uint256 amount)
public virtual override returns (bool) {
_transfer(sender, recipient, amount);
_approve(
sender,
msg.sender,
//更新转账后的事件
_allowances[sender][msg.sender].sub(
amount,
" ERC20: transfer amount exceeds allowance"
)
);
return true;
}
- _transfer()方法
function _transfer(address sender, address recipient, uint256 amount) internal {
//接收方和转账方地址都不能为0
require(sender != address(0), "ERC20: transfer from the zero address");
require(recipient != address(0), "ERC20: transfer to the zero address");
//使用safemath库来判断amount是否小于调用者地址现有的代币
_balances[sender] = _balances[sender].sub(
amount,
"ERC20: transfer amount exceeds balance"
);
_balances[recipient] = _balances[recipient].add(amount);
totalTransfer[sender] = totalTransfer[sender].add(amount);
emit Transfer(sender, recipient, amount);
}
4.LDN
该合约继承ERC20合约。
contract LDN is ERC20 {
constructor() ERC20("ld ssa", "LDN", 18) {
_mint(msg.sender, 1 * 10**8 * 10**18);
}
}