import { ethers } from "hardhat"; import { Lending } from "../../typechain-types"; /** * 购买清算抵押品脚本 * * 环境变量: * - LENDING_ADDRESS: Lending 合约地址 * - ASSET_ADDRESS: 抵押品资产地址 * - BASE_AMOUNT (可选): 愿意支付的最大金额,默认 100 * - SLIPPAGE (可选): 滑点容忍度 (1-5),默认 2 */ async function main() { // ==================== 配置 ==================== const LENDING_ADDRESS = process.env.LENDING_ADDRESS; const ASSET_ADDRESS = process.env.ASSET_ADDRESS; const BASE_AMOUNT_INPUT = process.env.BASE_AMOUNT || "100"; const SLIPPAGE_PERCENT = parseInt(process.env.SLIPPAGE || "2"); // 参数验证 if (!LENDING_ADDRESS || LENDING_ADDRESS === "0x...") { throw new Error("❌ 请设置 LENDING_ADDRESS 环境变量"); } if (!ASSET_ADDRESS || ASSET_ADDRESS === "0x...") { throw new Error("❌ 请设置 ASSET_ADDRESS 环境变量"); } const SLIPPAGE = SLIPPAGE_PERCENT / 100; // 转换为小数 console.log("==================== 购买清算抵押品 ===================="); console.log(`Lending 合约: ${LENDING_ADDRESS}`); console.log(`抵押品地址: ${ASSET_ADDRESS}`); console.log(`最大支付金额: ${BASE_AMOUNT_INPUT} USDC`); console.log(`滑点容忍度: ${SLIPPAGE_PERCENT}%\n`); // ==================== 初始化 ==================== const lending = await ethers.getContractAt("Lending", LENDING_ADDRESS) as unknown as Lending; const [buyer] = await ethers.getSigners(); const baseToken = await lending.baseToken(); const base = await ethers.getContractAt("IERC20Metadata", baseToken); const baseDecimals = await base.decimals(); const BASE_AMOUNT = ethers.parseUnits(BASE_AMOUNT_INPUT, baseDecimals); console.log(`买家地址: ${buyer.address}\n`); // ==================== 前置检查 ==================== console.log("📊 检查系统状态..."); // 1. 检查储备金状态 const reserves = await lending.getReserves(); const targetReserves = await lending.targetReserves(); console.log(`✓ 当前储备金: ${ethers.formatUnits(reserves, baseDecimals)} USDC`); console.log(`✓ 目标储备金: ${ethers.formatUnits(targetReserves, baseDecimals)} USDC`); if (reserves >= targetReserves) { throw new Error("❌ 储备金充足,当前无法购买抵押品"); } // 2. 检查抵押品储备 const collateralReserve = await lending.getCollateralReserves(ASSET_ADDRESS); if (collateralReserve === 0n) { throw new Error("❌ 该抵押品储备为空,无法购买"); } const asset = await ethers.getContractAt("IERC20Metadata", ASSET_ADDRESS); const assetDecimals = await asset.decimals(); console.log(`✓ 抵押品储备: ${ethers.formatUnits(collateralReserve, assetDecimals)} 代币\n`); // 3. 检查买家余额 const buyerBalance = await base.balanceOf(buyer.address); console.log(`💰 买家余额: ${ethers.formatUnits(buyerBalance, baseDecimals)} USDC`); if (buyerBalance < BASE_AMOUNT) { throw new Error(`❌ 余额不足。需要: ${ethers.formatUnits(BASE_AMOUNT, baseDecimals)} USDC,当前: ${ethers.formatUnits(buyerBalance, baseDecimals)} USDC`); } // ==================== 获取购买信息 ==================== console.log("\n📋 计算购买详情..."); const info = await getBuyCollateralInfo(lending, ASSET_ADDRESS, BASE_AMOUNT, SLIPPAGE); console.log(`✓ 预期购买: ${ethers.formatUnits(info.expectedAmount, assetDecimals)} 代币`); console.log(`✓ 实际购买: ${ethers.formatUnits(info.actualAmount, assetDecimals)} 代币`); console.log(`✓ 最小接受: ${ethers.formatUnits(info.minAmount, assetDecimals)} 代币`); if (info.isLimited) { console.log(`\n⚠️ 储备不足,购买量已调整:`); console.log(` 原计划支付: ${ethers.formatUnits(info.baseAmount, baseDecimals)} USDC`); console.log(` 实际约支付: ${ethers.formatUnits(info.actualBaseAmount, baseDecimals)} USDC`); console.log(` 节省约: ${ethers.formatUnits(info.baseAmount - info.actualBaseAmount, baseDecimals)} USDC`); } // ==================== 授权检查 ==================== console.log("\n🔐 检查授权..."); const allowance = await base.allowance(buyer.address, LENDING_ADDRESS); if (allowance < BASE_AMOUNT) { console.log(`当前授权: ${ethers.formatUnits(allowance, baseDecimals)} USDC (不足)`); console.log("正在授权..."); const approveTx = await base.approve(LENDING_ADDRESS, ethers.MaxUint256); await approveTx.wait(); console.log("✅ 授权成功"); } else { console.log("✓ 授权充足"); } // ==================== 执行购买 ==================== console.log("\n💸 执行购买交易..."); const tx = await lending.buyCollateral( ASSET_ADDRESS, info.minAmount, BASE_AMOUNT, buyer.address ); console.log(`交易已提交: ${tx.hash}`); console.log("等待确认..."); const receipt = await tx.wait(); console.log(`✅ 交易确认! Gas 消耗: ${receipt?.gasUsed.toString()}\n`); // ==================== 解析结果 ==================== const buyEvent = receipt?.logs.find((log: any) => { try { return lending.interface.parseLog(log)?.name === "BuyCollateral"; } catch { return false; } }); if (buyEvent) { const parsedEvent = lending.interface.parseLog(buyEvent); const paidAmount = parsedEvent?.args.baseAmount; const receivedAmount = parsedEvent?.args.collateralAmount; console.log("==================== 交易结果 ===================="); console.log(`✓ 实际支付: ${ethers.formatUnits(paidAmount, baseDecimals)} USDC`); console.log(`✓ 实际获得: ${ethers.formatUnits(receivedAmount, assetDecimals)} 代币`); console.log(`✓ 购买者: ${parsedEvent?.args.buyer}`); console.log(`✓ 接收地址: ${buyer.address}`); // 计算实际单价和折扣信息 // 实际单价 = 支付金额 / 获得数量 const actualPricePerToken = (paidAmount * ethers.parseUnits("1", assetDecimals)) / receivedAmount; // 计算正常市场价(使用 quoteCollateral 反推) // 如果用相同的 baseAmount 在市场价购买,能买到多少代币 const marketAmount = await lending.quoteCollateral(ASSET_ADDRESS, paidAmount); console.log(`\n💰 折扣购买说明:`); console.log(`✓ 实际单价: ${ethers.formatUnits(actualPricePerToken, baseDecimals)} USDC/代币`); // 只有当实际购买量大于市场价购买量时才显示折扣 if (receivedAmount > marketAmount) { const discount = ((receivedAmount - marketAmount) * 10000n) / marketAmount; const saved = actualPricePerToken * marketAmount / ethers.parseUnits("1", assetDecimals) - paidAmount; console.log(`✓ 市场价可购买: ${ethers.formatUnits(marketAmount, assetDecimals)} 代币`); console.log(`✓ 折扣多得: ${ethers.formatUnits(receivedAmount - marketAmount, assetDecimals)} 代币 (${Number(discount) / 100}%)`); console.log(`✓ 相当于节省: ${ethers.formatUnits(saved, baseDecimals)} USDC`); } else { console.log(`ℹ️ 这是清算抵押品的折扣购买,价格低于市场价`); } console.log("==================================================="); } // ==================== 更新后状态 ==================== console.log("\n📊 购买后状态:"); const newBalance = await base.balanceOf(buyer.address); const newAssetBalance = await asset.balanceOf(buyer.address); console.log(`买家 USDC 余额: ${ethers.formatUnits(newBalance, baseDecimals)} USDC`); console.log(`买家抵押品余额: ${ethers.formatUnits(newAssetBalance, assetDecimals)} 代币`); console.log("\n✅ 购买完成!"); } main() .then(() => process.exit(0)) .catch((error) => { console.error("\n❌ 执行失败:", error.message || error); process.exit(1); }); /** * 获取详细的购买信息 */ export async function getBuyCollateralInfo( lendingContract: Lending, asset: string, baseAmount: bigint, slippageTolerance: number = 0.01 ) { const expectedAmount = await lendingContract.quoteCollateral(asset, baseAmount); const availableReserve = await lendingContract.getCollateralReserves(asset); const actualAmount = expectedAmount < availableReserve ? expectedAmount : availableReserve; const slippageMultiplier = BigInt(Math.floor((1 - slippageTolerance) * 1e18)); const minAmount = (actualAmount * slippageMultiplier) / BigInt(1e18); // 估算实际支付金额(基于实际购买量) const actualBaseAmount = actualAmount < expectedAmount ? (baseAmount * actualAmount) / expectedAmount // 按比例计算 : baseAmount; return { expectedAmount, // 理想情况下可购买的数量 availableReserve, // 协议可用储备 actualAmount, // 实际可购买的数量(限制后) minAmount, // 应用滑点保护后的最小值 baseAmount, // 用户愿意支付的最大金额 actualBaseAmount, // 实际需要支付的金额(可能更少) isLimited: actualAmount < expectedAmount, // 是否受储备限制 }; }