React Native DApp 开发全栈实战·从 0 到 1 系列(收益聚合器-合约部分-补充)

  • 木西
  • 发布于 3小时前
  • 阅读 50

前言本文是对《ReactNativeDApp开发全栈实战·从0到1:收益聚合器合约篇》的补充与勘误,旨在同步更新合约变动与前端调用示例,保持代码与文章一致性。说明主要针对代币合约和收益聚合器的修改,其他合约不变代币合约说明:实现一个多地址授权代币,合约中资产代币和奖励代币雷

前言

本文是对《React Native DApp 开发全栈实战·从 0 到 1:收益聚合器合约篇》的补充与勘误,旨在同步更新合约变动与前端调用示例,保持代码与文章一致性。

说明

主要针对代币合约和收益聚合器的修改,其他合约不变

代币合约

说明:实现一个多地址授权代币,合约中资产代币和奖励代币雷同


// 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   // 👈 部署时一次性给多地址授权
) ERC20(name_, symbol_) {
    // 部署者拥有 DEFAULT_ADMIN_ROLE(可继续授权/撤销)
    _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);

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

    // 给部署者自己先发 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]],//参数 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"];

# 收益聚合器合约
#### 说明:收益聚合器合约不变,只对部署脚本和测试脚本进行调整
#### 部署脚本
* **`特别说明`**:`**部署脚本必须满足「代币先、聚合器后」的硬顺序:给聚合器脚本加上 `dependencies: ['token3', 'token4']` 并让文件名序号小于聚合器即可,hardhat-deploy 会自动按序执行,无需手动调整。`
* **在hardhat项目中deploy/文件夹下也要保证代币文件要在聚合器部署之前**:

例如

deploy/ ├── 01.deploy.token3.js // 多授权资产代币 MyToken3 ├── 02.deploy.token4.js // 多授权奖励代币 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");

//资产
//    const MyAsset=await deploy("MyToken3",{
//     from:getNamedAccount,
//     args: ["MyAsset","MyAsset",[getNamedAccount,secondAccount]],//参数
//     log: true,
// });
// console.log('MyToken 资产合约地址',MyAsset.address)

//奖励代币 
// const MyAward = await deploy("MyToken4",{
//     from:getNamedAccount,
//     args: ["MyAward","MA",[getNamedAccount,secondAccount]],//参数
//     log: true,
// })
// console.log('MyAward 奖励代币合约地址',MyAward.address)
//执行MockV3Aggregator部署合约

const MockV3Aggregator=await deploy("MockV3Aggregator",{ from:getNamedAccount, args: [8,"USDC/USD", 200000000000],//参数 log: true, }) console.log("MockV3Aggregator合约地址:", MockV3Aggregator.address); const YieldAggregator=await deploy("YieldAggregator",{ from:getNamedAccount, args: [MyAsset.address,MyAward.address,MockV3Aggregator.address],//参数 资产地址,奖励地址,喂价 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; // 存入资产(MyToken3) let reward; // 奖励代币(MyToken1 / USDC) let feed; // MockV3Aggregator let owner, alice, bob;

const INITIAL_PRICE = 2000_0000_0000; // 8 位小数,2000 USD/ETH const DEPOSIT_AMOUNT = ethers.parseUnits("100", 18); // 100 个 asset 代币

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

// 必须保证 deployments 文件夹里有对应的脚本:
// 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");          // 存入资产
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); / ===== 现勘 ===== / // 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("部署后初始状态正确", 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("首次存入后资产总量:",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,应得 100 份额

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

});

it("提取后份额与资产减少", 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 调用", 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 对应,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**
# 总结
本文一次性把「多授权代币 → 收益聚合器 → 顺序部署 → 单测/前端调用」全链路补齐:**合约只动部署参数,脚本加 `dependencies` 保顺序,测试用例直接平移前端,mint-approve-deposit/withdraw 一条龙,复制即可跑通。**
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
木西
木西
0x5D5C...2dD7
江湖只有他的大名,没有他的介绍。