const { expect } = require("chai"); const { ethers } = require("hardhat"); describe("MiniSwapAMM", function () { let TokenA, TokenB, MiniSwapAMM; let tokenA, tokenB, miniSwapAMM; let owner, addr1, addr2; const INITIAL_SUPPLY = ethers.parseEther("1000000"); const LIQUIDITY_AMOUNT_A = ethers.parseEther("1000"); const LIQUIDITY_AMOUNT_B = ethers.parseEther("2000"); beforeEach(async function () { [owner, addr1, addr2] = await ethers.getSigners(); // 部署代币合约 TokenA = await ethers.getContractFactory("TokenA"); tokenA = await TokenA.deploy(); await tokenA.waitForDeployment(); TokenB = await ethers.getContractFactory("TokenB"); tokenB = await TokenB.deploy(); await tokenB.waitForDeployment(); // 部署 AMM 合约 MiniSwapAMM = await ethers.getContractFactory("MiniSwapAMM"); miniSwapAMM = await MiniSwapAMM.deploy( await tokenA.getAddress(), await tokenB.getAddress() ); await miniSwapAMM.waitForDeployment(); // 给测试账户铸造代币 await tokenA.connect(addr1).faucet(ethers.parseEther("10000")); await tokenB.connect(addr1).faucet(ethers.parseEther("10000")); await tokenA.connect(addr2).faucet(ethers.parseEther("10000")); await tokenB.connect(addr2).faucet(ethers.parseEther("10000")); }); describe("部署", function () { it("应该正确设置代币地址", async function () { expect(await miniSwapAMM.tokenA()).to.equal(await tokenA.getAddress()); expect(await miniSwapAMM.tokenB()).to.equal(await tokenB.getAddress()); }); it("应该正确设置 LP 代币信息", async function () { expect(await miniSwapAMM.name()).to.equal("MiniSwap LP Token"); expect(await miniSwapAMM.symbol()).to.equal("MSLP"); }); it("初始储备应该为 0", async function () { const [reserveA, reserveB] = await miniSwapAMM.getReserves(); expect(reserveA).to.equal(0); expect(reserveB).to.equal(0); }); }); describe("添加流动性", function () { it("应该能够添加初始流动性", async function () { // 授权 AMM 合约使用代币 await tokenA.connect(addr1).approve(await miniSwapAMM.getAddress(), LIQUIDITY_AMOUNT_A); await tokenB.connect(addr1).approve(await miniSwapAMM.getAddress(), LIQUIDITY_AMOUNT_B); // 添加流动性 const tx = await miniSwapAMM.connect(addr1).addLiquidity( LIQUIDITY_AMOUNT_A, LIQUIDITY_AMOUNT_B, LIQUIDITY_AMOUNT_A, LIQUIDITY_AMOUNT_B ); // 检查事件 await expect(tx).to.emit(miniSwapAMM, "AddLiquidity") .withArgs(addr1.address, LIQUIDITY_AMOUNT_A, LIQUIDITY_AMOUNT_B, anyValue); // 检查储备 const [reserveA, reserveB] = await miniSwapAMM.getReserves(); expect(reserveA).to.equal(LIQUIDITY_AMOUNT_A); expect(reserveB).to.equal(LIQUIDITY_AMOUNT_B); // 检查 LP 代币余额 const lpBalance = await miniSwapAMM.balanceOf(addr1.address); expect(lpBalance).to.be.gt(0); }); it("应该能够添加后续流动性", async function () { // 首先添加初始流动性 await tokenA.connect(addr1).approve(await miniSwapAMM.getAddress(), LIQUIDITY_AMOUNT_A); await tokenB.connect(addr1).approve(await miniSwapAMM.getAddress(), LIQUIDITY_AMOUNT_B); await miniSwapAMM.connect(addr1).addLiquidity( LIQUIDITY_AMOUNT_A, LIQUIDITY_AMOUNT_B, LIQUIDITY_AMOUNT_A, LIQUIDITY_AMOUNT_B ); // 添加更多流动性 const additionalA = ethers.parseEther("500"); const additionalB = ethers.parseEther("1000"); await tokenA.connect(addr2).approve(await miniSwapAMM.getAddress(), additionalA); await tokenB.connect(addr2).approve(await miniSwapAMM.getAddress(), additionalB); await miniSwapAMM.connect(addr2).addLiquidity( additionalA, additionalB, 0, 0 ); // 检查储备增加 const [reserveA, reserveB] = await miniSwapAMM.getReserves(); expect(reserveA).to.equal(LIQUIDITY_AMOUNT_A + additionalA); expect(reserveB).to.equal(LIQUIDITY_AMOUNT_B + additionalB); }); }); describe("移除流动性", function () { beforeEach(async function () { // 添加初始流动性 await tokenA.connect(addr1).approve(await miniSwapAMM.getAddress(), LIQUIDITY_AMOUNT_A); await tokenB.connect(addr1).approve(await miniSwapAMM.getAddress(), LIQUIDITY_AMOUNT_B); await miniSwapAMM.connect(addr1).addLiquidity( LIQUIDITY_AMOUNT_A, LIQUIDITY_AMOUNT_B, LIQUIDITY_AMOUNT_A, LIQUIDITY_AMOUNT_B ); }); it("应该能够移除流动性", async function () { const lpBalance = await miniSwapAMM.balanceOf(addr1.address); const halfLiquidity = lpBalance / 2n; const tx = await miniSwapAMM.connect(addr1).removeLiquidity( halfLiquidity, 0, 0 ); // 检查事件 await expect(tx).to.emit(miniSwapAMM, "RemoveLiquidity"); // 检查 LP 代币余额减少 const newLpBalance = await miniSwapAMM.balanceOf(addr1.address); expect(newLpBalance).to.equal(lpBalance - halfLiquidity); }); it("移除流动性时应该按比例返回代币", async function () { const lpBalance = await miniSwapAMM.balanceOf(addr1.address); const balanceABefore = await tokenA.balanceOf(addr1.address); const balanceBBefore = await tokenB.balanceOf(addr1.address); await miniSwapAMM.connect(addr1).removeLiquidity( lpBalance, 0, 0 ); const balanceAAfter = await tokenA.balanceOf(addr1.address); const balanceBAfter = await tokenB.balanceOf(addr1.address); // 检查代币返还(接近初始流动性金额,因为有最小流动性锁定) expect(balanceAAfter - balanceABefore).to.be.closeTo(LIQUIDITY_AMOUNT_A, ethers.parseEther("1")); expect(balanceBAfter - balanceBBefore).to.be.closeTo(LIQUIDITY_AMOUNT_B, ethers.parseEther("1")); }); }); describe("代币交换", function () { beforeEach(async function () { // 添加流动性 await tokenA.connect(addr1).approve(await miniSwapAMM.getAddress(), LIQUIDITY_AMOUNT_A); await tokenB.connect(addr1).approve(await miniSwapAMM.getAddress(), LIQUIDITY_AMOUNT_B); await miniSwapAMM.connect(addr1).addLiquidity( LIQUIDITY_AMOUNT_A, LIQUIDITY_AMOUNT_B, LIQUIDITY_AMOUNT_A, LIQUIDITY_AMOUNT_B ); }); it("应该能够用 TokenA 交换 TokenB", async function () { const swapAmount = ethers.parseEther("100"); // 计算预期输出 const expectedOutput = await miniSwapAMM.getAmountOut( swapAmount, LIQUIDITY_AMOUNT_A, LIQUIDITY_AMOUNT_B ); await tokenA.connect(addr2).approve(await miniSwapAMM.getAddress(), swapAmount); const balanceBBefore = await tokenB.balanceOf(addr2.address); const tx = await miniSwapAMM.connect(addr2).swapAForB(swapAmount, 0); // 检查事件 await expect(tx).to.emit(miniSwapAMM, "Swap"); const balanceBAfter = await tokenB.balanceOf(addr2.address); const actualOutput = balanceBAfter - balanceBBefore; expect(actualOutput).to.equal(expectedOutput); }); it("应该能够用 TokenB 交换 TokenA", async function () { const swapAmount = ethers.parseEther("200"); const expectedOutput = await miniSwapAMM.getAmountOut( swapAmount, LIQUIDITY_AMOUNT_B, LIQUIDITY_AMOUNT_A ); await tokenB.connect(addr2).approve(await miniSwapAMM.getAddress(), swapAmount); const balanceABefore = await tokenA.balanceOf(addr2.address); await miniSwapAMM.connect(addr2).swapBForA(swapAmount, 0); const balanceAAfter = await tokenA.balanceOf(addr2.address); const actualOutput = balanceAAfter - balanceABefore; expect(actualOutput).to.equal(expectedOutput); }); it("交换应该改变储备比例", async function () { const swapAmount = ethers.parseEther("100"); const [reserveABefore, reserveBBefore] = await miniSwapAMM.getReserves(); await tokenA.connect(addr2).approve(await miniSwapAMM.getAddress(), swapAmount); await miniSwapAMM.connect(addr2).swapAForB(swapAmount, 0); const [reserveAAfter, reserveBAfter] = await miniSwapAMM.getReserves(); expect(reserveAAfter).to.be.gt(reserveABefore); expect(reserveBAfter).to.be.lt(reserveBBefore); }); it("应该拒绝输出不足的交换", async function () { const swapAmount = ethers.parseEther("100"); const minOutput = ethers.parseEther("1000"); // 设置过高的最小输出 await tokenA.connect(addr2).approve(await miniSwapAMM.getAddress(), swapAmount); await expect( miniSwapAMM.connect(addr2).swapAForB(swapAmount, minOutput) ).to.be.revertedWith("MiniSwapAMM: INSUFFICIENT_OUTPUT_AMOUNT"); }); }); describe("价格计算", function () { it("getAmountOut 应该正确计算输出数量", async function () { const amountIn = ethers.parseEther("100"); const reserveIn = ethers.parseEther("1000"); const reserveOut = ethers.parseEther("2000"); const amountOut = await miniSwapAMM.getAmountOut(amountIn, reserveIn, reserveOut); // 手动计算预期值:(100 * 997 * 2000) / (1000 * 1000 + 100 * 997) const amountInWithFee = amountIn * 997n; const numerator = amountInWithFee * reserveOut; const denominator = reserveIn * 1000n + amountInWithFee; const expected = numerator / denominator; expect(amountOut).to.equal(expected); }); it("应该在没有流动性时拒绝计算", async function () { const amountIn = ethers.parseEther("100"); await expect( miniSwapAMM.getAmountOut(amountIn, 0, 1000) ).to.be.revertedWith("MiniSwapAMM: INSUFFICIENT_LIQUIDITY"); }); }); }); // 用于检查任意值的辅助函数 const { anyValue } = require("@nomicfoundation/hardhat-chai-matchers/withArgs");