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, Launchpad__factory } from "../typechain-types/factories/contracts" import type { MockUSD1, 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(`🚀 第4步:测试用户贡献...`) console.log(`🌍 环境: ${environment}`) console.log(`🔗 RPC: ${rpcUrl}`) console.log(`📁 输出目录: ${outputDir}`) // 检查环境变量 if (!process.env.PRIVATE_KEY) { console.error("❌ 需要设置 PRIVATE_KEY 环境变量") process.exit(1) } // 从之前的步骤加载数据 const step2DataPath = path.join(outputDir, "step2-data.json") if (!fs.existsSync(step2DataPath)) { console.error(`❌ 未找到 ${step2DataPath} 文件。请先运行第3步。`) process.exit(1) } const step2Data = JSON.parse(fs.readFileSync(step2DataPath, "utf8")) console.log("📋 已从之前的步骤加载数据") // 设置提供者 const provider = new ethers.JsonRpcProvider(rpcUrl) const deployer = new ethers.Wallet(process.env.PRIVATE_KEY, provider) // 连接到合约 const mockUSD1 = MockUSD1__factory.connect(step2Data.mockUSD1, deployer) const launchpad = Launchpad__factory.connect(step2Data.launchpad, deployer) // 从JSON文件加载测试账户 const testAccountsPath = "./scripts/testAccount/BLaunchpadTest.json" const testAccounts = JSON.parse(fs.readFileSync(testAccountsPath, "utf8")) // 从加载的账户创建测试用户(所有用户) const testUsers = testAccounts.map((account: any) => new ethers.Wallet(account.privateKey, provider)) // 根据场景确定参与用户数量 const scenario = process.env.SCENARIO || "0" // 默认为认购不足 const target = BigInt(step2Data.saleParams.contributionTarget) // contributionTarget 已经是 wei 值 const maxContributionPerUser = ethers.parseEther("10000") // 每个用户最大10K USD1 const usersNeededForTarget = Number(target) / Number(maxContributionPerUser) // 达到目标需要的用户数 console.log(`📊 目标: ${ethers.formatEther(target)} USD1`) console.log(`📊 每个用户最大: ${ethers.formatEther(maxContributionPerUser)} USD1`) console.log(`📊 需要用户数: ${usersNeededForTarget}`) let participatingUsers: ethers.Wallet[] if (scenario === "1") { // 超额认购:使用超过目标所需的用户数量 participatingUsers = testUsers.slice(0, Math.ceil(usersNeededForTarget) + 1) // 6个用户 (5 + 1) console.log(`\n🎯 场景:超额认购 (${participatingUsers.length} 个用户参与,目标需要: ${usersNeededForTarget} 个用户)`) } else { // 认购不足:使用少于目标所需的用户数量 participatingUsers = testUsers.slice(0, Math.floor(usersNeededForTarget) - 1) // 4个用户 (5 - 1) console.log(`\n🎯 场景:认购不足 (${participatingUsers.length} 个用户参与,目标需要: ${usersNeededForTarget} 个用户)`) } console.log(`📋 ${participatingUsers.length} 个用户将参与销售`) // 重新创建Merkle树 const tree = StandardMerkleTree.of(step2Data.whitelistData, ["address", "uint256"]) // 检查销售是否已开始 // 在 Hardhat 环境下使用区块链时间,在真实网络下使用系统时间 const isHardhat = rpcUrl.includes("127.0.0.1") || rpcUrl.includes("localhost") || rpcUrl.includes("8545") let currentTime: number if (isHardhat) { // 在 Hardhat 环境下使用区块链时间 const blockNumber = await provider.getBlockNumber() const block = await provider.getBlock(blockNumber) currentTime = block?.timestamp || Math.floor(Date.now() / 1000) } else { // 在真实网络下使用系统时间 currentTime = Math.floor(Date.now() / 1000) } const saleStartTime = step2Data.saleParams.startTime if (currentTime < saleStartTime) { console.log(`\n⏰ 销售尚未开始。当前时间: ${new Date(currentTime * 1000).toLocaleString()}`) console.log(`⏰ 销售开始时间: ${new Date(saleStartTime * 1000).toLocaleString()}`) // 在 Hardhat 环境下自动快进时间 if (isHardhat) { const timeToAdvance = saleStartTime - currentTime + 60 // 快进到开始时间后1分钟 console.log(`⏰ 在 Hardhat 环境下快进时间 ${timeToAdvance} 秒...`) await provider.send("evm_increaseTime", [timeToAdvance]) await provider.send("evm_mine", []) // 获取新的当前时间 const newBlockNumber = await provider.getBlockNumber() const newBlock = await provider.getBlock(newBlockNumber) const newCurrentTime = newBlock?.timestamp || Math.floor(Date.now() / 1000) console.log(`✅ 时间已快进到: ${new Date(newCurrentTime * 1000).toLocaleString()}`) } else { console.log(`⏰ 等待 ${saleStartTime - currentTime} 秒...`) console.log("⏰ 您可以修改 step2-data.json 中的 startTime 为当前时间以立即测试") return } } console.log("\n💰 测试用户贡献...") // 每个用户贡献他们的最大允许金额 const contributionPerUser = ethers.parseEther("10000") // 每个用户10K USD1 for (let i = 0; i < participatingUsers.length; i++) { const user = participatingUsers[i] console.log(`\n👤 用户${i + 1} (${user.address}) 贡献 ${ethers.formatEther(contributionPerUser)} USD1...`) // 在白名单中找到用户的索引 const userIndex = step2Data.testUsers.indexOf(user.address) if (userIndex === -1) { console.error(`❌ 用户 ${user.address} 在白名单中未找到`) continue } const userProof = tree.getProof(userIndex) try { // 获取用户当前nonce const userNonce = await provider.getTransactionCount(user.address) console.log(`📋 用户${i + 1} 当前nonce: ${userNonce}`) // 批准USD1支出 await mockUSD1.connect(user).approve(step2Data.launchpad, contributionPerUser, { nonce: userNonce }) console.log(`✅ 用户${i + 1} 已批准USD1支出`) // 贡献 await launchpad.connect(user).contributeWithERC20( contributionPerUser, contributionPerUser, // 最大金额(与贡献相同) userProof, { nonce: userNonce + 1 } ) console.log(`✅ 用户${i + 1} 贡献了 ${ethers.formatEther(contributionPerUser)} USD1`) } catch (error) { console.error(`❌ 用户${i + 1} 贡献失败:`, error) } } // 检查总贡献 const totalContributed = await launchpad.totalContributionAmount() console.log("\n📊 总贡献:", ethers.formatEther(totalContributed), "USD1") // 检查销售是否超额认购 const isOversubscribed = totalContributed > target console.log("\n📋 销售状态:") console.log("- 目标:", ethers.formatEther(target), "USD1") console.log("- 总贡献:", ethers.formatEther(totalContributed), "USD1") console.log("- 状态:", isOversubscribed ? "超额认购" : "认购不足") // 检查个别用户贡献 console.log("\n📋 个别贡献:") for (let i = 0; i < Math.min(5, participatingUsers.length); i++) { const user = participatingUsers[i] const userContributed = await launchpad.userContributionAmount(user.address) console.log(`- 用户${i + 1}: ${ethers.formatEther(userContributed)} USD1`) } if (participatingUsers.length > 5) { console.log(`- ... 还有 ${participatingUsers.length - 5} 个用户`) } // 保存贡献数据 const contributionData = { ...step2Data, contributions: { total: totalContributed.toString(), participatingUsers: participatingUsers.map(user => user.address), participatingUsersCount: participatingUsers.length, isOversubscribed: isOversubscribed, }, } const step3DataPath = path.join(outputDir, "step3-data.json") fs.writeFileSync(step3DataPath, JSON.stringify(contributionData, null, 2)) console.log("\n🎉 贡献测试成功完成!") console.log(`\n📋 场景: ${isOversubscribed ? "超额认购" : "认购不足"}`) console.log("📋 下一步:运行 'npm run 05:claim' 在销售结束后测试领取") } // 导出main函数供多环境脚本调用 export { main } // 如果直接运行此脚本 if (require.main === module) { main() .then(() => process.exit(0)) .catch((error) => { console.error("❌ Error:", error) process.exit(1) }) }