Files
assetxContracts/scripts/utils/buyCollateral.ts

212 lines
9.0 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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, // 是否受储备限制
};
}