solidity学习之ERC4626
什么是ERC4626
ERC4626是对ERC20代币标准的扩展,用于实现收益金库的标准化,用户可以将资产质押到合约中,持有相应的shares凭证,通过凭证来享有合约后续的收益。
实现逻辑
ERC4626继承了ERC20,合约本身会发行一种shares代币,当用户存入指定的代币后,就会根据金库内的资产情况铸造响应的shares份额分发给用户,当用户想要赎回资产时,调用合约就会销毁shares份额,并将存入的代币和附加的收益返还给用户。
ERC4626提供了两种计算方式,用户可以根据存入的资产算出shares份额,也可以通过想要得到的份额计算出需要存入多少资产。
接口合约如下:
interface IERC4626 is IERC20, IERC20Metadata {
/*//////////////////////////////////////////////////////////////
事件
//////////////////////////////////////////////////////////////*/
// 存款时触发
event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);
// 取款时触发
event Withdraw(
address indexed sender,
address indexed receiver,
address indexed owner,
uint256 assets,
uint256 shares
);
/*//////////////////////////////////////////////////////////////
元数据
//////////////////////////////////////////////////////////////*/
/**
* @dev 返回金库的基础资产代币地址 (用于存款,取款)
* - 必须是 ERC20 代币合约地址.
* - 不能revert
*/
function asset() external view returns (address assetTokenAddress);
/*//////////////////////////////////////////////////////////////
存款/提款逻辑
//////////////////////////////////////////////////////////////*/
/**
* @dev 存款函数: 用户向金库存入 assets 单位的基础资产,然后合约铸造 shares 单位的金库额度给 receiver 地址
*
* - 必须释放 Deposit 事件.
* - 如果资产不能存入,必须revert,比如存款数额大大于上限等。
*/
function deposit(uint256 assets, address receiver) external returns (uint256 shares);
/**
* @dev 铸造函数: 用户需要存入 assets 单位的基础资产,然后合约给 receiver 地址铸造 share 数量的金库额度
* - 必须释放 Deposit 事件.
* - 如果全部金库额度不能铸造,必须revert,比如铸造数额大大于上限等。
*/
function mint(uint256 shares, address receiver) external returns (uint256 assets);
/**
* @dev 提款函数: owner 地址销毁 share 单位的金库额度,然后合约将 assets 单位的基础资产发送给 receiver 地址
* - 释放 Withdraw 事件
* - 如果全部基础资产不能提取,将revert
*/
function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);
/**
* @dev 赎回函数: owner 地址销毁 shares 数量的金库额度,然后合约将 assets 单位的基础资产发给 receiver 地址
* - 释放 Withdraw 事件
* - 如果金库额度不能全部销毁,则revert
*/
function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);
/*//////////////////////////////////////////////////////////////
会计逻辑
//////////////////////////////////////////////////////////////*/
/**
* @dev 返回金库中管理的基础资产代币总额
* - 要包含利息
* - 要包含费用
* - 不能revert
*/
function totalAssets() external view returns (uint256 totalManagedAssets);
/**
* @dev 返回利用一定数额基础资产可以换取的金库额度
* - 不要包含费用
* - 不包含滑点
* - 不能revert
*/
function convertToShares(uint256 assets) external view returns (uint256 shares);
/**
* @dev 返回利用一定数额金库额度可以换取的基础资产
* - 不要包含费用
* - 不包含滑点
* - 不能revert
*/
function convertToAssets(uint256 shares) external view returns (uint256 assets);
/**
* @dev 用于链上和链下用户在当前链上环境模拟存款一定数额的基础资产能够获得的金库额度
* - 返回值要接近且不大于在同一交易进行存款得到的金库额度
* - 不要考虑 maxDeposit 等限制,假设用户的存款交易会成功
* - 要考虑费用
* - 不能revert
* NOTE: 可以利用 convertToAssets 和 previewDeposit 返回值的差值来计算滑点
*/
function previewDeposit(uint256 assets) external view returns (uint256 shares);
/**
* @dev 用于链上和链下用户在当前链上环境模拟铸造 shares 数额的金库额度需要存款的基础资产数量
* - 返回值要接近且不小于在同一交易进行铸造一定数额金库额度所需的存款数量
* - 不要考虑 maxMint 等限制,假设用户的存款交易会成功
* - 要考虑费用
* - 不能revert
*/
function previewMint(uint256 shares) external view returns (uint256 assets);
/**
* @dev 用于链上和链下用户在当前链上环境模拟提款 assets 数额的基础资产需要赎回的金库份额
* - 返回值要接近且不大于在同一交易进行提款一定数额基础资产所需赎回的金库份额
* - 不要考虑 maxWithdraw 等限制,假设用户的提款交易会成功
* - 要考虑费用
* - 不能revert
*/
function previewWithdraw(uint256 assets) external view returns (uint256 shares);
/**
* @dev 用于链上和链下用户在当前链上环境模拟销毁 shares 数额的金库额度能够赎回的基础资产数量
* - 返回值要接近且不小于在同一交易进行销毁一定数额的金库额度所能赎回的基础资产数量
* - 不要考虑 maxRedeem 等限制,假设用户的赎回交易会成功
* - 要考虑费用
* - 不能revert.
*/
function previewRedeem(uint256 shares) external view returns (uint256 assets);
/*//////////////////////////////////////////////////////////////
存款/提款限额逻辑
//////////////////////////////////////////////////////////////*/
/**
* @dev 返回某个用户地址单次存款可存的最大基础资产数额。
* - 如果有存款上限,那么返回值应该是个有限值
* - 返回值不能超过 2 ** 256 - 1
* - 不能revert
*/
function maxDeposit(address receiver) external view returns (uint256 maxAssets);
/**
* @dev 返回某个用户地址单次铸造可以铸造的最大金库额度
* - 如果有铸造上限,那么返回值应该是个有限值
* - 返回值不能超过 2 ** 256 - 1
* - 不能revert
*/
function maxMint(address receiver) external view returns (uint256 maxShares);
/**
* @dev 返回某个用户地址单次取款可以提取的最大基础资产额度
* - 返回值应该是个有限值
* - 不能revert
*/
function maxWithdraw(address owner) external view returns (uint256 maxAssets);
/**
* @dev 返回某个用户地址单次赎回可以销毁的最大金库额度
* - 返回值应该是个有限值
* - 如果没有其他限制,返回值应该是 balanceOf(owner)
* - 不能revert
*/
function maxRedeem(address owner) external view returns (uint256 maxShares);
}
逻辑分为四部分:
- 元数据,asset()方法,返回的是基础资产,也就是存入资产的token地址。
- 存款提款逻辑,存款和提款各有两个方法,分别以asset和share为基准进行计算。
- 会计逻辑,用来计算asset和share互相转换的方法,既是存款提款的基础方法,也可以对外提供方便用户进行模拟计算。此处可以增加额外的逻辑,用来实现更复杂的算法。
- 限额逻辑,计算用户可存取的限额,与合约的设置以及用户的余额有关。
具体实现
function totalAssets() public view override returns (uint256 totalManagedAssets){
return _asset.balanceOf(address(this));
}
totalAssets就是简单地将当前合约下的asset返回。
function convertToShares(uint256 assets) public view override returns (uint256 shares){
uint256 supply = totalSupply();
return supply == 0 ? assets: assets * supply / totalAssets();
}
function convertToAssets(uint256 shares) public view override returns (uint256 assets){
uint256 supply = totalSupply();
return supply == 0? shares: shares * totalAssets() / supply;
}
convertToShares和convertToAssets是share和asset互相转换的方法,其中当supply为0时,也就是此时合约中还未有任何资产,也未有任何share,那么asset和share按照1:1的比例进行兑换。如果此时已经有资产,那么supply/totalAssets()就是每份asset可以兑换的share数量,反之亦然。
这里有两个思考点:
- 按这种计算方式,那么
asset和share始终都是1:1的,那么会出现不相等的情况是因为合约中是会有收益产生的(依赖于其他逻辑),此时asset增加而share不变,那么比例就发生了变化。 - 按理来说
supply=0时,share也应该为0,应该没有任何可兑换的asset,但此处兼容了这一场景,是为了防止revert的出现。
function previewDeposit(uint256 assets) public view override returns (uint256 shares){
return convertToShares(assets);
}
function previewMint(uint256 shares) public view returns (uint256 assets) {
return convertToAssets(shares);
}
function previewWithdraw(uint256 assets) public view returns (uint256 shares){
return convertToShares(assets);
}
function previewRedeem(uint256 shares) public view returns (uint256 assets) {
return convertToAssets(shares);
}
四个会计方法都简单地用了convert的调用,因为这里实现的是简版的合约,没有其他逻辑。
function maxDeposit(address receiver) external view returns (uint256 maxAssets){
return type(uint256).max;
}
function maxMint(address receiver) external view returns (uint256 maxShares){
return type(uint256).max;
}
function maxWithdraw(address owner) external view returns (uint256 maxAssets){
return convertToAssets(balanceOf(owner));
}
function maxRedeem(address owner) external view returns (uint256 maxShares){
return balanceOf(owner);
}
同样,限额逻辑也是简单地实现,并没有做过多地限制。
function deposit(uint256 assets, address receiver) external override returns (uint256 shares){
shares = previewDeposit(assets);
_asset.transferFrom(msg.sender, address(this), assets);
_mint(receiver, shares);
emit Deposit(msg.sender, receiver, assets, shares);
}
function mint(uint256 shares, address receiver) external returns (uint256 assets){
// 利用 previewMint() 计算需要存款的基础资产数额
assets = previewMint(shares);
// 先 transfer 后 mint,防止重入
_asset.transferFrom(msg.sender, address(this), assets);
_mint(receiver, shares);
// 释放 Deposit 事件
emit Deposit(msg.sender, receiver, assets, shares);
}
function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares){
shares = previewWithdraw(assets);
if(msg.sender != owner){
_spendAllowance(owner, msg.sender, shares);
}
_burn(owner, shares);
_asset.transfer(receiver, assets);
emit Withdraw(msg.sender, receiver, owner, assets, shares);
}
function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets){
assets = previewRedeem(shares);
if(msg.sender != owner){
_spendAllowance(owner, msg.sender, shares);
}
_burn(owner, shares);
_asset.transfer(receiver, assets);
emit Withdraw(msg.sender, receiver, owner, assets, shares);
}
可以看到deposit和mint,withdraw和redeem的实现都是基本一致的,只在于数量计算的区别。
有一个值得注意的地方就是deposit和mint只支持存入msg.sender自己的资产,但是withdraw和redeem是支持通过授权的形式由他人来调用的,所以当msg.sender不等于owner的时候,需要使用授权额度。
solidity学习之ERC4626的更多相关文章
- solidity学习-cryptoPunks为实例
在这里使用cryptoPunks为实例来进行solidity的介绍,一般这些内容理解了就能够进行相对简单的智能合约的编写了,同时会添加一些我认为也十分重要的内容学习文档为http://solidity ...
- solidity 学习笔记(7)内联汇编
为什么要有内联汇编? //普通循环和内敛汇编循环比较 pragma solidity ^0.4.25; contract Assembly{ function nativeLoop() public ...
- solidity 学习笔记(6)call 函数
call() 方法 call()是一个底层的接口,用来向一个合约发送消息,也就是说如果你想实现自己的消息传递,可以使用这个函数.函数支持传入任意类型的任意参数,并将参数打包成32字节,相互拼接后向合约 ...
- solidity 学习笔记(5)接口
接口:不用实现方法,仅仅定义方法. pragma solidity ^; contract cat{ //cat实际上实现了接口animalEat,因为他们有相同的方法. string name; f ...
- solidity 学习笔记(3) 函数修饰符/继承
修饰符: 函数修饰符有 constant view pure 其中 constant和view的功能是一样的 不会消耗gas 也就是说不会做任何存储 constant在5.0以后的版本中被废弃 ...
- solidity 学习笔记 2 (二维数组)
solidity 二维数组: pragma solidity ^0.4.23; contract twoArray{ uint[2][3] grade =[[20,30],[40,50],[45,60 ...
- solidity学习笔记
一 pragam solidity ^0.4.23; contract helloword{ string public name ="hello"; function getN ...
- 以太坊智能合约开发 Solidity学习
1. pragma solidity >=0.4.22 <0.6.0;//版本号,头文件 contract BooleanTest { bool _a;//默认返回false int nu ...
- solidity 学习笔记(4)library库
library库的申明: library SafeMath{ functrion mul(uint a,uint b) public returns (uint){ uint c= a*b; asse ...
- cryptopunks的代码解释
1.imageHash就是将punk所有图像合在一起的那张图punks.png进行hash得到一个值,并将该值存储到链上,用处就是你可以通过将图像hash然后跟该值对比看图像对不对.这就是它的用处,在 ...
随机推荐
- 【转载】数论学习笔记(Blog of tyqtyq)
from a famous oier \(\texttt{tyqtyq}\)请点链接tyqtyq~! - 博客园 (cnblogs.com) 数论分块 \(\sum_{i=1}^{n} \lfloor ...
- CRD的简单介绍
介绍 Custom Resource Define 简称 CRD,是 Kubernetes(v1.7+)为提高可扩展性,让开发者去自定义资源的一种方式. CRD 资源可以动态注册到集群中,注册完毕后, ...
- Springboot笔记<12>数据访问
springboot数据访问 springboot整合JDBC JdbcTemplate是Spring对JDBC的封装,目的是使JDBC更加易于使用.JdbcTemplate是Spring的一部分.J ...
- 【语义分割专栏】4:deeplab系列原理篇
目录 前言 背景介绍 Deeplab系列讲解 Deeplabv1 去池化和全连接层 空洞卷积(Atrous Convolution) 全连接条件随机场(Fully - connected Condit ...
- 如何测试wifi的吞吐率
基本知识储备 Interval 表示时间间隔, transfer 表示时间间隔传输的数据量, Bandwidth是时间间隔里的传输速率. Jitter 表示抖动. Lost/Total 表示丢包的数量 ...
- docker 开启远程访问功能
简介 部署了一个http服务在docker上,由于docker有自己的端口似乎无法访问 参考链接 https://blog.csdn.net/longzhanpeng/article/details/ ...
- 应用集成(iPaaS)和数据集成(ETL)高效协同的最佳实践
随着企业数字化的步伐加快,企业IT工程师和数据工程师在应用和数据集成方面的工作变得日益密切.IT工程师通常利用iPaaS(Integration Platform as a Service)来进行异构 ...
- SciTech-EECS-Power-SPS开关电源常用拓扑: Buck/Boost/Buck-Boost/Pull-Push/正激/Flyback反激/全桥/半桥
SciTech-EECS-Power-SPS开关电源常用拓扑: 汇总几种常见的开关电源拓扑结构及应用 [导读] 拓扑(电路拓扑): 是 "功率器件(常用 二极管+BJT/FET/IGBT/S ...
- POLIR-Society-Organization-Mind-真实社论: 守护好自我能量场:{气定心定,心定事顺} + 常政"平凡生活" + 不需要去应答负能量事情
守护好自我能量场心定事顺 少在烂事上纠缠,少为不值得的人生气烦心. 守护好自己的能量场: 气定心定, 心定事顺. 不需要去应答负能量事情 凡是负能量的事情, 都不需要去应答. 凡是应答, 必将与其纠缠 ...
- Linguistics-English-Psychology-Minds: Human Demands + Growth • Refuse with Boundaries+Principles • Self-Actualization with InnerPeace+SelfMastery
Linguistics-English-Minds: The Standard of and essence of GROWTH The standard of growth The standard ...