Files
mini-swap/test/MiniSwapAMM.test.js
NoBey 3faf89e0a1 feat: 完成 Mini Swap DEX AMM 项目开发
- 添加智能合约: TokenA, TokenB, MiniSwapAMM
- 实现 AMM 流动性池功能 (x * y = k 公式)
- 支持添加/移除流动性和代币交换
- 包含完整的测试套件
- 创建 React 前端界面,支持钱包连接
- 添加 Web3 集成和现代化 UI 设计
- 包含部署脚本和完整的项目配置
2025-07-10 01:39:43 +08:00

283 lines
9.9 KiB
JavaScript

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");