import { expect } from "chai" import hre, { ethers } from "hardhat" import { StandardMerkleTree } from "@openzeppelin/merkle-tree" import { time } from "@nomicfoundation/hardhat-toolbox/network-helpers" describe("Launchpad", () => { async function deployLaunchpadFixture() { const signers = await hre.ethers.getSigners() 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(signers[0].address, ethers.parseEther("1000000")) await saleTokenContract.approve(launchpadAddress, ethers.MaxUint256) await buyTokenContract.mint(signers[1].address, ethers.parseEther("10000")) return { launchpadContract, saleTokenContract, buyTokenContract, owner, } } async function createNativeSaleFixture() { const { launchpadContract, saleTokenContract, owner } = await deployLaunchpadFixture() const balanceBefore = await saleTokenContract.balanceOf(owner) const saleTokenAddress = await saleTokenContract.getAddress() const startTime = Math.floor(Date.now() / 1000) + 60 // 1 minute from now const endTime = Math.floor(Date.now() / 1000) + 60 * 60 // 1 hour from now const paymentTokens = ethers.ZeroAddress const paymentTokenPrice = ethers.parseEther("1") await launchpadContract.createSale( saleTokenAddress, balanceBefore, startTime, endTime, paymentTokens, paymentTokenPrice ) 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 deployLaunchpadFixture() const balanceBefore = await saleTokenContract.balanceOf(owner) const saleTokenAddress = await saleTokenContract.getAddress() const startTime = Math.floor(Date.now() / 1000) + 60 // 1 minute from now const endTime = Math.floor(Date.now() / 1000) + 60 * 60 // 1 hour from now const paymentTokens = ethers.ZeroAddress const paymentTokenPrice = ethers.parseEther("1") / 2500n await launchpadContract.createSale( saleTokenAddress, balanceBefore, startTime, endTime, paymentTokens, paymentTokenPrice ) expect(await saleTokenContract.balanceOf(owner)).to.equal(0) expect(await launchpadContract.getSaleToken()).to.equal(saleTokenAddress) expect(await launchpadContract.getTotalTokens()).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.getTokensSold()).to.equal(0n) }) it("should Allow Owner reset payment info", async () => { const { launchpadContract, saleTokenContract, owner } = await 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 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 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).contributeETH(buyAmount, maxBuyAmount, proof) ).to.be.revertedWith("MerkleRoot not initialized") await launchpadContract.setMerkleRoot(root, { from: owner }) await expect( launchpadContract.connect(buyer).contributeETH(buyAmount, maxBuyAmount, proof) ).to.be.revertedWith("Sale is not active") await time.increase(100) await expect( launchpadContract.connect(buyer).contributeETH(buyAmount + maxBuyAmount, maxBuyAmount, proof, { value: ethers.parseEther("0.5"), }) ).to.be.revertedWith("Buy amount exceeds max buy amount") await expect( launchpadContract.connect(buyer).contributeETH(buyAmount, maxBuyAmount, wrongProof, { value: ethers.parseEther("0.5"), }) ).to.be.revertedWith("Invalid proof") await expect( launchpadContract.connect(buyer).contributeETH(buyAmount, maxBuyAmount, proof, { value: ethers.parseEther("0.5"), }) ).to.be.revertedWith("Not enough ETH sent") await expect( launchpadContract.connect(buyer).contributeETH(ethers.parseEther("50"), maxBuyAmount, proof, { value: ethers.parseEther("50"), }) ).to.revertedWith("Buy amount exceeds max buy amount") //success once await expect( launchpadContract.connect(buyer).contributeETH(buyAmount, maxBuyAmount, proof, { value: ethers.parseEther("1"), }) ).to.emit(launchpadContract, "Contributed") //make it success twice await expect( launchpadContract.connect(buyer).contributeETH(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).contributeETH(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 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).contributeETH(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)).to.emit( launchpadContract, "EnableClaimToken" ) await expect(launchpadContract.connect(buyer).claimTokens()).to.be.revertedWith( "Claiming tokens not started yet" ) await time.increase(60 * 60 + 100) // 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 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) const launchpadAddress = await launchpadContract.getAddress() await expect( launchpadContract.connect(buyer).contributeETH(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) await time.increase(60 * 60 + 100) // increase time to finish sale await launchpadContract.connect(buyer).claimTokens() //todo 写不动了 接下来测试 非 owner withdraw token/钱 owner 的 withdraw token/钱 await expect(launchpadContract.connect(signers[2]).withdrawPayments()).to.revertedWith( "Only owner can call this function" ) await expect(launchpadContract.withdrawPayments()).to.emit(launchpadContract, "WithdrawPayments") expect(await ethers.provider.getBalance(launchpadAddress)).to.equal(0n) await expect(launchpadContract.connect(signers[2]).withdrawRemainingTokens()).to.revertedWith( "Only owner can call this function" ) await expect(launchpadContract.withdrawRemainingTokens()).to.emit( launchpadContract, "WithdrawRemainingTokens" ) expect(await saleTokenContract.balanceOf(launchpadAddress)).to.equal(0) }) }) })