123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696 |
- import { expect } from "chai"
- import hre, { ethers } from "hardhat"
- import { StandardMerkleTree } from "@openzeppelin/merkle-tree"
- import { loadFixture, time } from "@nomicfoundation/hardhat-toolbox/network-helpers"
- import { network } from "hardhat"
- let snapshotId: string
- describe("Launchpad", () => {
- const deployLaunchpadFixture = async () => {
- const signers = await hre.ethers.getSigners()
- // for (const signer of signers) {
- // await hre.network.provider.send("hardhat_setBalance", [
- // signer.address,
- // "0x3635C9ADC5DEA00000", // 1000 ETH
- // ])
- // }
- const owner = signers[0].address
- const Launchpad = await hre.ethers.getContractFactory("Launchpad")
- const Erc20Token = await hre.ethers.getContractFactory("BasicERC20")
- const launchpadContract = await Launchpad.deploy({
- from: owner,
- })
- const saleTokenContract = await Erc20Token.deploy("TestToken", "TT", owner, {
- from: owner,
- })
- const buyTokenContract = await Erc20Token.deploy("Usdt", "USDT", owner, {
- from: owner,
- })
- const launchpadAddress = await launchpadContract.getAddress()
- await saleTokenContract.mint(owner, ethers.parseEther("1000000"))
- await saleTokenContract.approve(launchpadAddress, ethers.MaxUint256)
- await buyTokenContract.mint(signers[1].address, ethers.parseEther("10000"))
- return {
- launchpadContract,
- saleTokenContract,
- buyTokenContract,
- owner,
- }
- }
- const createNativeSaleFixture = async () => {
- const { launchpadContract, saleTokenContract, owner } = await deployLaunchpadFixture()
- const balanceBefore = await saleTokenContract.balanceOf(owner)
- const saleTokenAddress = await saleTokenContract.getAddress()
- const now = Math.floor(Date.now() / 1000)
- const startTime = now + 60
- const endTime = now + 60 * 60 * 8
- const paymentTokens = ethers.ZeroAddress
- const paymentTokenPrice = ethers.parseEther("1")
- await launchpadContract.createSale(
- saleTokenAddress,
- balanceBefore,
- ethers.parseEther("100"), // contributionTarget
- startTime,
- endTime,
- paymentTokens,
- paymentTokenPrice,
- 0n // feeBps
- )
- return {
- launchpadContract,
- saleTokenContract,
- owner,
- }
- }
- describe("init function and setters", () => {
- it("Should Allow Owner to create a sale use native token", async () => {
- const { launchpadContract, saleTokenContract, owner } = await loadFixture(deployLaunchpadFixture)
- const balanceBefore = await saleTokenContract.balanceOf(owner)
- const saleTokenAddress = await saleTokenContract.getAddress()
- const now = Math.floor(Date.now() / 1000)
- let startTime = now + 60 // 1 minute from now
- const endTime = now + 60 * 60 * 8 // 1 hour from now
- const paymentTokens = ethers.ZeroAddress
- const paymentTokenPrice = ethers.parseEther("1")
- await launchpadContract.createSale(
- saleTokenAddress,
- balanceBefore,
- ethers.parseEther("100"), // contributionTarget
- startTime,
- endTime,
- paymentTokens,
- paymentTokenPrice,
- 0n // feeBps
- )
- startTime = Number(await launchpadContract.getStartTime())
- await time.increaseTo(startTime + 1)
- expect(await saleTokenContract.balanceOf(owner)).to.equal(0)
- expect(await launchpadContract.getSaleToken()).to.equal(saleTokenAddress)
- expect(await launchpadContract.getTotalTokensForSale()).to.equal(balanceBefore)
- expect(await launchpadContract.getStartTime()).to.equal(startTime)
- expect(await launchpadContract.getEndTime()).to.equal(endTime)
- expect(await launchpadContract.getPaymentTokens()).to.equal(ethers.ZeroAddress)
- expect(await launchpadContract.getPaymentTokenPrice()).to.equal(paymentTokenPrice)
- expect(await launchpadContract.totalContributionAmount()).to.equal(0n)
- })
- it("should Allow Owner reset payment info", async () => {
- const { launchpadContract, saleTokenContract, owner } = await loadFixture(createNativeSaleFixture)
- const paymentTokenBefore = await launchpadContract.getPaymentTokens()
- const paymentTokenPriceBefore = await launchpadContract.getPaymentTokenPrice()
- expect(await launchpadContract.getPaymentTokens()).to.equal(paymentTokenBefore)
- expect(await launchpadContract.getPaymentTokenPrice()).to.equal(paymentTokenPriceBefore)
- const newPaymentToken = await saleTokenContract.getAddress()
- const newPaymentTokenPrice = ethers.parseEther("1")
- await launchpadContract.setSalePayment(newPaymentToken, newPaymentTokenPrice, {
- from: owner,
- })
- expect(await launchpadContract.getPaymentTokens()).to.equal(newPaymentToken)
- expect(await launchpadContract.getPaymentTokenPrice()).to.equal(newPaymentTokenPrice)
- })
- it("should Allow Owner set MerkleRoot", async () => {
- const { launchpadContract, owner } = await loadFixture(createNativeSaleFixture)
- const signers = await hre.ethers.getSigners()
- const values = [
- [signers[0].address, ethers.parseEther("50")],
- [signers[1].address, ethers.parseEther("150")],
- [signers[2].address, ethers.parseEther("100")],
- ]
- const tree = StandardMerkleTree.of(values, ["address", "uint256"])
- const root = tree.root
- await launchpadContract.setMerkleRoot(root, { from: owner })
- expect(await launchpadContract.merkleRoot()).to.equal(tree.root)
- })
- it("should Allow Owner transfer Owner", async () => {
- const { launchpadContract, owner } = await createNativeSaleFixture()
- const signers = await hre.ethers.getSigners()
- const newOwner = signers[1].address
- await launchpadContract.setOwner(newOwner, { from: owner })
- expect(await launchpadContract.owner()).to.equal(newOwner)
- })
- })
- describe("after init buy function", () => {
- it("should Allow user use native token buy token", async () => {
- const { launchpadContract, owner } = await loadFixture(createNativeSaleFixture)
- const signers = await hre.ethers.getSigners()
- const values = [
- [signers[0].address, ethers.parseEther("50")],
- [signers[1].address, ethers.parseEther("10")],
- [signers[2].address, ethers.parseEther("100")],
- [signers[3].address, ethers.parseEther("200")],
- [signers[4].address, ethers.parseEther("300")],
- ]
- const tree = StandardMerkleTree.of(values, ["address", "uint256"])
- const root = tree.root
- const buyAmount = ethers.parseEther("1")
- const buyer = signers[1]
- let maxBuyAmount = 0n
- let proof = [] as string[]
- let wrongProof = [] as string[]
- for (const [i, v] of tree.entries()) {
- if (v[0] === buyer.address) {
- proof = tree.getProof(i)
- if (typeof v[1] === "bigint") {
- maxBuyAmount = v[1]
- }
- }
- if (v[0] === signers[2].address) {
- wrongProof = tree.getProof(i)
- }
- }
- await expect(
- launchpadContract.connect(buyer).contributeWithETH(buyAmount, maxBuyAmount, proof)
- ).to.be.revertedWith("MerkleRoot not initialized")
- await launchpadContract.setMerkleRoot(root, { from: owner })
- await time.increase(100)
- await expect(
- launchpadContract.connect(buyer).contributeWithETH(buyAmount, maxBuyAmount, proof)
- ).to.be.revertedWith("Must send enough ETH")
- await expect(
- launchpadContract.connect(buyer).contributeWithETH(buyAmount + maxBuyAmount, maxBuyAmount, proof, {
- value: ethers.parseEther("0.5"),
- })
- ).to.be.revertedWith("Buy amount exceeds max buy amount")
- await expect(
- launchpadContract.connect(buyer).contributeWithETH(buyAmount, maxBuyAmount, wrongProof, {
- value: ethers.parseEther("1"),
- })
- ).to.be.revertedWith("Invalid proof")
- await expect(
- launchpadContract.connect(buyer).contributeWithETH(buyAmount, maxBuyAmount, proof, {
- value: ethers.parseEther("0.5"),
- })
- ).to.be.revertedWith("Must send enough ETH")
- await expect(
- launchpadContract.connect(buyer).contributeWithETH(ethers.parseEther("50"), maxBuyAmount, proof, {
- value: ethers.parseEther("50"),
- })
- ).to.revertedWith("Buy amount exceeds max buy amount")
- //success once
- await expect(
- launchpadContract.connect(buyer).contributeWithETH(buyAmount, maxBuyAmount, proof, {
- value: ethers.parseEther("1"),
- })
- ).to.emit(launchpadContract, "Contributed")
- //make it success twice
- await expect(
- launchpadContract.connect(buyer).contributeWithETH(ethers.parseEther("4"), maxBuyAmount, proof, {
- value: ethers.parseEther("4"),
- })
- ).to.emit(launchpadContract, "Contributed")
- expect(await launchpadContract.getClaimableTokens(buyer.address)).to.equal(
- buyAmount + ethers.parseEther("4")
- )
- //failed by exceeding max buy amount
- await expect(
- launchpadContract.connect(buyer).contributeWithETH(ethers.parseEther("10"), maxBuyAmount, proof, {
- value: ethers.parseEther("10"),
- })
- ).to.be.revertedWith("Buy amount exceeds max buy amount")
- })
- it("should Allow user claim token after finished sale", async () => {
- const { launchpadContract, saleTokenContract, owner } = await loadFixture(createNativeSaleFixture)
- const signers = await hre.ethers.getSigners()
- const values = [
- [signers[0].address, ethers.parseEther("50")],
- [signers[1].address, ethers.parseEther("10")],
- [signers[2].address, ethers.parseEther("100")],
- [signers[3].address, ethers.parseEther("200")],
- [signers[4].address, ethers.parseEther("300")],
- ]
- const tree = StandardMerkleTree.of(values, ["address", "uint256"])
- const root = tree.root
- const buyAmount = ethers.parseEther("1")
- const buyer = signers[1]
- let maxBuyAmount = 0n
- let proof = [] as string[]
- for (const [i, v] of tree.entries()) {
- if (v[0] === buyer.address) {
- proof = tree.getProof(i)
- if (typeof v[1] === "bigint") {
- maxBuyAmount = v[1]
- }
- }
- }
- await launchpadContract.setMerkleRoot(root, { from: owner })
- await time.increase(100)
- await expect(
- launchpadContract.connect(buyer).contributeWithETH(buyAmount, maxBuyAmount, proof, {
- value: ethers.parseEther("1"),
- })
- ).to.emit(launchpadContract, "Contributed")
- expect(await launchpadContract.getClaimableTokens(buyer.address)).to.equal(buyAmount)
- await expect(launchpadContract.connect(buyer).claimTokens()).to.be.revertedWith(
- "Claiming tokens is not enabled"
- )
- expect(await launchpadContract.enableClaimTokens(Math.floor(Date.now() / 1000) + 60 * 60 * 8)).to.emit(
- launchpadContract,
- "EnableClaimToken"
- )
- await expect(launchpadContract.connect(buyer).claimTokens()).to.be.revertedWith(
- "Claiming tokens not started yet"
- )
- await time.increase(60 * 60 * 8 + 1) // increase time to finish sale
- await expect(launchpadContract.connect(signers[2]).claimTokens()).to.be.revertedWith("No tokens to claim")
- //Transfer token
- await expect(launchpadContract.connect(buyer).claimTokens()).to.emit(saleTokenContract, "Transfer")
- //Balance check
- expect(await saleTokenContract.balanceOf(buyer.address)).to.equal(buyAmount)
- })
- it("should Allow owner claim contribution token after finished sale", async () => {
- const { launchpadContract, saleTokenContract, owner } = await loadFixture(createNativeSaleFixture)
- const signers = await hre.ethers.getSigners()
- const values = [
- [signers[0].address, ethers.parseEther("50")],
- [signers[1].address, ethers.parseEther("10")],
- [signers[2].address, ethers.parseEther("100")],
- [signers[3].address, ethers.parseEther("200")],
- [signers[4].address, ethers.parseEther("300")],
- ]
- const tree = StandardMerkleTree.of(values, ["address", "uint256"])
- const root = tree.root
- const buyAmount = ethers.parseEther("1")
- const buyer = signers[1]
- let maxBuyAmount = 0n
- let proof = [] as string[]
- for (const [i, v] of tree.entries()) {
- if (v[0] === buyer.address) {
- proof = tree.getProof(i)
- if (typeof v[1] === "bigint") {
- maxBuyAmount = v[1]
- }
- }
- }
- await launchpadContract.setMerkleRoot(root, { from: owner })
- const launchpadAddress = await launchpadContract.getAddress()
- await time.increase(100)
- await expect(
- launchpadContract.connect(buyer).contributeWithETH(buyAmount, maxBuyAmount, proof, {
- value: ethers.parseEther("1"),
- })
- ).to.emit(launchpadContract, "Contributed")
- await expect(launchpadContract.withdrawPayments()).to.be.revertedWith("Sale is still active")
- await launchpadContract.enableClaimTokens(Math.floor(Date.now() / 1000) + 60 * 60 * 8)
- await time.increase(60 * 60 * 8 + 1) // increase time to finish sale
- await launchpadContract.connect(buyer).claimTokens()
- await expect(launchpadContract.connect(signers[2]).withdrawPayments()).to.revertedWith(
- "Only owner can call this function"
- )
- await expect(launchpadContract.connect(signers[2]).withdrawRemainingTokens()).to.revertedWith(
- "Only owner can call this function"
- )
- await launchpadContract.withdrawPayments()
- await launchpadContract.withdrawRemainingTokens()
- expect(await ethers.provider.getBalance(launchpadAddress)).to.equal(0n)
- expect(await saleTokenContract.balanceOf(launchpadAddress)).to.equal(0)
- })
- it("should allow user to buy and claim with ERC20 payment token", async () => {
- const { launchpadContract, saleTokenContract, buyTokenContract, owner } =
- await loadFixture(deployLaunchpadFixture)
- const signers = await hre.ethers.getSigners()
- const buyer = signers[1]
- // 创建ERC20众筹
- const saleTokenAddress = await saleTokenContract.getAddress()
- const buyTokenAddress = await buyTokenContract.getAddress()
- const now = Math.floor(Date.now() / 1000)
- const startTime = now + 100
- const endTime = now + 60 * 60 * 8
- const paymentTokenPrice = ethers.parseEther("1")
- const totalTokens = await saleTokenContract.balanceOf(owner)
- await launchpadContract.createSale(
- saleTokenAddress,
- totalTokens,
- totalTokens, // contributionTarget
- startTime,
- endTime,
- buyTokenAddress,
- paymentTokenPrice,
- 0n // feeBps
- )
- await time.increase(100)
- // Merkle tree
- const values = [[buyer.address, ethers.parseEther("100")]]
- const tree = StandardMerkleTree.of(values, ["address", "uint256"])
- const proof = tree.getProof(0)
- await launchpadContract.setMerkleRoot(tree.root, { from: owner })
- await time.increase(100)
- // approve
- await buyTokenContract
- .connect(buyer)
- .approve(await launchpadContract.getAddress(), ethers.parseEther("100"))
- // buy
- await expect(
- launchpadContract
- .connect(buyer)
- .contributeWithERC20(ethers.parseEther("10"), ethers.parseEther("100"), proof)
- ).to.emit(launchpadContract, "Contributed")
- // enable claim
- await launchpadContract.enableClaimTokens(Math.floor(Date.now() / 1000) + 60 * 60 * 8)
- await time.increase(60 * 60 * 8 + 1)
- // claim
- await expect(launchpadContract.connect(buyer).claimTokens()).to.emit(launchpadContract, "TokensClaimed")
- expect(await saleTokenContract.balanceOf(buyer.address)).to.equal(ethers.parseEther("10"))
- })
- it("should refund excess and distribute tokens proportionally when oversubscribed", async () => {
- const { launchpadContract, saleTokenContract, owner } = await loadFixture(deployLaunchpadFixture)
- const signers = await hre.ethers.getSigners()
- const tokenBalanceBefore = await saleTokenContract.balanceOf(owner)
- const saleTokenAddress = await saleTokenContract.getAddress()
- const now = Math.floor(Date.now() / 1000)
- const startTime = now + 60
- const endTime = now + 60 * 60 * 8
- const paymentTokens = ethers.ZeroAddress
- const paymentTokenPrice = ethers.parseEther("1")
- await launchpadContract.createSale(
- saleTokenAddress,
- tokenBalanceBefore,
- ethers.parseEther("100"), // contributionTarget
- startTime,
- endTime,
- paymentTokens,
- paymentTokenPrice,
- 0n // feeBps
- )
- // 设置白名单
- const values = [
- [signers[1].address, ethers.parseEther("100")],
- [signers[2].address, ethers.parseEther("100")],
- ]
- const tree = StandardMerkleTree.of(values, ["address", "uint256"])
- await launchpadContract.setMerkleRoot(tree.root, { from: owner })
- // 两人都买100,目标额度为100,实际总额200
- const proof1 = tree.getProof(0)
- const proof2 = tree.getProof(1)
- await time.increase(100)
- await launchpadContract
- .connect(signers[1])
- .contributeWithETH(ethers.parseEther("100"), ethers.parseEther("100"), proof1, {
- value: ethers.parseEther("100"),
- })
- await launchpadContract
- .connect(signers[2])
- .contributeWithETH(ethers.parseEther("100"), ethers.parseEther("100"), proof2, {
- value: ethers.parseEther("100"),
- })
- await launchpadContract.enableClaimTokens(Math.floor(Date.now() / 1000) + 60 * 60 * 8)
- await time.increase(3600 * 8)
- // 领取
- const balanceBefore = await ethers.provider.getBalance(signers[1].address)
- await launchpadContract.connect(signers[1]).claimTokens()
- // 检查领取的token数量为50(按比例分配
- const balanceAfter = await ethers.provider.getBalance(signers[1].address)
- expect(Number(ethers.formatEther(balanceAfter - balanceBefore))).to.closeTo(50, 0.001)
- // 断言退款金额接近50
- // ...可进一步断言
- })
- it("should deduct fee from user contribution when claiming tokens", async () => {
- const { launchpadContract, saleTokenContract, owner } = await loadFixture(deployLaunchpadFixture)
- const tokenBalanceBefore = await saleTokenContract.balanceOf(owner)
- const saleTokenAddress = await saleTokenContract.getAddress()
- const now = Math.floor(Date.now() / 1000)
- const startTime = now + 60
- const endTime = now + 60 * 60 * 8
- const paymentTokens = ethers.ZeroAddress
- const paymentTokenPrice = ethers.parseEther("1")
- await launchpadContract.createSale(
- saleTokenAddress,
- tokenBalanceBefore,
- ethers.parseEther("100"), // contributionTarget
- startTime,
- endTime,
- paymentTokens,
- paymentTokenPrice,
- 0n // feeBps
- )
- const signers = await hre.ethers.getSigners()
- const values = [[signers[1].address, ethers.parseEther("10")]]
- const tree = StandardMerkleTree.of(values, ["address", "uint256"])
- await launchpadContract.setMerkleRoot(tree.root, { from: owner })
- await launchpadContract.setFeeBps(500) // 5% fee
- await time.increase(100)
- await launchpadContract
- .connect(signers[1])
- .contributeWithETH(ethers.parseEther("10"), ethers.parseEther("10"), tree.getProof(0), {
- value: ethers.parseEther("10"),
- })
- await launchpadContract.enableClaimTokens(Math.floor(Date.now() / 1000) + 60 * 60 * 8)
- await time.increase(60 * 60 * 8 + 1)
- const before = await saleTokenContract.balanceOf(signers[1].address)
- await launchpadContract.connect(signers[1]).claimTokens()
- const after = await saleTokenContract.balanceOf(signers[1].address)
- // 断言实际到账 < 10
- expect(after - before).to.equal(ethers.parseEther("9.5")) // 5% fee
- })
- it("should not allow user to claim tokens twice", async () => {
- const { launchpadContract, saleTokenContract, owner } = await loadFixture(deployLaunchpadFixture)
- const tokenBalanceBefore = await saleTokenContract.balanceOf(owner)
- const saleTokenAddress = await saleTokenContract.getAddress()
- const now = Math.floor(Date.now() / 1000)
- const startTime = now + 60
- const endTime = now + 60 * 60 * 8
- const paymentTokens = ethers.ZeroAddress
- const paymentTokenPrice = ethers.parseEther("1")
- await launchpadContract.createSale(
- saleTokenAddress,
- tokenBalanceBefore,
- ethers.parseEther("100"), // contributionTarget
- startTime,
- endTime,
- paymentTokens,
- paymentTokenPrice,
- 0n // feeBps
- )
- const signers = await hre.ethers.getSigners()
- const values = [[signers[1].address, ethers.parseEther("10")]]
- const tree = StandardMerkleTree.of(values, ["address", "uint256"])
- await launchpadContract.setMerkleRoot(tree.root, { from: owner })
- await time.increase(100)
- await launchpadContract
- .connect(signers[1])
- .contributeWithETH(ethers.parseEther("10"), ethers.parseEther("10"), tree.getProof(0), {
- value: ethers.parseEther("10"),
- })
- await launchpadContract.enableClaimTokens(Math.floor(Date.now() / 1000) + 60 * 60 * 8)
- await time.increase(60 * 60 * 8)
- await launchpadContract.connect(signers[1]).claimTokens()
- await expect(launchpadContract.connect(signers[1]).claimTokens()).to.be.revertedWith("No tokens to claim")
- })
- it("should revert claim if contract has insufficient token balance", async () => {
- const { launchpadContract, saleTokenContract, owner } = await loadFixture(deployLaunchpadFixture)
- const tokenBalanceBefore = await saleTokenContract.balanceOf(owner)
- const saleTokenAddress = await saleTokenContract.getAddress()
- const now = Math.floor(Date.now() / 1000)
- const startTime = now + 60
- const endTime = now + 60 * 60 * 8
- const paymentTokens = ethers.ZeroAddress
- const paymentTokenPrice = ethers.parseEther("1")
- await launchpadContract.createSale(
- saleTokenAddress,
- tokenBalanceBefore,
- ethers.parseEther("100"), // contributionTarget
- startTime,
- endTime,
- paymentTokens,
- paymentTokenPrice,
- 0n // feeBps
- )
- const signers = await hre.ethers.getSigners()
- const values = [[signers[1].address, ethers.parseEther("10")]]
- const tree = StandardMerkleTree.of(values, ["address", "uint256"])
- await launchpadContract.setMerkleRoot(tree.root, { from: owner })
- await time.increase(100)
- await launchpadContract
- .connect(signers[1])
- .contributeWithETH(ethers.parseEther("10"), ethers.parseEther("10"), tree.getProof(0), {
- value: ethers.parseEther("10"),
- })
- await launchpadContract.enableClaimTokens(Math.floor(Date.now() / 1000) + 60 * 60 * 8)
- await time.increase(60 * 60 * 8)
- // owner 提前提走所有token
- await launchpadContract.withdrawRemainingTokens()
- await expect(launchpadContract.connect(signers[1]).claimTokens()).to.be.reverted // 断言revert
- })
- it("should revert claim if contract has insufficient ETH balance for refund", async () => {
- const { launchpadContract, saleTokenContract, owner } = await loadFixture(deployLaunchpadFixture)
- const tokenBalanceBefore = await saleTokenContract.balanceOf(owner)
- const saleTokenAddress = await saleTokenContract.getAddress()
- const now = Math.floor(Date.now() / 1000)
- const startTime = now + 60
- const endTime = now + 60 * 60 * 8
- const paymentTokens = ethers.ZeroAddress
- const paymentTokenPrice = ethers.parseEther("1")
- await launchpadContract.createSale(
- saleTokenAddress,
- tokenBalanceBefore,
- ethers.parseEther("100"), // contributionTarget
- startTime,
- endTime,
- paymentTokens,
- paymentTokenPrice,
- 0n // feeBps
- )
- const signers = await hre.ethers.getSigners()
- // 设置白名单
- const values = [
- [signers[1].address, ethers.parseEther("100")],
- [signers[2].address, ethers.parseEther("100")],
- ]
- const tree = StandardMerkleTree.of(values, ["address", "uint256"])
- await launchpadContract.setMerkleRoot(tree.root, { from: owner })
- await time.increase(100)
- // 两人都买100,目标额度为100,实际总额200,claim时应部分退款
- const proof1 = tree.getProof(0)
- const proof2 = tree.getProof(1)
- await launchpadContract
- .connect(signers[1])
- .contributeWithETH(ethers.parseEther("100"), ethers.parseEther("100"), proof1, {
- value: ethers.parseEther("100"),
- })
- await launchpadContract
- .connect(signers[2])
- .contributeWithETH(ethers.parseEther("100"), ethers.parseEther("100"), proof2, {
- value: ethers.parseEther("100"),
- })
- await launchpadContract.enableClaimTokens(Math.floor(Date.now() / 1000) + 60 * 60 * 8)
- await time.increase(60 * 60 * 8)
- // owner 提前提走所有ETH
- await launchpadContract.withdrawPayments()
- await expect(launchpadContract.connect(signers[1]).claimTokens()).to.be.reverted
- })
- it("should revert claim if contract has insufficient ERC20 balance for refund", async () => {
- const { launchpadContract, saleTokenContract, buyTokenContract, owner } =
- await loadFixture(deployLaunchpadFixture)
- const signers = await hre.ethers.getSigners()
- const buyer1 = signers[1]
- const buyer2 = signers[2]
- await buyTokenContract.mint(buyer1.address, ethers.parseEther("1000000000"))
- await buyTokenContract.mint(buyer2.address, ethers.parseEther("1000000000"))
- // 创建ERC20众筹
- const saleTokenAddress = await saleTokenContract.getAddress()
- const buyTokenAddress = await buyTokenContract.getAddress()
- const now = Math.floor(Date.now() / 1000)
- const startTime = now + 60
- const endTime = now + 60 * 60 * 8
- const paymentTokenPrice = ethers.parseEther("1")
- const totalTokens = await saleTokenContract.balanceOf(owner)
- await launchpadContract.createSale(
- saleTokenAddress,
- totalTokens,
- totalTokens, // contributionTarget
- startTime,
- endTime,
- buyTokenAddress,
- paymentTokenPrice,
- 0n // feeBps
- )
- const blockInfo = await ethers.provider.getBlock("latest")
- await time.increaseTo(blockInfo!.timestamp + 1)
- // Merkle tree
- const values = [
- [buyer1.address, ethers.parseEther("100")],
- [buyer2.address, ethers.parseEther("100")],
- ]
- const tree = StandardMerkleTree.of(values, ["address", "uint256"])
- await launchpadContract.setMerkleRoot(tree.root, { from: owner })
- await time.increase(100)
- // approve
- await buyTokenContract
- .connect(buyer1)
- .approve(await launchpadContract.getAddress(), ethers.parseEther("100"))
- await buyTokenContract
- .connect(buyer2)
- .approve(await launchpadContract.getAddress(), ethers.parseEther("100"))
- // buy
- await launchpadContract
- .connect(buyer1)
- .contributeWithERC20(ethers.parseEther("100"), ethers.parseEther("100"), tree.getProof(0))
- await launchpadContract
- .connect(buyer2)
- .contributeWithERC20(ethers.parseEther("100"), ethers.parseEther("100"), tree.getProof(1))
- await launchpadContract.enableClaimTokens(Math.floor(Date.now() / 1000) + 60 * 60 * 8)
- await time.increase(3600 * 8)
- // owner 提前提走所有ERC20
- await launchpadContract.withdrawPayments()
- await launchpadContract.connect(buyer1).claimTokens()
- //可能是个 bug 点,在 claim alive 的时候 是否允许 claim token
- await launchpadContract.withdrawRemainingTokens()
- await expect(launchpadContract.connect(buyer2).claimTokens()).to.be.reverted
- })
- it("should allow multiple users to claim tokens concurrently without interference", async () => {
- const { launchpadContract, saleTokenContract, owner } = await loadFixture(deployLaunchpadFixture)
- const tokenBalanceBefore = await saleTokenContract.balanceOf(owner)
- const saleTokenAddress = await saleTokenContract.getAddress()
- const now = Math.floor(Date.now() / 1000)
- const startTime = now + 60
- const endTime = now + 60 * 60 * 8
- const paymentTokens = ethers.ZeroAddress
- const paymentTokenPrice = ethers.parseEther("1")
- await launchpadContract.createSale(
- saleTokenAddress,
- tokenBalanceBefore,
- ethers.parseEther("100"), // contributionTarget
- startTime,
- endTime,
- paymentTokens,
- paymentTokenPrice,
- 0n // feeBps
- )
- const signers = await hre.ethers.getSigners()
- const values = [
- [signers[1].address, ethers.parseEther("10")],
- [signers[2].address, ethers.parseEther("10")],
- [signers[3].address, ethers.parseEther("10")],
- ]
- const tree = StandardMerkleTree.of(values, ["address", "uint256"])
- await launchpadContract.setMerkleRoot(tree.root, { from: owner })
- await time.increase(100)
- for (let i = 1; i <= 3; i++) {
- await launchpadContract
- .connect(signers[i])
- .contributeWithETH(values[i - 1][1].toString(), values[i - 1][1].toString(), tree.getProof(i - 1), {
- value: values[i - 1][1].toString(),
- })
- }
- await launchpadContract.enableClaimTokens(Math.floor(Date.now() / 1000) + 60 * 60 * 8)
- await time.increase(3600 * 8)
- // 多用户并发claim
- await Promise.all([
- launchpadContract.connect(signers[1]).claimTokens(),
- launchpadContract.connect(signers[2]).claimTokens(),
- launchpadContract.connect(signers[3]).claimTokens(),
- ])
- // 检查每个用户都正确领取
- for (let i = 1; i <= 3; i++) {
- expect(await saleTokenContract.balanceOf(signers[i].address)).to.equal(values[i - 1][1].toString())
- }
- })
- })
- })
|