DeFi基石ERC4626标准实现一个金库合约

前言

ERC4626标准是对ERC20标准的扩展,旨在提供统一的API标准,本文通过对ERC4626标准的实现一个保险金库的智能合约;

ERC4626标准

定义
ERC4626 是 ERC20 的扩展,提供了一个标准 API,用于表示单个底层 ERC-20 代币的收益保险库份额;

举例说明:用户通过存入 ERC20 Token,从而获取一定比例的 vToken。在 ERC20 Token 存入的过程中,会在一定的时间内产生收益。在收益到期后,用户可以通过持有的 vToken 个数,获得一定比例的收益回报;

功能:

  • 存款和提取:用户可以将 ERC20 代币存入保险库,并获取相应的份额代币。用户也可以通过销毁份额代币来提取基础资产。
  • 余额查询:用户可以查询保险库中管理的基础资产总额,以及特定用户地址的存款和提取限额。
  • 转换率:提供将基础资产转换为份额代币,以及将份额代币转换为基础资产的功能。
  • 事件:定义了存款和提取时触发的事件,如 DepositWithdraw

优点:

  • 代币化:继承了 ERC20,用户在向金库存款时,将获得同样符合 ERC20 标准的金库份额。
  • 更好的流通性:用户可以在不取回基础资产的情况下,利用金库份额进行其他操作,如在 Uniswap 上提供流动性或交易。
  • 更好的可组合性:有了标准之后,用一套接口可以和所有 ERC4626 金库交互,让基于金库的应用、插件、工具开发更容易。

在DeFi领域的使用场景

  • 收益农场:用户可以将代币质押到收益农场,获取利息。
  • 借贷:用户可以将代币出借,获取存款利息和贷款。
  • 质押:用户可以将代币质押参与质押,获取生息的代币。

合约开发

合约说明:合约包含存款,退款、铸造、转换率等功能


// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import {ERC20, IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "hardhat/console.sol";

contract ERC4626 is ERC20, IERC4626 {
    /*//////////////////////////////////////////////////////////////
                    状态变量
    //////////////////////////////////////////////////////////////*/
    ERC20 private immutable _asset; // 
    uint8 private immutable _decimals;

    constructor(
        ERC20 asset_,
        string memory name_,
        string memory symbol_
    ) ERC20(name_, symbol_) {
        _asset = asset_;
        _decimals = asset_.decimals();

    }

    /** @dev See {IERC4626-asset}. */
    function asset() public view virtual override returns (address) {
        return address(_asset);
    }

    /**
     * See {IERC20Metadata-decimals}.
     */
    function decimals() public view virtual override(IERC20Metadata, ERC20) returns (uint8) {
        return _decimals;
    }

    /*//////////////////////////////////////////////////////////////
                        存款/提款逻辑
    //////////////////////////////////////////////////////////////*/
    /** @dev See {IERC4626-deposit}. */
    function deposit(uint256 assets, address receiver) public virtual returns (uint256 shares) {
        // 利用 previewDeposit() 计算将获得的金库份额
        shares = previewDeposit(assets);

        // 先 transfer 后 mint,防止重入
        _asset.transferFrom(msg.sender, address(this), assets);
        _mint(receiver, shares);

        // 释放 Deposit 事件
        emit Deposit(msg.sender, receiver, assets, shares);
    }

    /** @dev See {IERC4626-mint}. */
    function mint(uint256 shares, address receiver) public virtual returns (uint256 assets) {
        // 利用 previewMint() 计算需要存款的基础资产数额
        assets = previewMint(shares);
        // 先 transfer 后 mint,防止重入
        _asset.transfer(address(this), assets*10**18);
        // _asset.transferFrom(msg.sender, address(this), assets);
        _mint(receiver, shares*10**18);

        // 释放 Deposit 事件
        emit Deposit(msg.sender, receiver, assets, shares);

    }

    /** @dev See {IERC4626-withdraw}. */
    function withdraw(
        uint256 assets,
        address receiver,
        address owner
    ) public virtual returns (uint256 shares) {
        // 利用 previewWithdraw() 计算将销毁的金库份额
        shares = previewWithdraw(assets);

        // 如果调用者不是 owner,则检查并更新授权
        if (msg.sender != owner) {
            _spendAllowance(owner, msg.sender, shares);
        }

        // 先销毁后 transfer,防止重入
        _burn(owner, shares*10**18);
        _asset.transfer(receiver, assets);

        // 释放 Withdraw 函数
        emit Withdraw(msg.sender, receiver, owner, assets, shares);
    }

    /** @dev See {IERC4626-redeem}. */
    function redeem(
        uint256 shares,
        address receiver,
        address owner
    ) public virtual returns (uint256 assets) {
        // 利用 previewRedeem() 计算能赎回的基础资产数额
        assets = previewRedeem(shares);

        // 如果调用者不是 owner,则检查并更新授权
        if (msg.sender != owner) {
            _spendAllowance(owner, msg.sender, shares);
        }

        // 先销毁后 transfer,防止重入
        _burn(owner, shares*10**18);
        _asset.transfer(receiver, assets*10**18);

        // 释放 Withdraw 函数        
        emit Withdraw(msg.sender, receiver, owner, assets, shares);
    }

    /*//////////////////////////////////////////////////////////////
                            会计逻辑
    //////////////////////////////////////////////////////////////*/
    /** @dev See {IERC4626-totalAssets}. */
    function totalAssets() public view virtual returns (uint256){
        // 返回合约中基础资产持仓
        return _asset.balanceOf(address(this));
    }

    /** @dev See {IERC4626-convertToShares}. */
    function convertToShares(uint256 assets) public view virtual returns (uint256) {
        uint256 supply = totalSupply();
        // 如果 supply 为 0,那么 1:1 铸造金库份额
        // 如果 supply 不为0,那么按比例铸造
        return supply == 0 ? assets : assets * supply / totalAssets();
    }

    /** @dev See {IERC4626-convertToAssets}. */
    function convertToAssets(uint256 shares) public view virtual returns (uint256) {
        uint256 supply = totalSupply();
        // 如果 supply 为 0,那么 1:1 赎回基础资产
        // 如果 supply 不为0,那么按比例赎回
        return supply == 0 ? shares : shares * totalAssets() / supply;
    }

    /** @dev See {IERC4626-previewDeposit}. */
    function previewDeposit(uint256 assets) public view virtual returns (uint256) {
        return convertToShares(assets);
    }

    /** @dev See {IERC4626-previewMint}. */
    function previewMint(uint256 shares) public view virtual returns (uint256) {
        return convertToAssets(shares);
    }

    /** @dev See {IERC4626-previewWithdraw}. */
    function previewWithdraw(uint256 assets) public view virtual returns (uint256) {
        return convertToShares(assets);
    }

    /** @dev See {IERC4626-previewRedeem}. */
    function previewRedeem(uint256 shares) public view virtual returns (uint256) {
        return convertToAssets(shares);
    }

    /*//////////////////////////////////////////////////////////////
                     DEPOSIT/WITHDRAWAL LIMIT LOGIC
    //////////////////////////////////////////////////////////////*/
    /** @dev See {IERC4626-maxDeposit}. */
    function maxDeposit(address) public view virtual returns (uint256) {
        return type(uint256).max;
    }

    /** @dev See {IERC4626-maxMint}. */
    function maxMint(address) public view virtual returns (uint256) {
        return type(uint256).max;
    }
    
    /** @dev See {IERC4626-maxWithdraw}. */
    function maxWithdraw(address owner) public view virtual returns (uint256) {
        return convertToAssets(balanceOf(owner));
    }
    
    /** @dev See {IERC4626-maxRedeem}. */
    function maxRedeem(address owner) public view virtual returns (uint256) {
        return balanceOf(owner);
    }
}
# 编译指令
# npx hardhat compile

