import "dotenv/config" import { ethers } from "ethers" import * as fs from "fs" import * as path from "path" import { StandardMerkleTree } from "@openzeppelin/merkle-tree" import { MockUSD1__factory, BasicERC20__factory, Launchpad__factory } from "../typechain-types/factories/contracts" import type { MockUSD1, BasicERC20, Launchpad } from "../typechain-types/contracts" async function main() { // 获取环境配置 const environment = process.env.ENVIRONMENT || "bsctest" const rpcUrl = process.env.RPC_URL || "https://data-seed-prebsc-1-s1.binance.org:8545/" const outputDir = process.env.OUTPUT_DIR || "./output/bsctest" console.log(`🚀 第3步:初始化销售并铸造代币...`) console.log(`🌍 环境: ${environment}`) console.log(`🔗 RPC: ${rpcUrl}`) console.log(`📁 输出目录: ${outputDir}`) // 检查环境变量 if (!process.env.PRIVATE_KEY) { console.error("❌ 需要设置 PRIVATE_KEY 环境变量") process.exit(1) } // 从之前的步骤加载合约地址 const contractAddressesPath = path.join(outputDir, "contract-addresses.json") if (!fs.existsSync(contractAddressesPath)) { console.error(`❌ 未找到 ${contractAddressesPath} 文件。请先运行第1步。`) process.exit(1) } const contractAddresses = JSON.parse(fs.readFileSync(contractAddressesPath, "utf8")) console.log("📋 已从之前的步骤加载合约地址") // 设置提供者和部署者 const provider = new ethers.JsonRpcProvider(rpcUrl) const deployer = new ethers.Wallet(process.env.PRIVATE_KEY, provider) // 获取当前nonce const currentNonce = await provider.getTransactionCount(deployer.address) console.log("📋 当前nonce:", currentNonce) // 连接到已部署的合约 const mockUSD1 = MockUSD1__factory.connect(contractAddresses.mockUSD1, deployer) const saleToken = BasicERC20__factory.connect(contractAddresses.saleToken, deployer) const launchpad = Launchpad__factory.connect(contractAddresses.launchpad, deployer) // 检查并确保有足够的销售代币(幂等操作) console.log("\n💰 检查销售代币余额...") const totalTokensForSale = ethers.parseEther("50000") // 5万个代币(对应50K USDT目标) // 检查部署者余额 const deployerBalance = await saleToken.balanceOf(deployer.address) console.log("📊 部署者当前余额:", ethers.formatEther(deployerBalance), "代币") // 检查Launchpad合约余额 const launchpadBalance = await saleToken.balanceOf(contractAddresses.launchpad) console.log("📊 Launchpad合约当前余额:", ethers.formatEther(launchpadBalance), "代币") // 检查部署者对Launchpad的授权 const allowance = await saleToken.allowance(deployer.address, contractAddresses.launchpad) console.log("📊 当前授权额度:", ethers.formatEther(allowance), "代币") // 计算需要的代币数量 const totalNeeded = totalTokensForSale const currentTotal = deployerBalance + launchpadBalance console.log("📊 需要总代币数:", ethers.formatEther(totalNeeded), "代币") console.log("📊 当前总代币数:", ethers.formatEther(currentTotal), "代币") // 如果部署者余额不足,需要铸造 let nonce = currentNonce if (deployerBalance < totalNeeded) { const needToMint = totalNeeded - deployerBalance console.log("🔄 需要向部署者铸造", ethers.formatEther(needToMint), "个代币") await saleToken.connect(deployer).mint(deployer.address, needToMint, { nonce: nonce++ }) console.log("✅ 已向部署者铸造", ethers.formatEther(needToMint), "个销售代币") } else { console.log("✅ 部署者已有足够的代币余额") } // 如果授权不足,需要批准 if (allowance < totalNeeded) { const needToApprove = totalNeeded - allowance console.log("🔄 需要批准", ethers.formatEther(needToApprove), "个代币给Launchpad") await saleToken.connect(deployer).approve(contractAddresses.launchpad, needToApprove, { nonce: nonce++ }) console.log("✅ 已批准", ethers.formatEther(needToApprove), "个代币给Launchpad") } else { console.log("✅ Launchpad已有足够的代币授权") } // 从JSON文件加载测试账户 const testAccountsPath = "./scripts/testAccount/BLaunchpadTest.json" if (!fs.existsSync(testAccountsPath)) { console.error("❌ 未找到测试账户文件:", testAccountsPath) process.exit(1) } const testAccounts = JSON.parse(fs.readFileSync(testAccountsPath, "utf8")) console.log("📋 已从", testAccountsPath, "加载", testAccounts.length, "个测试账户") // 从加载的账户创建测试用户(所有20个账户) const testUsers = testAccounts.map((account: any) => new ethers.Wallet(account.privateKey, provider)) // 基于第2步的USD1分发情况创建销售参数 const now = Math.floor(Date.now() / 1000) const startTime = now + 60 // 1分钟后开始 const endTime = now + 3600 // 1小时后结束(更短的时间用于测试) const paymentTokenPrice = ethers.parseEther("1") // 1 USDT 每代币 const feeBps = 500 // 5% 手续费 // 设置一个固定目标,如果有5个以上用户参与就会超过 // 每个用户可以贡献最多10,000 USD1(他们的全部USD1余额) // 5个用户 × 10,000 USD1 = 50,000 USD1 = 50,000 USD1 目标(刚好达到) // 6个用户 × 10,000 USD1 = 60,000 USD1 > 50,000 USD1 目标(超募) // 直接设置目标为 50,000 USD1(带 18 位小数) const contributionTarget = ethers.parseEther("50000") // 50K USDT 目标 console.log("\n🎯 目标设置为 50K USDT") console.log("📊 如果有5个以上用户参与 (5 × 10K = 50K),销售将达到目标") console.log("📊 如果有6个以上用户参与 (6 × 10K = 60K),销售将超额认购") console.log("📊 如果少于5个用户参与,销售将认购不足") console.log("\n📋 销售参数:") console.log("- 开始时间:", new Date(startTime * 1000).toLocaleString()) console.log("- 结束时间:", new Date(endTime * 1000).toLocaleString()) console.log("- 持续时间:", Math.floor((endTime - startTime) / 3600), "小时") console.log("- 目标:", ethers.formatEther(contributionTarget), "USDT") console.log("- 价格:", ethers.formatEther(paymentTokenPrice), "USDT 每代币") console.log("- 手续费:", feeBps / 100, "%") console.log("- 销售代币总数:", ethers.formatEther(totalTokensForSale)) // 验证销售参数 if (startTime >= endTime) { console.error("❌ 错误:开始时间必须在结束时间之前") process.exit(1) } if (contributionTarget <= 0) { console.error("❌ 错误:贡献目标必须大于0") process.exit(1) } if (paymentTokenPrice <= 0) { console.error("❌ 错误:支付代币价格必须大于0") process.exit(1) } // 创建销售(幂等操作) console.log("\n🎯 检查并创建销售...") try { // 尝试获取销售详情,如果成功说明销售已存在 const saleDetails = await launchpad.getSaleDetails() const currentSaleToken = saleDetails[0] const currentTotalTokens = saleDetails[1] const currentContribution = saleDetails[2] // totalContributionAmount // 检查是否已经调用过createSale(销售代币地址不为0) if (currentSaleToken === ethers.ZeroAddress || currentTotalTokens === BigInt(0)) { console.log("📋 检测到未初始化的销售,直接创建...") console.log("📦 创建新销售...") console.log("📋 参数详情:") console.log(` - 销售代币: ${contractAddresses.saleToken}`) console.log(` - 总代币数: ${ethers.formatEther(totalTokensForSale)}`) console.log(` - 贡献目标: ${ethers.formatEther(contributionTarget)}`) console.log(` - 开始时间: ${startTime} (${new Date(startTime * 1000).toLocaleString()})`) console.log(` - 结束时间: ${endTime} (${new Date(endTime * 1000).toLocaleString()})`) console.log(` - 支付代币: ${contractAddresses.mockUSD1}`) console.log(` - 价格: ${ethers.formatEther(paymentTokenPrice)}`) console.log(` - 手续费: ${feeBps} bps`) try { const tx = await launchpad.connect(deployer).createSale( contractAddresses.saleToken, // _token totalTokensForSale, // _totalTokensForSale contributionTarget, // _contributionTarget startTime, // _startTime endTime, // _endTime contractAddresses.mockUSD1, // _paymentToken (USD1) paymentTokenPrice, // _price feeBps, // _feeBps { nonce: nonce++ } ) console.log("📋 交易哈希:", tx.hash) const receipt = await tx.wait() console.log("📋 交易状态:", receipt?.status === 1 ? "✅ 成功" : "❌ 失败") console.log("✅ 销售创建成功") } catch (error) { console.error("❌ 创建销售失败:", error) throw error } } else { // 销售已存在,检查参数一致性 console.log("📋 销售已存在,检查参数...") console.log("- 销售代币地址:", currentSaleToken) console.log("- 总代币数量:", ethers.formatEther(currentTotalTokens)) console.log("- 当前贡献:", ethers.formatEther(currentContribution)) // 检查销售参数是否与预期一致 const expectedSaleToken = contractAddresses.saleToken const expectedTotalTokens = totalTokensForSale const expectedTarget = contributionTarget const expectedStartTime = BigInt(startTime) const expectedEndTime = BigInt(endTime) const expectedPaymentToken = contractAddresses.mockUSD1 const expectedPrice = paymentTokenPrice const expectedFeeBps = BigInt(feeBps) // 获取当前销售参数 const currentTarget = BigInt(saleDetails[3]) // contributionTarget const currentStartTime = BigInt(saleDetails[4]) // startTime const currentEndTime = BigInt(saleDetails[5]) // endTime const currentMerkleRoot = saleDetails[6] // merkleRoot const currentPaymentToken = saleDetails[7] // paymentTokenAddress const currentPrice = BigInt(saleDetails[8]) // paymentTokenPrice const currentFeeBps = BigInt(saleDetails[9]) // feeBps console.log("\n📊 参数对比:") console.log("- 销售代币地址:", currentSaleToken === expectedSaleToken ? "✅ 一致" : "❌ 不一致") console.log("- 总代币数量:", currentTotalTokens === expectedTotalTokens ? "✅ 一致" : "❌ 不一致") console.log("- 贡献目标:", currentTarget === expectedTarget ? "✅ 一致" : "❌ 不一致") // 时间容差检查(1小时 = 3600秒) const timeTolerance = BigInt(3600) const startTimeDiff = currentStartTime > expectedStartTime ? currentStartTime - expectedStartTime : expectedStartTime - currentStartTime const endTimeDiff = currentEndTime > expectedEndTime ? currentEndTime - expectedEndTime : expectedEndTime - currentEndTime const startTimeConsistent = startTimeDiff <= timeTolerance const endTimeConsistent = endTimeDiff <= timeTolerance console.log("- 开始时间:", startTimeConsistent ? "✅ 一致" : "❌ 不一致") if (!startTimeConsistent) { console.log(` 当前: ${new Date(Number(currentStartTime) * 1000).toLocaleString()}`) console.log(` 预期: ${new Date(Number(expectedStartTime) * 1000).toLocaleString()}`) console.log(` 差异: ${Number(startTimeDiff)} 秒`) } console.log("- 结束时间:", endTimeConsistent ? "✅ 一致" : "❌ 不一致") if (!endTimeConsistent) { console.log(` 当前: ${new Date(Number(currentEndTime) * 1000).toLocaleString()}`) console.log(` 预期: ${new Date(Number(expectedEndTime) * 1000).toLocaleString()}`) console.log(` 差异: ${Number(endTimeDiff)} 秒`) } console.log("- 支付代币:", currentPaymentToken === expectedPaymentToken ? "✅ 一致" : "❌ 不一致") console.log("- 价格:", currentPrice === expectedPrice ? "✅ 一致" : "❌ 不一致") console.log("- 手续费:", currentFeeBps === expectedFeeBps ? "✅ 一致" : "❌ 不一致") // 检查是否有任何参数不一致 const isConsistent = currentSaleToken === expectedSaleToken && currentTotalTokens === expectedTotalTokens && currentTarget === expectedTarget && startTimeConsistent && endTimeConsistent && currentPaymentToken === expectedPaymentToken && currentPrice === expectedPrice && currentFeeBps === expectedFeeBps if (isConsistent) { console.log("\n✅ 销售参数与预期一致,无需重置") } else { console.log("\n⚠️ 销售参数与预期不一致") // 检查是否有贡献,如果有贡献则不能重置 if (currentContribution > 0) { console.error("❌ 错误:销售已有贡献,无法重置。请手动处理或等待销售结束") console.error("当前贡献:", ethers.formatEther(currentContribution), "USD1") process.exit(1) } // 检查是否是严重不一致(销售代币地址为0) const isSeverelyInconsistent = currentSaleToken === ethers.ZeroAddress || currentTotalTokens === BigInt(0) || currentPaymentToken === ethers.ZeroAddress if (isSeverelyInconsistent) { console.log("🔄 检测到严重不一致,尝试重新创建销售...") // 尝试重新创建销售(这可能会失败,但值得尝试) try { console.log("📦 重新创建销售...") await launchpad.connect(deployer).createSale( contractAddresses.saleToken, // _token totalTokensForSale, // _totalTokensForSale contributionTarget, // _contributionTarget startTime, // _startTime endTime, // _endTime contractAddresses.mockUSD1, // _paymentToken (USD1) paymentTokenPrice, // _price feeBps, // _feeBps { gasLimit: 10000000, gasPrice: ethers.parseUnits("0.1", "gwei"), nonce: nonce++ } ) console.log("✅ 销售重新创建成功") } catch (createError) { console.error("❌ 重新创建销售失败:", createError) console.log("💡 提示:请手动处理不一致的参数,或重新部署合约") console.log("💡 当前销售将继续使用现有参数") } } else { console.log("💡 提示:参数轻微不一致,合约不支持自动重置销售") console.log("💡 请手动处理不一致的参数,或等待销售结束后重新部署") console.log("💡 当前销售将继续使用现有参数") } } } } catch (error) { // 销售不存在,创建新销售 console.log("📦 创建新销售...") await launchpad.connect(deployer).createSale( contractAddresses.saleToken, // _token totalTokensForSale, // _totalTokensForSale contributionTarget, // _contributionTarget startTime, // _startTime endTime, // _endTime contractAddresses.mockUSD1, // _paymentToken (USD1) paymentTokenPrice, // _price feeBps, // _feeBps { nonce: nonce++ } ) console.log("✅ 销售创建成功") } console.log("\n📋 测试用户已加载:") console.log(`- 总用户数: ${testUsers.length}`) for (let i = 0; i < Math.min(5, testUsers.length); i++) { console.log(`- 用户${i + 1}: ${testUsers[i].address}`) } if (testUsers.length > 5) { console.log(`- ... 还有 ${testUsers.length - 5} 个用户`) } // 检查第2步分发的USD1余额(前5个用户) console.log("\n💰 检查第2步分发的USD1余额...") let totalUsd1Balance = ethers.parseEther("0") for (let i = 0; i < Math.min(5, testUsers.length); i++) { const balance = await mockUSD1.balanceOf(testUsers[i].address) totalUsd1Balance += balance console.log(`✅ 用户${i + 1} USD1余额: ${ethers.formatEther(balance)}`) } if (testUsers.length > 5) { console.log(`- ... 还有 ${testUsers.length - 5} 个用户有类似余额`) } // 验证用户是否有足够的USD1进行销售 const expectedBalancePerUser = ethers.parseEther("10000") const avgBalance = totalUsd1Balance / BigInt(Math.min(5, testUsers.length)) if (avgBalance < expectedBalancePerUser) { console.warn( `⚠️ 警告:平均USD1余额 (${ethers.formatEther(avgBalance)}) 少于预期 (${ethers.formatEther(expectedBalancePerUser)})` ) console.warn(" 请确保第2步(分发代币)已成功运行") } // 为白名单设置Merkle根(幂等操作) console.log("\n🌳 检查并设置白名单...") let whitelistData: [string, bigint][] // 每个用户可以贡献最多10,000 USD1(使用他们的全部USD1余额) const maxContributionPerUser = ethers.parseEther("10000") // 将所有测试用户添加到白名单,设置大的贡献限额 whitelistData = testUsers.map((user: ethers.Wallet) => [user.address, maxContributionPerUser]) const totalMaxContribution = maxContributionPerUser * BigInt(testUsers.length) console.log( `📋 白名单:所有 ${testUsers.length} 个用户每人最多可贡献 ${ethers.formatEther(maxContributionPerUser)} USDT` ) console.log(`📋 最大总贡献:${ethers.formatEther(totalMaxContribution)} USDT`) console.log(`📋 目标:${ethers.formatEther(contributionTarget)} USDT`) console.log(`📋 超额认购阈值:5个用户 (5 × 10K = 50K = 50K 目标)`) const tree = StandardMerkleTree.of(whitelistData, ["address", "uint256"]) // 检查当前Merkle根 console.log("🔍 检查当前Merkle根...") try { const currentMerkleRoot = await launchpad.merkleRoot() console.log("📊 当前Merkle根:", currentMerkleRoot) console.log("📊 新计算的Merkle根:", tree.root) if (currentMerkleRoot === tree.root) { console.log("✅ Merkle根未变化,无需更新") } else if (currentMerkleRoot === ethers.ZeroHash) { console.log("📝 当前Merkle根为空,设置新的Merkle根...") await launchpad.connect(deployer).setMerkleRoot(tree.root, { nonce: nonce++ }) console.log("✅ Merkle根已设置:", tree.root) } else { console.log("📝 Merkle根已变化,更新Merkle根...") console.log(" - 旧Merkle根:", currentMerkleRoot) console.log(" - 新Merkle根:", tree.root) await launchpad.connect(deployer).setMerkleRoot(tree.root, { nonce: nonce++ }) console.log("✅ Merkle根已更新:", tree.root) } } catch (error) { // 如果获取Merkle根失败,说明还没有设置 console.log("📝 无法获取当前Merkle根,设置新的Merkle根...") await launchpad.connect(deployer).setMerkleRoot(tree.root, { nonce: nonce++ }) console.log("✅ Merkle根已设置:", tree.root) } // 为后续步骤保存额外数据 const step2Data = { ...contractAddresses, testUsers: testUsers.map((user: ethers.Wallet) => user.address), merkleRoot: tree.root, whitelistData: whitelistData, saleParams: { startTime, endTime, contributionTarget: contributionTarget.toString(), paymentTokenPrice: paymentTokenPrice.toString(), feeBps, }, // 第4步的控制参数 oversubscriptionThreshold: 5, // 5个以上用户 = 超额认购 maxContributionPerUser: maxContributionPerUser.toString(), totalMaxContribution: totalMaxContribution.toString(), } const step2DataPath = path.join(outputDir, "step2-data.json") fs.writeFileSync( step2DataPath, JSON.stringify(step2Data, (key, value) => (typeof value === "bigint" ? value.toString() : value), 2) ) console.log("\n🎉 销售初始化成功完成!") console.log("\n📋 总结:") console.log("- 创建了50K USDT目标的销售") console.log("- 所有20个测试用户已添加到白名单") console.log("- 每个用户最多可贡献10K USDT") console.log("- 超额认购阈值:5个以上用户") console.log("- 销售持续时间:24小时") console.log(`- 数据已保存到 ${step2DataPath}`) console.log("\n📋 下一步:运行 'npm run 04:contribute' 测试贡献") console.log(" - 使用少于5个用户进行认购不足场景") console.log(" - 使用5个以上用户进行超额认购场景") console.log("\n📋 控制场景:") console.log(" - 认购不足:npm run 04:contribute (4个用户)") console.log(" - 超额认购:SCENARIO=1 npm run 04:contribute (6个用户)") } // 导出main函数供多环境脚本调用 export { main } // 如果直接运行此脚本 if (require.main === module) { main() .then(() => process.exit(0)) .catch((error) => { console.error("❌ 错误:", error) process.exit(1) }) }