feat: 添加 MiniSwap CLI 工具及相关文档
- 新增 CLI 工具,支持通过命令行与 MiniSwapAMM 合约交互 - 创建 CLI 使用指南文档,详细说明命令和操作流程 - 更新合约地址,确保与新部署的合约匹配 - 添加快速网络连接测试脚本,验证合约连接和网络状态 - 实现完整测试场景脚本,模拟用户操作和流动性管理
This commit is contained in:
231
scripts/test-scenario.js
Normal file
231
scripts/test-scenario.js
Normal file
@ -0,0 +1,231 @@
|
||||
const { ethers } = require("hardhat");
|
||||
const fs = require("fs");
|
||||
|
||||
// 颜色输出
|
||||
const colors = {
|
||||
reset: '\x1b[0m',
|
||||
red: '\x1b[31m',
|
||||
green: '\x1b[32m',
|
||||
yellow: '\x1b[33m',
|
||||
blue: '\x1b[34m',
|
||||
cyan: '\x1b[36m',
|
||||
white: '\x1b[37m'
|
||||
};
|
||||
|
||||
function log(color, message) {
|
||||
console.log(`${colors[color]}${message}${colors.reset}`);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
log('cyan', '🚀 开始 MiniSwap AMM 完整测试场景');
|
||||
|
||||
// 加载合约地址
|
||||
const addresses = JSON.parse(fs.readFileSync("contract-addresses.json", "utf8"));
|
||||
|
||||
// 获取签名者
|
||||
const signers = await ethers.getSigners();
|
||||
const [deployer, user1, user2] = signers;
|
||||
|
||||
// 连接到合约
|
||||
const TokenA = await ethers.getContractFactory("TokenA");
|
||||
const TokenB = await ethers.getContractFactory("TokenB");
|
||||
const MiniSwapAMM = await ethers.getContractFactory("MiniSwapAMM");
|
||||
|
||||
const tokenA = TokenA.attach(addresses.TokenA);
|
||||
const tokenB = TokenB.attach(addresses.TokenB);
|
||||
const amm = MiniSwapAMM.attach(addresses.MiniSwapAMM);
|
||||
|
||||
log('blue', '📄 合约连接成功');
|
||||
|
||||
// 步骤 1: 显示初始状态
|
||||
log('yellow', '\n=== 步骤 1: 显示初始状态 ===');
|
||||
await showBalances('部署者', deployer, tokenA, tokenB, amm);
|
||||
await showBalances('用户1', user1, tokenA, tokenB, amm);
|
||||
await showReserves(amm);
|
||||
|
||||
// 步骤 2: 为用户铸造代币
|
||||
log('yellow', '\n=== 步骤 2: 为用户铸造代币 ===');
|
||||
const mintAmount = ethers.parseEther("5000");
|
||||
|
||||
await tokenA.connect(user1).faucet(mintAmount);
|
||||
await tokenB.connect(user1).faucet(mintAmount);
|
||||
await tokenA.connect(user2).faucet(mintAmount);
|
||||
await tokenB.connect(user2).faucet(mintAmount);
|
||||
|
||||
log('green', '✅ 为用户1和用户2各铸造了 5000 TKA 和 5000 TKB');
|
||||
|
||||
// 步骤 3: 授权代币
|
||||
log('yellow', '\n=== 步骤 3: 授权代币给 AMM 合约 ===');
|
||||
const approveAmount = ethers.parseEther("10000");
|
||||
const ammAddress = await amm.getAddress();
|
||||
|
||||
await tokenA.connect(user1).approve(ammAddress, approveAmount);
|
||||
await tokenB.connect(user1).approve(ammAddress, approveAmount);
|
||||
await tokenA.connect(user2).approve(ammAddress, approveAmount);
|
||||
await tokenB.connect(user2).approve(ammAddress, approveAmount);
|
||||
|
||||
log('green', '✅ 用户1和用户2已授权代币给 AMM 合约');
|
||||
|
||||
// 步骤 4: 用户1添加初始流动性
|
||||
log('yellow', '\n=== 步骤 4: 用户1添加初始流动性 ===');
|
||||
const liquidityA = ethers.parseEther("1000");
|
||||
const liquidityB = ethers.parseEther("2000"); // 1:2 比例
|
||||
|
||||
const tx1 = await amm.connect(user1).addLiquidity(
|
||||
liquidityA,
|
||||
liquidityB,
|
||||
liquidityA,
|
||||
liquidityB
|
||||
);
|
||||
await tx1.wait();
|
||||
|
||||
log('green', '✅ 用户1添加了 1000 TKA + 2000 TKB 的流动性');
|
||||
|
||||
await showBalances('用户1', user1, tokenA, tokenB, amm);
|
||||
await showReserves(amm);
|
||||
|
||||
// 步骤 5: 用户2进行交换 A -> B
|
||||
log('yellow', '\n=== 步骤 5: 用户2进行交换 (A -> B) ===');
|
||||
const swapAmount = ethers.parseEther("100");
|
||||
|
||||
// 计算预期输出
|
||||
const [reserveA, reserveB] = await amm.getReserves();
|
||||
const expectedOut = await amm.getAmountOut(swapAmount, reserveA, reserveB);
|
||||
|
||||
log('blue', `输入: 100 TKA`);
|
||||
log('blue', `预期输出: ${ethers.formatEther(expectedOut)} TKB`);
|
||||
|
||||
const tx2 = await amm.connect(user2).swapAForB(swapAmount, 0);
|
||||
await tx2.wait();
|
||||
|
||||
log('green', '✅ 用户2完成交换');
|
||||
|
||||
await showBalances('用户2', user2, tokenA, tokenB, amm);
|
||||
await showReserves(amm);
|
||||
|
||||
// 步骤 6: 用户2进行反向交换 B -> A
|
||||
log('yellow', '\n=== 步骤 6: 用户2进行反向交换 (B -> A) ===');
|
||||
const swapBackAmount = ethers.parseEther("50");
|
||||
|
||||
// 重新获取储备量
|
||||
const [newReserveA, newReserveB] = await amm.getReserves();
|
||||
const expectedOutBack = await amm.getAmountOut(swapBackAmount, newReserveB, newReserveA);
|
||||
|
||||
log('blue', `输入: 50 TKB`);
|
||||
log('blue', `预期输出: ${ethers.formatEther(expectedOutBack)} TKA`);
|
||||
|
||||
const tx3 = await amm.connect(user2).swapBForA(swapBackAmount, 0);
|
||||
await tx3.wait();
|
||||
|
||||
log('green', '✅ 用户2完成反向交换');
|
||||
|
||||
await showBalances('用户2', user2, tokenA, tokenB, amm);
|
||||
await showReserves(amm);
|
||||
|
||||
// 步骤 7: 用户1添加更多流动性
|
||||
log('yellow', '\n=== 步骤 7: 用户1添加更多流动性 ===');
|
||||
|
||||
// 根据当前比例添加流动性
|
||||
const [currentReserveA, currentReserveB] = await amm.getReserves();
|
||||
const addAmountA = ethers.parseEther("200");
|
||||
const addAmountB = (addAmountA * currentReserveB) / currentReserveA;
|
||||
|
||||
log('blue', `添加 ${ethers.formatEther(addAmountA)} TKA + ${ethers.formatEther(addAmountB)} TKB`);
|
||||
|
||||
const tx4 = await amm.connect(user1).addLiquidity(
|
||||
addAmountA,
|
||||
addAmountB,
|
||||
addAmountA * 99n / 100n, // 1% 滑点
|
||||
addAmountB * 99n / 100n
|
||||
);
|
||||
await tx4.wait();
|
||||
|
||||
log('green', '✅ 用户1添加了更多流动性');
|
||||
|
||||
await showBalances('用户1', user1, tokenA, tokenB, amm);
|
||||
await showReserves(amm);
|
||||
|
||||
// 步骤 8: 用户1移除部分流动性
|
||||
log('yellow', '\n=== 步骤 8: 用户1移除部分流动性 ===');
|
||||
|
||||
const lpBalance = await amm.balanceOf(user1.address);
|
||||
const removeAmount = lpBalance / 4n; // 移除 1/4 的流动性
|
||||
|
||||
log('blue', `移除 ${ethers.formatEther(removeAmount)} LP 代币`);
|
||||
|
||||
const tx5 = await amm.connect(user1).removeLiquidity(
|
||||
removeAmount,
|
||||
0, // 简化起见,最小值设为0
|
||||
0
|
||||
);
|
||||
await tx5.wait();
|
||||
|
||||
log('green', '✅ 用户1移除了部分流动性');
|
||||
|
||||
await showBalances('用户1', user1, tokenA, tokenB, amm);
|
||||
await showReserves(amm);
|
||||
|
||||
// 步骤 9: 多次小额交换测试价格影响
|
||||
log('yellow', '\n=== 步骤 9: 多次小额交换测试价格影响 ===');
|
||||
|
||||
for (let i = 1; i <= 3; i++) {
|
||||
const smallSwap = ethers.parseEther("10");
|
||||
const tx = await amm.connect(user2).swapAForB(smallSwap, 0);
|
||||
await tx.wait();
|
||||
|
||||
const [rA, rB] = await amm.getReserves();
|
||||
const price = Number(ethers.formatEther(rB)) / Number(ethers.formatEther(rA));
|
||||
log('blue', `第${i}次交换后,价格 (TKB/TKA): ${price.toFixed(6)}`);
|
||||
}
|
||||
|
||||
// 最终状态
|
||||
log('yellow', '\n=== 最终状态 ===');
|
||||
await showBalances('用户1', user1, tokenA, tokenB, amm);
|
||||
await showBalances('用户2', user2, tokenA, tokenB, amm);
|
||||
await showReserves(amm);
|
||||
|
||||
log('cyan', '\n🎉 测试场景完成!');
|
||||
|
||||
// 显示费用收益
|
||||
log('yellow', '\n=== 交易费用分析 ===');
|
||||
const finalReserves = await amm.getReserves();
|
||||
log('white', '由于每次交换都有 0.3% 的手续费');
|
||||
log('white', '这些费用会留在流动性池中,增加流动性提供者的收益');
|
||||
log('white', `最终储备量已经包含了累积的交易费用`);
|
||||
}
|
||||
|
||||
async function showBalances(name, signer, tokenA, tokenB, amm) {
|
||||
const address = signer.address;
|
||||
const ethBalance = await signer.provider.getBalance(address);
|
||||
const tokenABalance = await tokenA.balanceOf(address);
|
||||
const tokenBBalance = await tokenB.balanceOf(address);
|
||||
const lpBalance = await amm.balanceOf(address);
|
||||
|
||||
log('cyan', `\n--- ${name} 余额 (${address.slice(0,8)}...) ---`);
|
||||
log('white', `ETH: ${ethers.formatEther(ethBalance).slice(0,8)}`);
|
||||
log('white', `TKA: ${ethers.formatEther(tokenABalance)}`);
|
||||
log('white', `TKB: ${ethers.formatEther(tokenBBalance)}`);
|
||||
log('white', `LP: ${ethers.formatEther(lpBalance)}`);
|
||||
}
|
||||
|
||||
async function showReserves(amm) {
|
||||
const [reserveA, reserveB] = await amm.getReserves();
|
||||
const totalSupply = await amm.totalSupply();
|
||||
|
||||
log('cyan', '\n--- AMM 储备量 ---');
|
||||
log('white', `TKA 储备: ${ethers.formatEther(reserveA)}`);
|
||||
log('white', `TKB 储备: ${ethers.formatEther(reserveB)}`);
|
||||
log('white', `LP 总量: ${ethers.formatEther(totalSupply)}`);
|
||||
|
||||
if (reserveA > 0 && reserveB > 0) {
|
||||
const price = Number(ethers.formatEther(reserveB)) / Number(ethers.formatEther(reserveA));
|
||||
log('white', `价格 (TKB/TKA): ${price.toFixed(6)}`);
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
.then(() => process.exit(0))
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user