合约测试

const {ethers,getNamedAccounts,deployments} = require("hardhat");
const { assert,expect } = require("chai");
describe("Treasury",function(){
    let token;
    let treasury;
    let addr1;
    let addr2;
    let firstAccount;
    let secondAccount;
    beforeEach(async function(){
        await deployments.fixture(["token","Treasury"]);
        [addr1,addr2]=await ethers.getSigners();
        firstAccount=(await getNamedAccounts()).firstAccount;
        secondAccount=(await getNamedAccounts()).secondAccount;
        //代币合约
        const tokenDeployment = await deployments.get("MyToken");
        token = await ethers.getContractAt("MyToken",tokenDeployment.address);//已经部署的合约交互
        //金库合约
        const treasuryDeployment = await deployments.get("ERC4626");
        treasury = await ethers.getContractAt("ERC4626",treasuryDeployment.address);//已经部署的合约交互
       
    });
    describe("金库合约",function(){
        it("金库合约测试",async function(){
            //获取代币的余额
            const balance = await token.balanceOf(firstAccount);
            console.log("余额",`${ethers.utils.formatEther(balance)} ETH`);
            //将代币授权给ERC4626合约
            await token.approve(treasury.address,balance);
            //将代币存入金库
            await treasury.deposit(balance,firstAccount);
            //获取金库中的代币余额
            const treasuryBalance = await treasury.balanceOf(firstAccount);
            console.log("金库余额",`${ethers.utils.formatEther(treasuryBalance)} ETH`);
            //铸造1000个代币
            await treasury.mint(1000,firstAccount);
            //将代币存入金库
            let treasuryBalance1=await treasury.balanceOf(firstAccount);
            console.log("金库余额",`${ethers.utils.formatEther(treasuryBalance1)} ETH`);
            //提款
            await treasury.withdraw(100,firstAccount,firstAccount);
            let treasuryBalance2=await treasury.balanceOf(firstAccount);
            console.log("金库余额提款",`${ethers.utils.formatEther(treasuryBalance2)} ETH`);
            //赎回
            await treasury.redeem(900,firstAccount,firstAccount);
            let treasuryBalance3=await treasury.balanceOf(firstAccount);
            console.log("金库余额",`${ethers.utils.formatEther(treasuryBalance3)} ETH`);
        });
    })

})
# 测试指令
# npx hardhat test ./test/xxx.js

合约部署

module.exports = async function ({getNamedAccounts,deployments}) {
  const  firstAccount= (await getNamedAccounts()).firstAccount;
  const {deploy,log} = deployments;
  const MyToken=await deployments.get("MyToken");//获取代币合约地址
  const TokenAddress = MyToken.address;//代币合约地址
  const Treasury=await deploy("ERC4626",{
    from:firstAccount,
    args: [TokenAddress,"Treasury ETH","TBTH"],//参数 代币地址,name,symble
    log: true,
  })
  console.log('金库合约地址',Treasury.address)
}
module.exports.tags = ["all", "Treasury"];
# 部署指令
# npx hardhat deploy

总结

以上就是ERC4626标准的保险金库合约从开发到测试再到部署的全部过程,包含了存取款,铸造,转化率等功能;

版权声明:
作者:玉兰
链接:https://www.techfm.club/p/183840.html
来源:TechFM
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
< <上一篇
下一篇>>