123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464 |
- 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)
- })
- }
|