{"author":{"address":null,"user":"https://learnblockchain.cn/people/18158"},"content":{"body":"# 前言\r\n\u003e ERC4626标准是对ERC20标准的扩展，旨在提供统一的API标准，本文通过对ERC4626标准的实现一个保险金库的智能合约；\r\n# ERC4626标准\r\n**定义**：\r\nERC4626 是 ERC20 的扩展，提供了一个标准 API，用于表示单个底层 ERC-20 代币的收益保险库份额；\u003cbr\u003e\r\n**举例说明**：用户通过存入 ERC20 Token，从而获取一定比例的 vToken。在 ERC20 Token 存入的过程中，会在一定的时间内产生收益。在收益到期后，用户可以通过持有的 vToken 个数，获得一定比例的收益回报；\u003cbr\u003e\r\n#### 功能：\r\n-   **存款和提取**：用户可以将 ERC20 代币存入保险库，并获取相应的份额代币。用户也可以通过销毁份额代币来提取基础资产。\r\n-   **余额查询**：用户可以查询保险库中管理的基础资产总额，以及特定用户地址的存款和提取限额。\r\n-   **转换率**：提供将基础资产转换为份额代币，以及将份额代币转换为基础资产的功能。\r\n-   **事件**：定义了存款和提取时触发的事件，如 `Deposit` 和 `Withdraw`。\r\n#### 优点：\r\n-   **代币化**：继承了 ERC20，用户在向金库存款时，将获得同样符合 ERC20 标准的金库份额。\r\n-   **更好的流通性**：用户可以在不取回基础资产的情况下，利用金库份额进行其他操作，如在 Uniswap 上提供流动性或交易。\r\n-   **更好的可组合性**：有了标准之后，用一套接口可以和所有 ERC4626 金库交互，让基于金库的应用、插件、工具开发更容易。\r\n#### 在DeFi领域的使用场景\r\n-   **收益农场**：用户可以将代币质押到收益农场，获取利息。\r\n-   **借贷**：用户可以将代币出借，获取存款利息和贷款。\r\n-   **质押**：用户可以将代币质押参与质押，获取生息的代币。\r\n\r\n# 合约开发\r\n**合约说明**：合约包含存款，退款、铸造、转换率等功能\r\n```\r\n\r\n// SPDX-License-Identifier: MIT\r\npragma solidity \u003e=0.8.0;\r\n\r\nimport {IERC4626} from \"@openzeppelin/contracts/interfaces/IERC4626.sol\";\r\nimport {ERC20, IERC20Metadata} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\r\nimport \"hardhat/console.sol\";\r\n\r\ncontract ERC4626 is ERC20, IERC4626 {\r\n    /*//////////////////////////////////////////////////////////////\r\n                    状态变量\r\n    //////////////////////////////////////////////////////////////*/\r\n    ERC20 private immutable _asset; // \r\n    uint8 private immutable _decimals;\r\n\r\n    constructor(\r\n        ERC20 asset_,\r\n        string memory name_,\r\n        string memory symbol_\r\n    ) ERC20(name_, symbol_) {\r\n        _asset = asset_;\r\n        _decimals = asset_.decimals();\r\n\r\n    }\r\n\r\n    /** @dev See {IERC4626-asset}. */\r\n    function asset() public view virtual override returns (address) {\r\n        return address(_asset);\r\n    }\r\n\r\n    /**\r\n     * See {IERC20Metadata-decimals}.\r\n     */\r\n    function decimals() public view virtual override(IERC20Metadata, ERC20) returns (uint8) {\r\n        return _decimals;\r\n    }\r\n\r\n    /*//////////////////////////////////////////////////////////////\r\n                        存款/提款逻辑\r\n    //////////////////////////////////////////////////////////////*/\r\n    /** @dev See {IERC4626-deposit}. */\r\n    function deposit(uint256 assets, address receiver) public virtual returns (uint256 shares) {\r\n        // 利用 previewDeposit() 计算将获得的金库份额\r\n        shares = previewDeposit(assets);\r\n\r\n        // 先 transfer 后 mint，防止重入\r\n        _asset.transferFrom(msg.sender, address(this), assets);\r\n        _mint(receiver, shares);\r\n\r\n        // 释放 Deposit 事件\r\n        emit Deposit(msg.sender, receiver, assets, shares);\r\n    }\r\n\r\n    /** @dev See {IERC4626-mint}. */\r\n    function mint(uint256 shares, address receiver) public virtual returns (uint256 assets) {\r\n        // 利用 previewMint() 计算需要存款的基础资产数额\r\n        assets = previewMint(shares);\r\n        // 先 transfer 后 mint，防止重入\r\n        _asset.transfer(address(this), assets*10**18);\r\n        // _asset.transferFrom(msg.sender, address(this), assets);\r\n        _mint(receiver, shares*10**18);\r\n\r\n        // 释放 Deposit 事件\r\n        emit Deposit(msg.sender, receiver, assets, shares);\r\n\r\n    }\r\n\r\n    /** @dev See {IERC4626-withdraw}. */\r\n    function withdraw(\r\n        uint256 assets,\r\n        address receiver,\r\n        address owner\r\n    ) public virtual returns (uint256 shares) {\r\n        // 利用 previewWithdraw() 计算将销毁的金库份额\r\n        shares = previewWithdraw(assets);\r\n\r\n        // 如果调用者不是 owner，则检查并更新授权\r\n        if (msg.sender != owner) {\r\n            _spendAllowance(owner, msg.sender, shares);\r\n        }\r\n\r\n        // 先销毁后 transfer，防止重入\r\n        _burn(owner, shares*10**18);\r\n        _asset.transfer(receiver, assets);\r\n\r\n        // 释放 Withdraw 函数\r\n        emit Withdraw(msg.sender, receiver, owner, assets, shares);\r\n    }\r\n\r\n    /** @dev See {IERC4626-redeem}. */\r\n    function redeem(\r\n        uint256 shares,\r\n        address receiver,\r\n        address owner\r\n    ) public virtual returns (uint256 assets) {\r\n        // 利用 previewRedeem() 计算能赎回的基础资产数额\r\n        assets = previewRedeem(shares);\r\n\r\n        // 如果调用者不是 owner，则检查并更新授权\r\n        if (msg.sender != owner) {\r\n            _spendAllowance(owner, msg.sender, shares);\r\n        }\r\n\r\n        // 先销毁后 transfer，防止重入\r\n        _burn(owner, shares*10**18);\r\n        _asset.transfer(receiver, assets*10**18);\r\n\r\n        // 释放 Withdraw 函数        \r\n        emit Withdraw(msg.sender, receiver, owner, assets, shares);\r\n    }\r\n\r\n    /*//////////////////////////////////////////////////////////////\r\n                            会计逻辑\r\n    //////////////////////////////////////////////////////////////*/\r\n    /** @dev See {IERC4626-totalAssets}. */\r\n    function totalAssets() public view virtual returns (uint256){\r\n        // 返回合约中基础资产持仓\r\n        return _asset.balanceOf(address(this));\r\n    }\r\n\r\n    /** @dev See {IERC4626-convertToShares}. */\r\n    function convertToShares(uint256 assets) public view virtual returns (uint256) {\r\n        uint256 supply = totalSupply();\r\n        // 如果 supply 为 0，那么 1:1 铸造金库份额\r\n        // 如果 supply 不为0，那么按比例铸造\r\n        return supply == 0 ? assets : assets * supply / totalAssets();\r\n    }\r\n\r\n    /** @dev See {IERC4626-convertToAssets}. */\r\n    function convertToAssets(uint256 shares) public view virtual returns (uint256) {\r\n        uint256 supply = totalSupply();\r\n        // 如果 supply 为 0，那么 1:1 赎回基础资产\r\n        // 如果 supply 不为0，那么按比例赎回\r\n        return supply == 0 ? shares : shares * totalAssets() / supply;\r\n    }\r\n\r\n    /** @dev See {IERC4626-previewDeposit}. */\r\n    function previewDeposit(uint256 assets) public view virtual returns (uint256) {\r\n        return convertToShares(assets);\r\n    }\r\n\r\n    /** @dev See {IERC4626-previewMint}. */\r\n    function previewMint(uint256 shares) public view virtual returns (uint256) {\r\n        return convertToAssets(shares);\r\n    }\r\n\r\n    /** @dev See {IERC4626-previewWithdraw}. */\r\n    function previewWithdraw(uint256 assets) public view virtual returns (uint256) {\r\n        return convertToShares(assets);\r\n    }\r\n\r\n    /** @dev See {IERC4626-previewRedeem}. */\r\n    function previewRedeem(uint256 shares) public view virtual returns (uint256) {\r\n        return convertToAssets(shares);\r\n    }\r\n\r\n    /*//////////////////////////////////////////////////////////////\r\n                     DEPOSIT/WITHDRAWAL LIMIT LOGIC\r\n    //////////////////////////////////////////////////////////////*/\r\n    /** @dev See {IERC4626-maxDeposit}. */\r\n    function maxDeposit(address) public view virtual returns (uint256) {\r\n        return type(uint256).max;\r\n    }\r\n\r\n    /** @dev See {IERC4626-maxMint}. */\r\n    function maxMint(address) public view virtual returns (uint256) {\r\n        return type(uint256).max;\r\n    }\r\n    \r\n    /** @dev See {IERC4626-maxWithdraw}. */\r\n    function maxWithdraw(address owner) public view virtual returns (uint256) {\r\n        return convertToAssets(balanceOf(owner));\r\n    }\r\n    \r\n    /** @dev See {IERC4626-maxRedeem}. */\r\n    function maxRedeem(address owner) public view virtual returns (uint256) {\r\n        return balanceOf(owner);\r\n    }\r\n}\r\n# 编译指令\r\n# npx hardhat compile\r\n```\r\n# 合约测试\r\n```\r\nconst {ethers,getNamedAccounts,deployments} = require(\"hardhat\");\r\nconst { assert,expect } = require(\"chai\");\r\ndescribe(\"Treasury\",function(){\r\n    let token;\r\n    let treasury;\r\n    let addr1;\r\n    let addr2;\r\n    let firstAccount;\r\n    let secondAccount;\r\n    beforeEach(async function(){\r\n        await deployments.fixture([\"token\",\"Treasury\"]);\r\n        [addr1,addr2]=await ethers.getSigners();\r\n        firstAccount=(await getNamedAccounts()).firstAccount;\r\n        secondAccount=(await getNamedAccounts()).secondAccount;\r\n        //代币合约\r\n        const tokenDeployment = await deployments.get(\"MyToken\");\r\n        token = await ethers.getContractAt(\"MyToken\",tokenDeployment.address);//已经部署的合约交互\r\n        //金库合约\r\n        const treasuryDeployment = await deployments.get(\"ERC4626\");\r\n        treasury = await ethers.getContractAt(\"ERC4626\",treasuryDeployment.address);//已经部署的合约交互\r\n       \r\n    });\r\n    describe(\"金库合约\",function(){\r\n        it(\"金库合约测试\",async function(){\r\n            //获取代币的余额\r\n            const balance = await token.balanceOf(firstAccount);\r\n            console.log(\"余额\",`${ethers.utils.formatEther(balance)} ETH`);\r\n            //将代币授权给ERC4626合约\r\n            await token.approve(treasury.address,balance);\r\n            //将代币存入金库\r\n            await treasury.deposit(balance,firstAccount);\r\n            //获取金库中的代币余额\r\n            const treasuryBalance = await treasury.balanceOf(firstAccount);\r\n            console.log(\"金库余额\",`${ethers.utils.formatEther(treasuryBalance)} ETH`);\r\n            //铸造1000个代币\r\n            await treasury.mint(1000,firstAccount);\r\n            //将代币存入金库\r\n            let treasuryBalance1=await treasury.balanceOf(firstAccount);\r\n            console.log(\"金库余额\",`${ethers.utils.formatEther(treasuryBalance1)} ETH`);\r\n            //提款\r\n            await treasury.withdraw(100,firstAccount,firstAccount);\r\n            let treasuryBalance2=await treasury.balanceOf(firstAccount);\r\n            console.log(\"金库余额提款\",`${ethers.utils.formatEther(treasuryBalance2)} ETH`);\r\n            //赎回\r\n            await treasury.redeem(900,firstAccount,firstAccount);\r\n            let treasuryBalance3=await treasury.balanceOf(firstAccount);\r\n            console.log(\"金库余额\",`${ethers.utils.formatEther(treasuryBalance3)} ETH`);\r\n        });\r\n    })\r\n\r\n})\r\n# 测试指令\r\n# npx hardhat test ./test/xxx.js\r\n```\r\n# 合约部署\r\n```\r\nmodule.exports = async function ({getNamedAccounts,deployments}) {\r\n  const  firstAccount= (await getNamedAccounts()).firstAccount;\r\n  const {deploy,log} = deployments;\r\n  const MyToken=await deployments.get(\"MyToken\");//获取代币合约地址\r\n  const TokenAddress = MyToken.address;//代币合约地址\r\n  const Treasury=await deploy(\"ERC4626\",{\r\n    from:firstAccount,\r\n    args: [TokenAddress,\"Treasury ETH\",\"TBTH\"],//参数 代币地址，name，symble\r\n    log: true,\r\n  })\r\n  console.log('金库合约地址',Treasury.address)\r\n}\r\nmodule.exports.tags = [\"all\", \"Treasury\"];\r\n# 部署指令\r\n# npx hardhat deploy\r\n```\r\n# 总结\r\n以上就是ERC4626标准的保险金库合约从开发到测试再到部署的全部过程，包含了存取款，铸造，转化率等功能；","title":"DeFi基石ERC4626标准实现一个金库合约"},"history":null,"timestamp":1740807433,"version":1}