04-test-contributions.ts 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. import "dotenv/config"
  2. import { ethers } from "ethers"
  3. import * as fs from "fs"
  4. import * as path from "path"
  5. import { StandardMerkleTree } from "@openzeppelin/merkle-tree"
  6. import { MockUSD1__factory, Launchpad__factory } from "../typechain-types/factories/contracts"
  7. import type { MockUSD1, Launchpad } from "../typechain-types/contracts"
  8. async function main() {
  9. // 获取环境配置
  10. const environment = process.env.ENVIRONMENT || "bsctest"
  11. const rpcUrl = process.env.RPC_URL || "https://data-seed-prebsc-1-s1.binance.org:8545/"
  12. const outputDir = process.env.OUTPUT_DIR || "./output/bsctest"
  13. console.log(`🚀 第4步:测试用户贡献...`)
  14. console.log(`🌍 环境: ${environment}`)
  15. console.log(`🔗 RPC: ${rpcUrl}`)
  16. console.log(`📁 输出目录: ${outputDir}`)
  17. // 检查环境变量
  18. if (!process.env.PRIVATE_KEY) {
  19. console.error("❌ 需要设置 PRIVATE_KEY 环境变量")
  20. process.exit(1)
  21. }
  22. // 从之前的步骤加载数据
  23. const step2DataPath = path.join(outputDir, "step2-data.json")
  24. if (!fs.existsSync(step2DataPath)) {
  25. console.error(`❌ 未找到 ${step2DataPath} 文件。请先运行第3步。`)
  26. process.exit(1)
  27. }
  28. const step2Data = JSON.parse(fs.readFileSync(step2DataPath, "utf8"))
  29. console.log("📋 已从之前的步骤加载数据")
  30. // 设置提供者
  31. const provider = new ethers.JsonRpcProvider(rpcUrl)
  32. const deployer = new ethers.Wallet(process.env.PRIVATE_KEY, provider)
  33. // 连接到合约
  34. const mockUSD1 = MockUSD1__factory.connect(step2Data.mockUSD1, deployer)
  35. const launchpad = Launchpad__factory.connect(step2Data.launchpad, deployer)
  36. // 从JSON文件加载测试账户
  37. const testAccountsPath = "./scripts/testAccount/BLaunchpadTest.json"
  38. const testAccounts = JSON.parse(fs.readFileSync(testAccountsPath, "utf8"))
  39. // 从加载的账户创建测试用户(所有用户)
  40. const testUsers = testAccounts.map((account: any) => new ethers.Wallet(account.privateKey, provider))
  41. // 根据场景确定参与用户数量
  42. const scenario = process.env.SCENARIO || "0" // 默认为认购不足
  43. const target = BigInt(step2Data.saleParams.contributionTarget) // contributionTarget 已经是 wei 值
  44. const maxContributionPerUser = ethers.parseEther("10000") // 每个用户最大10K USD1
  45. const usersNeededForTarget = Number(target) / Number(maxContributionPerUser) // 达到目标需要的用户数
  46. console.log(`📊 目标: ${ethers.formatEther(target)} USD1`)
  47. console.log(`📊 每个用户最大: ${ethers.formatEther(maxContributionPerUser)} USD1`)
  48. console.log(`📊 需要用户数: ${usersNeededForTarget}`)
  49. let participatingUsers: ethers.Wallet[]
  50. if (scenario === "1") {
  51. // 超额认购:使用超过目标所需的用户数量
  52. participatingUsers = testUsers.slice(0, Math.ceil(usersNeededForTarget) + 1) // 6个用户 (5 + 1)
  53. console.log(`\n🎯 场景:超额认购 (${participatingUsers.length} 个用户参与,目标需要: ${usersNeededForTarget} 个用户)`)
  54. } else {
  55. // 认购不足:使用少于目标所需的用户数量
  56. participatingUsers = testUsers.slice(0, Math.floor(usersNeededForTarget) - 1) // 4个用户 (5 - 1)
  57. console.log(`\n🎯 场景:认购不足 (${participatingUsers.length} 个用户参与,目标需要: ${usersNeededForTarget} 个用户)`)
  58. }
  59. console.log(`📋 ${participatingUsers.length} 个用户将参与销售`)
  60. // 重新创建Merkle树
  61. const tree = StandardMerkleTree.of(step2Data.whitelistData, ["address", "uint256"])
  62. // 检查销售是否已开始
  63. // 在 Hardhat 环境下使用区块链时间,在真实网络下使用系统时间
  64. const isHardhat = rpcUrl.includes("127.0.0.1") || rpcUrl.includes("localhost") || rpcUrl.includes("8545")
  65. let currentTime: number
  66. if (isHardhat) {
  67. // 在 Hardhat 环境下使用区块链时间
  68. const blockNumber = await provider.getBlockNumber()
  69. const block = await provider.getBlock(blockNumber)
  70. currentTime = block?.timestamp || Math.floor(Date.now() / 1000)
  71. } else {
  72. // 在真实网络下使用系统时间
  73. currentTime = Math.floor(Date.now() / 1000)
  74. }
  75. const saleStartTime = step2Data.saleParams.startTime
  76. if (currentTime < saleStartTime) {
  77. console.log(`\n⏰ 销售尚未开始。当前时间: ${new Date(currentTime * 1000).toLocaleString()}`)
  78. console.log(`⏰ 销售开始时间: ${new Date(saleStartTime * 1000).toLocaleString()}`)
  79. // 在 Hardhat 环境下自动快进时间
  80. if (isHardhat) {
  81. const timeToAdvance = saleStartTime - currentTime + 60 // 快进到开始时间后1分钟
  82. console.log(`⏰ 在 Hardhat 环境下快进时间 ${timeToAdvance} 秒...`)
  83. await provider.send("evm_increaseTime", [timeToAdvance])
  84. await provider.send("evm_mine", [])
  85. // 获取新的当前时间
  86. const newBlockNumber = await provider.getBlockNumber()
  87. const newBlock = await provider.getBlock(newBlockNumber)
  88. const newCurrentTime = newBlock?.timestamp || Math.floor(Date.now() / 1000)
  89. console.log(`✅ 时间已快进到: ${new Date(newCurrentTime * 1000).toLocaleString()}`)
  90. } else {
  91. console.log(`⏰ 等待 ${saleStartTime - currentTime} 秒...`)
  92. console.log("⏰ 您可以修改 step2-data.json 中的 startTime 为当前时间以立即测试")
  93. return
  94. }
  95. }
  96. console.log("\n💰 测试用户贡献...")
  97. // 每个用户贡献他们的最大允许金额
  98. const contributionPerUser = ethers.parseEther("10000") // 每个用户10K USD1
  99. for (let i = 0; i < participatingUsers.length; i++) {
  100. const user = participatingUsers[i]
  101. console.log(`\n👤 用户${i + 1} (${user.address}) 贡献 ${ethers.formatEther(contributionPerUser)} USD1...`)
  102. // 在白名单中找到用户的索引
  103. const userIndex = step2Data.testUsers.indexOf(user.address)
  104. if (userIndex === -1) {
  105. console.error(`❌ 用户 ${user.address} 在白名单中未找到`)
  106. continue
  107. }
  108. const userProof = tree.getProof(userIndex)
  109. try {
  110. // 获取用户当前nonce
  111. const userNonce = await provider.getTransactionCount(user.address)
  112. console.log(`📋 用户${i + 1} 当前nonce: ${userNonce}`)
  113. // 批准USD1支出
  114. await mockUSD1.connect(user).approve(step2Data.launchpad, contributionPerUser, { nonce: userNonce })
  115. console.log(`✅ 用户${i + 1} 已批准USD1支出`)
  116. // 贡献
  117. await launchpad.connect(user).contributeWithERC20(
  118. contributionPerUser,
  119. contributionPerUser, // 最大金额(与贡献相同)
  120. userProof,
  121. { nonce: userNonce + 1 }
  122. )
  123. console.log(`✅ 用户${i + 1} 贡献了 ${ethers.formatEther(contributionPerUser)} USD1`)
  124. } catch (error) {
  125. console.error(`❌ 用户${i + 1} 贡献失败:`, error)
  126. }
  127. }
  128. // 检查总贡献
  129. const totalContributed = await launchpad.totalContributionAmount()
  130. console.log("\n📊 总贡献:", ethers.formatEther(totalContributed), "USD1")
  131. // 检查销售是否超额认购
  132. const isOversubscribed = totalContributed > target
  133. console.log("\n📋 销售状态:")
  134. console.log("- 目标:", ethers.formatEther(target), "USD1")
  135. console.log("- 总贡献:", ethers.formatEther(totalContributed), "USD1")
  136. console.log("- 状态:", isOversubscribed ? "超额认购" : "认购不足")
  137. // 检查个别用户贡献
  138. console.log("\n📋 个别贡献:")
  139. for (let i = 0; i < Math.min(5, participatingUsers.length); i++) {
  140. const user = participatingUsers[i]
  141. const userContributed = await launchpad.userContributionAmount(user.address)
  142. console.log(`- 用户${i + 1}: ${ethers.formatEther(userContributed)} USD1`)
  143. }
  144. if (participatingUsers.length > 5) {
  145. console.log(`- ... 还有 ${participatingUsers.length - 5} 个用户`)
  146. }
  147. // 保存贡献数据
  148. const contributionData = {
  149. ...step2Data,
  150. contributions: {
  151. total: totalContributed.toString(),
  152. participatingUsers: participatingUsers.map(user => user.address),
  153. participatingUsersCount: participatingUsers.length,
  154. isOversubscribed: isOversubscribed,
  155. },
  156. }
  157. const step3DataPath = path.join(outputDir, "step3-data.json")
  158. fs.writeFileSync(step3DataPath, JSON.stringify(contributionData, null, 2))
  159. console.log("\n🎉 贡献测试成功完成!")
  160. console.log(`\n📋 场景: ${isOversubscribed ? "超额认购" : "认购不足"}`)
  161. console.log("📋 下一步:运行 'npm run 05:claim' 在销售结束后测试领取")
  162. }
  163. // 导出main函数供多环境脚本调用
  164. export { main }
  165. // 如果直接运行此脚本
  166. if (require.main === module) {
  167. main()
  168. .then(() => process.exit(0))
  169. .catch((error) => {
  170. console.error("❌ Error:", error)
  171. process.exit(1)
  172. })
  173. }