React Native DApp 開發(fā)全棧實(shí)戰(zhàn)·從 0 到 1 系列(收益聚合器-合約部分-補(bǔ)充)

前言

本文是對《React Native DApp 開發(fā)全棧實(shí)戰(zhàn)·從 0 到 1:收益聚合器合約篇》的補(bǔ)充與勘誤,旨在同步更新合約變動與前端調(diào)用示例,保持代碼與文章一致性。

說明

主要針對代幣合約和收益聚合器的修改,其他合約不變

代幣合約

說明:實(shí)現(xiàn)一個多地址授權(quán)代幣,合約中資產(chǎn)代幣和獎勵代幣雷同

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {ERC20Burnable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";

contract MyToken3 is ERC20, ERC20Burnable, AccessControl {
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");

    constructor(
        string memory name_,
        string memory symbol_,
        address[] memory initialMinters   // ?? 部署時一次性給多地址授權(quán)
    ) ERC20(name_, symbol_) {
        // 部署者擁有 DEFAULT_ADMIN_ROLE(可繼續(xù)授權(quán)/撤銷)
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);

        // 把 MINTER_ROLE 給所有傳入地址
        for (uint256 i = 0; i < initialMinters.length; ++i) {
            _grantRole(MINTER_ROLE, initialMinters[i]);
        }

        // 給部署者自己先發(fā) 1000 個
        _mint(msg.sender, 1000 * 10 ** decimals());
    }

    // 任何擁有 MINTER_ROLE 的人都能鑄幣
    function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) {
        _mint(to, amount);
    }
}

部署腳本

module.exports = async  ({getNamedAccounts,deployments})=>{
    const getNamedAccount = (await getNamedAccounts()).firstAccount;
    const secondAccount= (await getNamedAccounts()).secondAccount;
    console.log('secondAccount',secondAccount)
    const TokenName = "MyETH";
    const TokenSymbol = "MYETH";
    const {deploy,log} = deployments;
    const TokenC=await deploy("MyToken3",{
        from:getNamedAccount,
        args: [TokenName,TokenSymbol,[getNamedAccount,secondAccount]],//參數(shù) name,symblo,[Owner1,Owner1]
        log: true,
    })
    // await hre.run("verify:verify", {
    //     address: TokenC.address,
    //     constructorArguments: [TokenName, TokenSymbol],
    //     });
    console.log('MYTOKEN3合約地址 多Owner合約',TokenC.address)
}
module.exports.tags = ["all", "token3"];

收益聚合器合約

說明:收益聚合器合約不變,只對部署腳本和測試腳本進(jìn)行調(diào)整

部署腳本

  • 特別說明**部署腳本必須滿足「代幣先、聚合器后」的硬順序:給聚合器腳本加上dependencies: ['token3', 'token4']并讓文件名序號小于聚合器即可,hardhat-deploy 會自動按序執(zhí)行,無需手動調(diào)整。
  • 在hardhat項目中deploy/文件夾下也要保證代幣文件要在聚合器部署之前:
# 例如
deploy/
├── 01.deploy.token3.js              // 多授權(quán)資產(chǎn)代幣 MyToken3
├── 02.deploy.token4.js              // 多授權(quán)獎勵代幣 MyToken4
├── 03.deploy.MockV3Aggregator.js    // ETH/USD 喂價 Mock
└── 04.deploy.YieldAggregator.js     // 收益聚合器(依賴 01-03)
  • 部署腳本
module.exports = async  ({getNamedAccounts,deployments})=>{
    const getNamedAccount = (await getNamedAccounts()).firstAccount;
    const secondAccount= (await getNamedAccounts()).secondAccount;
    console.log('secondAccount',secondAccount)
    const {deploy,log} = deployments;
    const MyAsset  = await deployments.get("MyToken3");
    const MyAward = await deployments.get("MyToken4");

    //資產(chǎn)
    //    const MyAsset=await deploy("MyToken3",{
    //     from:getNamedAccount,
    //     args: ["MyAsset","MyAsset",[getNamedAccount,secondAccount]],//參數(shù)
    //     log: true,
    // });
    // console.log('MyToken 資產(chǎn)合約地址',MyAsset.address)
    
    //獎勵代幣 
    // const MyAward = await deploy("MyToken4",{
    //     from:getNamedAccount,
    //     args: ["MyAward","MA",[getNamedAccount,secondAccount]],//參數(shù)
    //     log: true,
    // })
    // console.log('MyAward 獎勵代幣合約地址',MyAward.address)
    //執(zhí)行MockV3Aggregator部署合約
  const MockV3Aggregator=await deploy("MockV3Aggregator",{
        from:getNamedAccount,
        args: [8,"USDC/USD", 200000000000],//參數(shù)
        log: true,
    })
  console.log("MockV3Aggregator合約地址:", MockV3Aggregator.address);
    const YieldAggregator=await deploy("YieldAggregator",{
        from:getNamedAccount,
        args: [MyAsset.address,MyAward.address,MockV3Aggregator.address],//參數(shù) 資產(chǎn)地址,獎勵地址,喂價
        log: true,
    })
    // await hre.run("verify:verify", {
    //     address: TokenC.address,
    //     constructorArguments: [TokenName, TokenSymbol],
    //     });
    console.log('YieldAggregator 聚合器合約地址',YieldAggregator.address)
}
module.exports.tags = ["all", "YieldAggregator"];

