关于ERC20.sol 中的decimals() 函数的理解
这个函数的目的就是告诉别人,合约中运行的计量单位Wei 和程序员显示的计量单位Ethers 之间的数量级关系。相当于我们在电子商城中,内部使用的单位是分,但是外部使用的单位是元,于是我就可以返回2作为进制差异。
在以太坊网络中,内部使用的单位是Wei ,外部使用的单位是Ether ,所以,返回18作为进制差异。
其中的view 的目的是控制函数,不可以改变状态。最典型的有下面三个情况:
第一个个毋庸置疑,就是改变状态变量;第二个可以简单归类为view 函数不能发送事件,一旦发送事件,那么以太坊的日志就会改变,所以也不算view ;创建其他合约,以太坊的数据也会改变。
总之,但凡使得以太坊数据可能发生变更的内容,都不可以在view 函数中出现。
virtual 标识能被子合约继承,override 标识重写了父亲合约。
returns(uint8) 标识返回的内容是uint8类型,uint8 标识无符号的8位整数,也就是能表示0~255的数字,完全足够。
function decimals() public view virtual override returns (uint8) {
return 18;
}
关于balanceOf 函数来获得用户余额
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
注意这个指定账户的余额,是按照单位Wei 进行标识的。这里的external 就是告诉我们,这个是外部函数,但是为什么不用public 呢?
public 表明函数或者变量,对外部和内部都可见;external 表明函数或者变量,只对外部可见,内部不可见。
- 下面这个函数时关于转账的逻辑
transfer
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount) external returns (bool);
注意,参数中只有address是用来接收转账钱款的,uint256 表示转账的数量,external 表示的是只有外部可见。
上面仅仅是函数的接口,下面是实现
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `recipient` cannot be the zero address.
* - the caller must have a balance of at least `amount`.
*/
function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
_transfer(_msgSender(), recipient, amount);
return true;
}
这个文档就2行,默认返回true。如果函数中出错,直接抛出异常,不会执行相应的逻辑。
该函数中有一个比较有趣的东西是_msgSender() ,其实,如果按照我的思路来讲的化,我觉得完全可以使用msg.sender() ,为什么需要使用一个函数呢?我觉得这应该是作者希望将所有的公共的东西,上下文的东西,全部整合到Context.sol; 中,这样可以做统一调整。
/**
* @dev Moves `amount` of tokens from `sender` to `recipient`.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* Requirements:
*
* - `sender` cannot be the zero address.
* - `recipient` cannot be the zero address.
* - `sender` must have a balance of at least `amount`.
*/
function _transfer(
address sender,
address recipient,
uint256 amount
) internal virtual {
require(sender != address(0), "ERC20: transfer from the zero address");
require(recipient != address(0), "ERC20: transfer to the zero address");
_beforeTokenTransfer(sender, recipient, amount);
uint256 senderBalance = _balances[sender];
require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
unchecked {
_balances[sender] = senderBalance - amount;
}
_balances[recipient] += amount;
emit Transfer(sender, recipient, amount);
_afterTokenTransfer(sender, recipient, amount);
}
上面的internal 表示该函数内部可见,外部不可见,其他没有上面可以多说的。
这里的address(0) 表示的是发送者和接收者不可以是0地址,这里涉及的显示转换,将数字转换为地址,这里是防止因为地址没有填写而造成的错误转账。
_beforeTokenTransfer 表示的转帐前需要做的事情,当然也可以直接抛出异常来中断转账函数,相当于非主线逻辑全部使用异常来解决。同理,位于最下面的_afterTokenTransfer 表示转账成功后要做的事情。
_balances 是一个mapping类型,他的定义是mapping(address => uint256) private _balances; ,定义非常明确,内部可见,可以根据地址获得余额。于是,下面这两句很容易理解:
uint256 senderBalance = _balances[sender];
...
_balances[recipient] += amount;
现在下面这个东西很有趣,首先,作者用require 确定,发送的金额一定不能大于转账的金额,案例说一定可以转成功的,为什么还需要加unchecked 这个区域呢?
require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
unchecked {
_balances[sender] = senderBalance - amount;
}
这里我认为更多的是一种心理上的安慰,因为不会发证溢出的情况。
如果一个元素向上溢出或者向下溢出,不加unchecked ,就会抛出异常。加上unchecked ,则会在让数值循环,比如说
0-1=>2^256-1
internal 在内部函数可以高效的传递memory 引用,因为内存并没有并清空掉。因此,完全可以在智能合约中使用递归的方式来处理数组中的内容,但是千万要注意,递归占用的栈插槽不可以大于1024,因为每次递归调用都至少使用一个栈插槽,而EVM 限制栈插槽的数量为1024。
关于allowance函数来获得授权额度
下面我觉得可以理解为银行的授信额度,这里头有一个external view ,没有上面可说的。
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
主要是看下实现:
/**
* @dev See {IERC20-transferFrom}.
*
* Emits an {Approval} event indicating the updated allowance. This is not
* required by the EIP. See the note at the beginning of {ERC20}.
*
* Requirements:
*
* - `sender` and `recipient` cannot be the zero address.
* - `sender` must have a balance of at least `amount`.
* - the caller must have allowance for ``sender``'s tokens of at least
* `amount`.
*/
function transferFrom(
address sender,
address recipient,
uint256 amount
) public virtual override returns (bool) {
_transfer(sender, recipient, amount);
uint256 currentAllowance = _allowances[sender][_msgSender()];
require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
unchecked {
_approve(sender, _msgSender(), currentAllowance - amount);
}
return true;
}
首先第一步是转账,第二部才校验授信额度够不够转账,如果不够的话,直接报异常,如果够的话,更新授信额度。
可以看到,这个函数是virtual 的,我们可以继承并更新函数。
关于approve函数
下面是ERC20 中的关于授信的函数,这个函数是外部函数,没有什么可以说的。
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
下面是这段函数的实现
/**
* @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*/
function _approve(
address owner,
address spender,
uint256 amount
) internal virtual {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
也非常简单,但是这个函数有一个问题,加入两次授信,第一次授信金额大,第二次授信金额小,那么是不是意味着有问题呢?
到目前位置,ERC20中的所有函数已经基本实现,下面实现的函数都是为了弥补整个收发币的流程。
increaseAllowance 和decreaseAllowance 用来增加和减少额度,这个是为了弥补approve 。
_mint 和_burn 用来给指定账户进行发币和燃烧,以维持币值健康。
_beforeTokenTransfer 和_afterTokenTransfer 这俩方法出现在涉及转账的所有交易里头,相当于Java中的环绕,可以在这些地方做一些日志相关的东西或者安全相关的东西。
|