ERC20Wrapper能够实现对指定ERC20的锚定,通过调用ERC20Wrapper的存入、取出方法,在实现锚定ERC20转移的同时,实现等值ERC20Wrapper的mint、burn。
用法参考
可用方式如下:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IERC20,ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {ERC20Wrapper} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Wrapper.sol";
contract MyERC20Wrapper is ERC20, ERC20Wrapper{
constructor(IERC20 _underlying) ERC20("MyWrapper","MW") ERC20Wrapper(_underlying){
}
function decimals() public view virtual override(ERC20, ERC20Wrapper) returns (uint8) {
return super.decimals();
}
}
部署后对外暴露的方法如下所示:
ERC20相关方法不做过多介绍,主要说明下ERC20Wrapper相关个性化方法:
1、depositFor:该方法能够首先从指定地址向当前ERC20Wrapper合约地址转移指定数量锚定ERC20 TOKEN,然后向该地址铸造等量的ERC20Wrapper TOKEN。这里要注意的是这个ERC20转移底层实现ERC20的转移方式为transferFrom,所以会消耗授权额度,具体消耗的是指定地址对当前ERC20Wrapper合约地址的授权额度,因此在调用该方法前,要先在指定账户上进行approve(ERC20Wrapper,value)。
2、withdrawTo:该方法首先进行ERC20Wrapper TOKEN的销毁,然后从当前ERC20Wrapper合约向指定账户转账等量锚定ERC20,这里的ERC20转移调用的是transfer方法,因此不消耗授权额度,因而不需要approve。
代码详解
状态变量与构造函数
首先看下状态变量与构造函数,状态变量为immutabe的锚定ERC20合约地址,在经过构造函数赋值后不能再修改,赋值要求锚定ERC20不能是当前ERC20Wrapper合约地址:
IERC20 private immutable _underlying;
/**
* @dev The underlying token couldn't be wrapped.
*/
error ERC20InvalidUnderlying(address token);
constructor(IERC20 underlyingToken) {
if (underlyingToken == this) {
revert ERC20InvalidUnderlying(address(this));
}
_underlying = underlyingToken;
}
函数
depositFor函数功能如前所述,实现细节如下:
//从指定地址account存入到当前合约value数量的ERC20,然后向该地址account铸造同等数量的ERC20Wrapper
function depositFor(address account, uint256 value) public virtual returns (bool) {
address sender = _msgSender();
if (sender == address(this)) {
revert ERC20InvalidSender(address(this));
}
if (account == address(this)) {
revert ERC20InvalidReceiver(account);
}
//SafeERC20为Library,因此在SafeERC20的safeTransferFrom方法中,msg.sender仍然为当前ERC20Wrapper合约地址,然后SafeERC20中调用的是锚定ERC20的transferFrom,所以在ERC20的transferFrom中的msg.sender就是当前ERC20Wrapper合约地址,因此要先进行account对ERC20Wrapper合约的可转移ERC20授权(approve)
SafeERC20.safeTransferFrom(_underlying, sender, address(this), value);
//向account铸造value数量的ERC20Wrapper
_mint(account, value);
return true;
}
withdrawTo函数功能如前所述,实现细节如下:
//从指定地址account销毁指定数量value的ERC20Wrapper,然后从ERC20Wrapper合约地址向account转移value数量的锚定ERC20
function withdrawTo(address account, uint256 value) public virtual returns (bool) {
if (account == address(this)) {
revert ERC20InvalidReceiver(account);
}
_burn(_msgSender(), value);
//safeTransfer底层调用的是transfer,因此不需要进行授权
SafeERC20.safeTransfer(_underlying, account, value);
return true;
}
_recover函数用于恢复错误转入该地址锚定ERC20的情况,实现细节如下:
//针对没有通过depositFor方法转入锚定ERC20,而是直接transfer,会出现锚定ERC20数量大于ERC20Wrapper数量的情况,可以同步铸造差值数量的ERC20Wrapper来找补错误转入的ERC20
function _recover(address account) internal virtual returns (uint256) {
uint256 value = _underlying.balanceOf(address(this)) - totalSupply();
_mint(account, value);
return value;
}