測試腳本

const { expect } = require("chai");
const { ethers, deployments } = require("hardhat");

describe("YieldAggregator", function () {
  let yieldAg;          // 被測合約
  let asset;            // 存入資產(chǎn)(MyToken3)
  let reward;           // 獎勵代幣(MyToken1 / USDC)
  let feed;             // MockV3Aggregator
  let owner, alice, bob;

  const INITIAL_PRICE = 2000_0000_0000;          // 8 位小數(shù),2000 USD/ETH
  const DEPOSIT_AMOUNT = ethers.parseUnits("100", 18); // 100 個 asset 代幣

  beforeEach(async () => {
    [owner, alice, bob] = await ethers.getSigners();

    // 必須保證 deployments 文件夾里有對應(yīng)的腳本:
    // 01-deploy-tokens.js   02-deploy-mock.js   03-deploy-yield.js
    await deployments.fixture(["token3", "token4", "MockV3Aggregator", "YieldAggregator"]);

    const a = await deployments.get("MyToken3");          // 存入資產(chǎn)
    const b = await deployments.get("MyToken4");          // 獎勵代幣(USDC)
    const c = await deployments.get("MockV3Aggregator");
    const d = await deployments.get("YieldAggregator");

    asset   = await ethers.getContractAt("MyToken3", a.address);
    reward  = await ethers.getContractAt("MyToken4", b.address);
    feed    = await ethers.getContractAt("MockV3Aggregator", c.address);
    yieldAg = await ethers.getContractAt("YieldAggregator", d.address);


//     console.log("=== 地址核對 ===");
// console.log("asset  :", await asset.getAddress());
// console.log("reward :", await reward.getAddress());
// console.log("yieldAg:", await yieldAg.getAddress());
// console.log("asset in yieldAg:", await yieldAg.asset());
// console.log("reward in yieldAg:", await yieldAg.rewardToken());
    
  });

  /* ------------------  helper  ------------------ */
  async function mintAndApprove(user, amount) {
    await asset.mint(user.address, amount);
   /* ===== 現(xiàn)勘 ===== */
// console.log("user地址        :", user.address);
// console.log("yieldAg地址     :", await yieldAg.getAddress());
// console.log("approve前額度   :", await asset.allowance(user.address, await yieldAg.getAddress()));
await asset.connect(user).approve(await yieldAg.getAddress(), amount);
// console.log("approve后額度   :", await asset.allowance(user.address, await yieldAg.getAddress()));

  }
// async function mintAndApprove(user, amount) {
//   const yieldAddr = await yieldAg.getAddress();
//   console.log("asset 地址:", await asset.getAddress());
//   console.log("yield 地址:", yieldAddr);
//   console.log("user 地址 :", user.address);

//   await asset.mint(user.address, amount);

//   const allowanceBefore = await asset.allowance(user.address, yieldAddr);
//   console.log("approve 前 allowance:", allowanceBefore.toString());

//   const tx = await asset.connect(user).approve(yieldAddr, amount);
//   await tx.wait(); // 確保上鏈

//   const allowanceAfter = await asset.allowance(user.address, yieldAddr);
//   console.log("approve 后 allowance:", allowanceAfter.toString());
// }

  /* ------------------  測試用例  ------------------ */
  // it("部署后初始狀態(tài)正確", async () => {
  //   console.log(await yieldAg.asset())
  //   console.log(asset.target);
  //   console.log(await yieldAg.rewardToken())
  //   console.log(reward.target);
  //  console.log(await yieldAg.priceFeed())
  //  console.log(feed.target);
  //  console.log(await yieldAg.totalShares());
  //   console.log(await yieldAg.totalAssetsDeposited());
  // });

  it("首次存入正確鑄造份額", async () => {
    console.log("================")
    await mintAndApprove(alice, DEPOSIT_AMOUNT);
    await yieldAg.connect(alice).deposit(DEPOSIT_AMOUNT)

    //   .to.emit(yieldAg, "Deposit")
    //   .withArgs(alice.address, DEPOSIT_AMOUNT, DEPOSIT_AMOUNT); // 1:1

    console.log("首次存入后用戶份額:",await yieldAg.shares(alice.address))
    // .to.eq(DEPOSIT_AMOUNT);
    console.log("首次存入后份額總量:",await yieldAg.totalShares())
    // .to.eq(DEPOSIT_AMOUNT);
    console.log("首次存入后資產(chǎn)總量:",await yieldAg.totalAssetsDeposited())
    // .to.eq(DEPOSIT_AMOUNT);
  });

  it("二次存入按比例鑄造份額", async () => {
    await mintAndApprove(alice, DEPOSIT_AMOUNT);
    await mintAndApprove(bob,  DEPOSIT_AMOUNT);

    await yieldAg.connect(alice).deposit(DEPOSIT_AMOUNT); // 總量 100,份額 100
    await yieldAg.connect(bob).deposit(DEPOSIT_AMOUNT);   // 總量 200,應(yīng)得 100 份額

    console.log(await yieldAg.shares(bob.address))
    // .to.eq(DEPOSIT_AMOUNT);
    console.log(await yieldAg.totalShares())
    // .to.eq(DEPOSIT_AMOUNT * 2n);
  });

  it("提取后份額與資產(chǎn)減少", async () => {
    await mintAndApprove(alice, DEPOSIT_AMOUNT);
    await yieldAg.connect(alice).deposit(DEPOSIT_AMOUNT);

    const withdrawShares = DEPOSIT_AMOUNT / 2n;
    const expectAssets   = DEPOSIT_AMOUNT / 2n;

    await expect(yieldAg.connect(alice).withdraw(withdrawShares))
      .to.emit(yieldAg, "Withdraw")
      .withArgs(alice.address, expectAssets, withdrawShares);

    expect(await yieldAg.shares(alice.address)).to.eq(withdrawShares);
    expect(await yieldAg.totalShares()).to.eq(withdrawShares);
    expect(await yieldAg.totalAssetsDeposited()).to.eq(withdrawShares);
  });

  it("無法提取超過自身份額", async () => {
    await mintAndApprove(alice, DEPOSIT_AMOUNT);
    await yieldAg.connect(alice).deposit(DEPOSIT_AMOUNT);

    await expect(
      yieldAg.connect(alice).withdraw(DEPOSIT_AMOUNT + 1n)
    ).to.be.revertedWith("Not enough shares");
  });

  it("rescue 只能 owner 調(diào)用", async () => {
    const rescueAmount = ethers.parseUnits("10", 18);
    await asset.mint(yieldAg.target, rescueAmount);

    // owner 可以 rescue
    await expect(() =>
      yieldAg.connect(owner).rescue(asset.target, rescueAmount)
    ).to.changeTokenBalance(asset, owner, rescueAmount);

    // alice 不能 rescue
    await expect(
      yieldAg.connect(alice).rescue(asset.target, 1n)
    ).to.be.reverted;
  });

  it("getETHPrice 返回 Mock 價格", async () => {
    expect(await yieldAg.getETHPrice()).to.eq(INITIAL_PRICE);
  });

  it("getUserAssetValue 計算正確", async () => {
    await mintAndApprove(alice, DEPOSIT_AMOUNT);
    await yieldAg.connect(alice).deposit(DEPOSIT_AMOUNT);

    // 1:1 對應(yīng),USDC 視為 1 USD
    expect(await yieldAg.getUserAssetValue(alice.address)).to.eq(DEPOSIT_AMOUNT);
  });
});

常用指令

  • 編譯npx hardhat compile
  • 部署npx hardhat deploy --tags xxx,xxx
  • 測試npx hardhat test ./test/xxx.js

總結(jié)

本文一次性把「多授權(quán)代幣 → 收益聚合器 → 順序部署 → 單測/前端調(diào)用」全鏈路補(bǔ)齊:合約只動部署參數(shù),腳本加 dependencies 保順序,測試用例直接平移前端,mint-approve-deposit/withdraw 一條龍,復(fù)制即可跑通。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容