ERC20Wrapper库是一种对某ERC20 token作为标的资产进行包装的wrapped ERC20 token。用户可以质押或赎回标的token,同时获得或销毁相同数量的wrapped token。
[openzeppelin]:v4.8.3,[forge-std]:v1.5.6
ERC20Wrapper库是一种对某ERC20 token作为标的资产进行包装的wrapped ERC20 token。用户可以质押或赎回标的token,同时获得或销毁相同数量的wrapped token。该库可与其他ERC20拓展库结合使用,如:与ERC20Votes库结合,可将已有的ERC20 token包装成治理token。
继承ERC20Wrapper合约:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Wrapper.sol";
contract MockERC20Wrapper is ERC20Wrapper {
constructor(
string memory name,
string memory symbol,
IERC20 underlyingToken
)
ERC20Wrapper(underlyingToken)
ERC20(name, symbol)
{}
function recover(address account) external {
_recover(account);
}
}
全部foundry测试合约:
测试使用的物料合约:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
contract MockERC20 is ERC20 {
constructor(string memory name, string memory symbol)
ERC20(name, symbol) {}
function mint(address account, uint amount) external {
_mint(account, amount);
}
}
contract MockERC20WithDecimals {
uint8 private _decimals;
constructor(uint8 dec){
_decimals = dec;
}
function decimals() external view returns (uint8){
return _decimals;
}
}
contract MockERC20WithoutDecimals {}
// 底层的标的ERC20合约地址
IERC20 public immutable underlying;
// 设置底层的标的ERC20合约地址
constructor(IERC20 underlyingToken) {
underlying = underlyingToken;
}
重写ERC20.decimals(),定义wrapper token的decimals。
function decimals() public view virtual override returns (uint8) {
// 如果标的token的decimals()方法可以成功调用,wrapped token的decimals与之一致
try IERC20Metadata(address(underlying)).decimals() returns (uint8 value) {
return value;
} catch {
// 如果标的token的decimals()方法无法成功调用,wrapped token的decimals为ERC20.decimals(),即18
return super.decimals();
}
}
foundry代码验证:
contract ERC20WrapperTest is Test {
MockERC20Wrapper private _testing = new MockERC20Wrapper("test name", "test symbol", _underlyingToken);
function test_Decimals() external {
// case 1: underlying token with 1 decimals
MockERC20WithDecimals mockERC20WithDecimals = new MockERC20WithDecimals(1);
_testing = new MockERC20Wrapper("test name", "test symbol", IERC20(address(mockERC20WithDecimals)));
assertEq(_testing.decimals(), mockERC20WithDecimals.decimals());
// case 2: underlying token without decimals
MockERC20WithoutDecimals mockERC20WithoutDecimals = new MockERC20WithoutDecimals();
_testing = new MockERC20Wrapper("test name", "test symbol", IERC20(address(mockERC20WithoutDecimals)));
assertEq(_testing.decimals(), 18);
}
}
用户质押标的token到本合约内并为account地址铸造对应数量的wrapped token。
function depositFor(address account, uint256 amount) public virtual returns (bool) {
// 使用SafeERC20库的safeTransferFrom方法,转移用户名下数量为amount的标的token到本合约下
SafeERC20.safeTransferFrom(underlying, _msgSender(), address(this), amount);
// 为account地址铸造数量为amount的wrapped token
_mint(account, amount);
// 返回true
return true;
}
foundry代码验证:
contract ERC20WrapperTest is Test {
MockERC20 private _underlyingToken = new MockERC20("test name", "test symbol");
MockERC20Wrapper private _testing = new MockERC20Wrapper("test name", "test symbol", _underlyingToken);
address private account = address(1);
function setUp() external {
_underlyingToken.mint(address(this), 100);
}
function test_DepositFor() external {
assertEq(_testing.balanceOf(account), 0);
assertEq(_testing.totalSupply(), 0);
assertEq(_underlyingToken.balanceOf(address(this)), 100);
assertEq(_underlyingToken.balanceOf(address(_testing)), 0);
_underlyingToken.approve(address(_testing), 100);
uint amountToDeposit = 10;
assertTrue(_testing.depositFor(account, amountToDeposit));
// check balances
assertEq(_testing.balanceOf(account), 0 + amountToDeposit);
assertEq(_testing.totalSupply(), 0 + amountToDeposit);
assertEq(_underlyingToken.balanceOf(address(this)), 100 - amountToDeposit);
assertEq(_underlyingToken.balanceOf(address(_testing)), 0 + amountToDeposit);
}
}
用户销毁数量为amount的wrapped token并从本合约转移相同数量的标的token到account地址。
function withdrawTo(address account, uint256 amount) public virtual returns (bool) {
// 销毁调用者数量为amount的wrapped token
_burn(_msgSender(), amount);
// 使用SafeERC20库的safeTransfer方法,从本合约转移数量为amount的标的token到account地址
SafeERC20.safeTransfer(underlying, account, amount);
// 返回true
return true;
}
foundry代码验证:
contract ERC20WrapperTest is Test {
MockERC20 private _underlyingToken = new MockERC20("test name", "test symbol");
MockERC20Wrapper private _testing = new MockERC20Wrapper("test name", "test symbol", _underlyingToken);
address private account = address(1);
function setUp() external {
_underlyingToken.mint(address(this), 100);
}
function test_WithdrawTo() external {
_underlyingToken.approve(address(_testing), 100);
_testing.depositFor(account, 100);
assertEq(_underlyingToken.balanceOf(address(_testing)), 100);
assertEq(_underlyingToken.balanceOf(address(account)), 0);
assertEq(_underlyingToken.balanceOf(address(this)), 0);
assertEq(_testing.balanceOf(address(account)), 100);
assertEq(_testing.totalSupply(), 100);
uint amountToWithdraw = 10;
vm.prank(account);
assertTrue(_testing.withdrawTo(address(this), amountToWithdraw));
assertEq(_underlyingToken.balanceOf(address(_testing)), 100 - amountToWithdraw);
assertEq(_underlyingToken.balanceOf(address(account)), 0);
assertEq(_underlyingToken.balanceOf(address(this)), 0 + amountToWithdraw);
assertEq(_testing.balanceOf(address(account)), 100 - amountToWithdraw);
assertEq(_testing.totalSupply(), 100 - amountToWithdraw);
}
}
当本合约名下的标的资产数量与wrapped token总量出现不一致时,为account地址铸造对应差值的wrapped token。
注:本方法为internal方法,可以将其封装成一个带权限管理的external方法。
function _recover(address account) internal virtual returns (uint256) {
// 计算本合约名下的标的资产数量与wrapped token总量的差值
uint256 value = underlying.balanceOf(address(this)) - totalSupply();
// 为account地址铸造数量为上述差值的wrapped token
_mint(account, value);
// 返回差值
return value;
}
foundry代码验证:
contract ERC20WrapperTest is Test {
MockERC20 private _underlyingToken = new MockERC20("test name", "test symbol");
MockERC20Wrapper private _testing = new MockERC20Wrapper("test name", "test symbol", _underlyingToken);
address private account = address(1);
function setUp() external {
_underlyingToken.mint(address(this), 100);
}
function test_Recover() external {
// transfer underlying token into ERC20Wrapper directly
_underlyingToken.transfer(address(_testing), 20);
assertEq(_underlyingToken.balanceOf(address(_testing)), 20);
assertEq(_testing.totalSupply(), 0);
assertEq(_testing.balanceOf(account), 0);
uint difference = _underlyingToken.balanceOf(address(_testing)) - _testing.totalSupply();
_testing.recover(account);
assertEq(_underlyingToken.balanceOf(address(_testing)), 20);
assertEq(_testing.totalSupply(), 0 + difference);
assertEq(_testing.balanceOf(account), 0 + difference);
}
}
ps: 本人热爱图灵,热爱中本聪,热爱V神。 以下是我个人的公众号,如果有技术问题可以关注我的公众号来跟我交流。 同时我也会在这个公众号上每周更新我的原创文章,喜欢的小伙伴或者老伙计可以支持一下! 如果需要转发,麻烦注明作者。十分感谢!
公众号名称:后现代泼痞浪漫主义奠基人
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!