LaunchpadOwner.ts 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696
  1. import { expect } from "chai"
  2. import hre, { ethers } from "hardhat"
  3. import { StandardMerkleTree } from "@openzeppelin/merkle-tree"
  4. import { loadFixture, time } from "@nomicfoundation/hardhat-toolbox/network-helpers"
  5. import { network } from "hardhat"
  6. let snapshotId: string
  7. describe("Launchpad", () => {
  8. const deployLaunchpadFixture = async () => {
  9. const signers = await hre.ethers.getSigners()
  10. // for (const signer of signers) {
  11. // await hre.network.provider.send("hardhat_setBalance", [
  12. // signer.address,
  13. // "0x3635C9ADC5DEA00000", // 1000 ETH
  14. // ])
  15. // }
  16. const owner = signers[0].address
  17. const Launchpad = await hre.ethers.getContractFactory("Launchpad")
  18. const Erc20Token = await hre.ethers.getContractFactory("BasicERC20")
  19. const launchpadContract = await Launchpad.deploy({
  20. from: owner,
  21. })
  22. const saleTokenContract = await Erc20Token.deploy("TestToken", "TT", owner, {
  23. from: owner,
  24. })
  25. const buyTokenContract = await Erc20Token.deploy("Usdt", "USDT", owner, {
  26. from: owner,
  27. })
  28. const launchpadAddress = await launchpadContract.getAddress()
  29. await saleTokenContract.mint(owner, ethers.parseEther("1000000"))
  30. await saleTokenContract.approve(launchpadAddress, ethers.MaxUint256)
  31. await buyTokenContract.mint(signers[1].address, ethers.parseEther("10000"))
  32. return {
  33. launchpadContract,
  34. saleTokenContract,
  35. buyTokenContract,
  36. owner,
  37. }
  38. }
  39. const createNativeSaleFixture = async () => {
  40. const { launchpadContract, saleTokenContract, owner } = await deployLaunchpadFixture()
  41. const balanceBefore = await saleTokenContract.balanceOf(owner)
  42. const saleTokenAddress = await saleTokenContract.getAddress()
  43. const now = Math.floor(Date.now() / 1000)
  44. const startTime = now + 60
  45. const endTime = now + 60 * 60 * 8
  46. const paymentTokens = ethers.ZeroAddress
  47. const paymentTokenPrice = ethers.parseEther("1")
  48. await launchpadContract.createSale(
  49. saleTokenAddress,
  50. balanceBefore,
  51. ethers.parseEther("100"), // contributionTarget
  52. startTime,
  53. endTime,
  54. paymentTokens,
  55. paymentTokenPrice,
  56. 0n // feeBps
  57. )
  58. return {
  59. launchpadContract,
  60. saleTokenContract,
  61. owner,
  62. }
  63. }
  64. describe("init function and setters", () => {
  65. it("Should Allow Owner to create a sale use native token", async () => {
  66. const { launchpadContract, saleTokenContract, owner } = await loadFixture(deployLaunchpadFixture)
  67. const balanceBefore = await saleTokenContract.balanceOf(owner)
  68. const saleTokenAddress = await saleTokenContract.getAddress()
  69. const now = Math.floor(Date.now() / 1000)
  70. let startTime = now + 60 // 1 minute from now
  71. const endTime = now + 60 * 60 * 8 // 1 hour from now
  72. const paymentTokens = ethers.ZeroAddress
  73. const paymentTokenPrice = ethers.parseEther("1")
  74. await launchpadContract.createSale(
  75. saleTokenAddress,
  76. balanceBefore,
  77. ethers.parseEther("100"), // contributionTarget
  78. startTime,
  79. endTime,
  80. paymentTokens,
  81. paymentTokenPrice,
  82. 0n // feeBps
  83. )
  84. startTime = Number(await launchpadContract.getStartTime())
  85. await time.increaseTo(startTime + 1)
  86. expect(await saleTokenContract.balanceOf(owner)).to.equal(0)
  87. expect(await launchpadContract.getSaleToken()).to.equal(saleTokenAddress)
  88. expect(await launchpadContract.getTotalTokensForSale()).to.equal(balanceBefore)
  89. expect(await launchpadContract.getStartTime()).to.equal(startTime)
  90. expect(await launchpadContract.getEndTime()).to.equal(endTime)
  91. expect(await launchpadContract.getPaymentTokens()).to.equal(ethers.ZeroAddress)
  92. expect(await launchpadContract.getPaymentTokenPrice()).to.equal(paymentTokenPrice)
  93. expect(await launchpadContract.totalContributionAmount()).to.equal(0n)
  94. })
  95. it("should Allow Owner reset payment info", async () => {
  96. const { launchpadContract, saleTokenContract, owner } = await loadFixture(createNativeSaleFixture)
  97. const paymentTokenBefore = await launchpadContract.getPaymentTokens()
  98. const paymentTokenPriceBefore = await launchpadContract.getPaymentTokenPrice()
  99. expect(await launchpadContract.getPaymentTokens()).to.equal(paymentTokenBefore)
  100. expect(await launchpadContract.getPaymentTokenPrice()).to.equal(paymentTokenPriceBefore)
  101. const newPaymentToken = await saleTokenContract.getAddress()
  102. const newPaymentTokenPrice = ethers.parseEther("1")
  103. await launchpadContract.setSalePayment(newPaymentToken, newPaymentTokenPrice, {
  104. from: owner,
  105. })
  106. expect(await launchpadContract.getPaymentTokens()).to.equal(newPaymentToken)
  107. expect(await launchpadContract.getPaymentTokenPrice()).to.equal(newPaymentTokenPrice)
  108. })
  109. it("should Allow Owner set MerkleRoot", async () => {
  110. const { launchpadContract, owner } = await loadFixture(createNativeSaleFixture)
  111. const signers = await hre.ethers.getSigners()
  112. const values = [
  113. [signers[0].address, ethers.parseEther("50")],
  114. [signers[1].address, ethers.parseEther("150")],
  115. [signers[2].address, ethers.parseEther("100")],
  116. ]
  117. const tree = StandardMerkleTree.of(values, ["address", "uint256"])
  118. const root = tree.root
  119. await launchpadContract.setMerkleRoot(root, { from: owner })
  120. expect(await launchpadContract.merkleRoot()).to.equal(tree.root)
  121. })
  122. it("should Allow Owner transfer Owner", async () => {
  123. const { launchpadContract, owner } = await createNativeSaleFixture()
  124. const signers = await hre.ethers.getSigners()
  125. const newOwner = signers[1].address
  126. await launchpadContract.setOwner(newOwner, { from: owner })
  127. expect(await launchpadContract.owner()).to.equal(newOwner)
  128. })
  129. })
  130. describe("after init buy function", () => {
  131. it("should Allow user use native token buy token", async () => {
  132. const { launchpadContract, owner } = await loadFixture(createNativeSaleFixture)
  133. const signers = await hre.ethers.getSigners()
  134. const values = [
  135. [signers[0].address, ethers.parseEther("50")],
  136. [signers[1].address, ethers.parseEther("10")],
  137. [signers[2].address, ethers.parseEther("100")],
  138. [signers[3].address, ethers.parseEther("200")],
  139. [signers[4].address, ethers.parseEther("300")],
  140. ]
  141. const tree = StandardMerkleTree.of(values, ["address", "uint256"])
  142. const root = tree.root
  143. const buyAmount = ethers.parseEther("1")
  144. const buyer = signers[1]
  145. let maxBuyAmount = 0n
  146. let proof = [] as string[]
  147. let wrongProof = [] as string[]
  148. for (const [i, v] of tree.entries()) {
  149. if (v[0] === buyer.address) {
  150. proof = tree.getProof(i)
  151. if (typeof v[1] === "bigint") {
  152. maxBuyAmount = v[1]
  153. }
  154. }
  155. if (v[0] === signers[2].address) {
  156. wrongProof = tree.getProof(i)
  157. }
  158. }
  159. await expect(
  160. launchpadContract.connect(buyer).contributeWithETH(buyAmount, maxBuyAmount, proof)
  161. ).to.be.revertedWith("MerkleRoot not initialized")
  162. await launchpadContract.setMerkleRoot(root, { from: owner })
  163. await time.increase(100)
  164. await expect(
  165. launchpadContract.connect(buyer).contributeWithETH(buyAmount, maxBuyAmount, proof)
  166. ).to.be.revertedWith("Must send enough ETH")
  167. await expect(
  168. launchpadContract.connect(buyer).contributeWithETH(buyAmount + maxBuyAmount, maxBuyAmount, proof, {
  169. value: ethers.parseEther("0.5"),
  170. })
  171. ).to.be.revertedWith("Buy amount exceeds max buy amount")
  172. await expect(
  173. launchpadContract.connect(buyer).contributeWithETH(buyAmount, maxBuyAmount, wrongProof, {
  174. value: ethers.parseEther("1"),
  175. })
  176. ).to.be.revertedWith("Invalid proof")
  177. await expect(
  178. launchpadContract.connect(buyer).contributeWithETH(buyAmount, maxBuyAmount, proof, {
  179. value: ethers.parseEther("0.5"),
  180. })
  181. ).to.be.revertedWith("Must send enough ETH")
  182. await expect(
  183. launchpadContract.connect(buyer).contributeWithETH(ethers.parseEther("50"), maxBuyAmount, proof, {
  184. value: ethers.parseEther("50"),
  185. })
  186. ).to.revertedWith("Buy amount exceeds max buy amount")
  187. //success once
  188. await expect(
  189. launchpadContract.connect(buyer).contributeWithETH(buyAmount, maxBuyAmount, proof, {
  190. value: ethers.parseEther("1"),
  191. })
  192. ).to.emit(launchpadContract, "Contributed")
  193. //make it success twice
  194. await expect(
  195. launchpadContract.connect(buyer).contributeWithETH(ethers.parseEther("4"), maxBuyAmount, proof, {
  196. value: ethers.parseEther("4"),
  197. })
  198. ).to.emit(launchpadContract, "Contributed")
  199. expect(await launchpadContract.getClaimableTokens(buyer.address)).to.equal(
  200. buyAmount + ethers.parseEther("4")
  201. )
  202. //failed by exceeding max buy amount
  203. await expect(
  204. launchpadContract.connect(buyer).contributeWithETH(ethers.parseEther("10"), maxBuyAmount, proof, {
  205. value: ethers.parseEther("10"),
  206. })
  207. ).to.be.revertedWith("Buy amount exceeds max buy amount")
  208. })
  209. it("should Allow user claim token after finished sale", async () => {
  210. const { launchpadContract, saleTokenContract, owner } = await loadFixture(createNativeSaleFixture)
  211. const signers = await hre.ethers.getSigners()
  212. const values = [
  213. [signers[0].address, ethers.parseEther("50")],
  214. [signers[1].address, ethers.parseEther("10")],
  215. [signers[2].address, ethers.parseEther("100")],
  216. [signers[3].address, ethers.parseEther("200")],
  217. [signers[4].address, ethers.parseEther("300")],
  218. ]
  219. const tree = StandardMerkleTree.of(values, ["address", "uint256"])
  220. const root = tree.root
  221. const buyAmount = ethers.parseEther("1")
  222. const buyer = signers[1]
  223. let maxBuyAmount = 0n
  224. let proof = [] as string[]
  225. for (const [i, v] of tree.entries()) {
  226. if (v[0] === buyer.address) {
  227. proof = tree.getProof(i)
  228. if (typeof v[1] === "bigint") {
  229. maxBuyAmount = v[1]
  230. }
  231. }
  232. }
  233. await launchpadContract.setMerkleRoot(root, { from: owner })
  234. await time.increase(100)
  235. await expect(
  236. launchpadContract.connect(buyer).contributeWithETH(buyAmount, maxBuyAmount, proof, {
  237. value: ethers.parseEther("1"),
  238. })
  239. ).to.emit(launchpadContract, "Contributed")
  240. expect(await launchpadContract.getClaimableTokens(buyer.address)).to.equal(buyAmount)
  241. await expect(launchpadContract.connect(buyer).claimTokens()).to.be.revertedWith(
  242. "Claiming tokens is not enabled"
  243. )
  244. expect(await launchpadContract.enableClaimTokens(Math.floor(Date.now() / 1000) + 60 * 60 * 8)).to.emit(
  245. launchpadContract,
  246. "EnableClaimToken"
  247. )
  248. await expect(launchpadContract.connect(buyer).claimTokens()).to.be.revertedWith(
  249. "Claiming tokens not started yet"
  250. )
  251. await time.increase(60 * 60 * 8 + 1) // increase time to finish sale
  252. await expect(launchpadContract.connect(signers[2]).claimTokens()).to.be.revertedWith("No tokens to claim")
  253. //Transfer token
  254. await expect(launchpadContract.connect(buyer).claimTokens()).to.emit(saleTokenContract, "Transfer")
  255. //Balance check
  256. expect(await saleTokenContract.balanceOf(buyer.address)).to.equal(buyAmount)
  257. })
  258. it("should Allow owner claim contribution token after finished sale", async () => {
  259. const { launchpadContract, saleTokenContract, owner } = await loadFixture(createNativeSaleFixture)
  260. const signers = await hre.ethers.getSigners()
  261. const values = [
  262. [signers[0].address, ethers.parseEther("50")],
  263. [signers[1].address, ethers.parseEther("10")],
  264. [signers[2].address, ethers.parseEther("100")],
  265. [signers[3].address, ethers.parseEther("200")],
  266. [signers[4].address, ethers.parseEther("300")],
  267. ]
  268. const tree = StandardMerkleTree.of(values, ["address", "uint256"])
  269. const root = tree.root
  270. const buyAmount = ethers.parseEther("1")
  271. const buyer = signers[1]
  272. let maxBuyAmount = 0n
  273. let proof = [] as string[]
  274. for (const [i, v] of tree.entries()) {
  275. if (v[0] === buyer.address) {
  276. proof = tree.getProof(i)
  277. if (typeof v[1] === "bigint") {
  278. maxBuyAmount = v[1]
  279. }
  280. }
  281. }
  282. await launchpadContract.setMerkleRoot(root, { from: owner })
  283. const launchpadAddress = await launchpadContract.getAddress()
  284. await time.increase(100)
  285. await expect(
  286. launchpadContract.connect(buyer).contributeWithETH(buyAmount, maxBuyAmount, proof, {
  287. value: ethers.parseEther("1"),
  288. })
  289. ).to.emit(launchpadContract, "Contributed")
  290. await expect(launchpadContract.withdrawPayments()).to.be.revertedWith("Sale is still active")
  291. await launchpadContract.enableClaimTokens(Math.floor(Date.now() / 1000) + 60 * 60 * 8)
  292. await time.increase(60 * 60 * 8 + 1) // increase time to finish sale
  293. await launchpadContract.connect(buyer).claimTokens()
  294. await expect(launchpadContract.connect(signers[2]).withdrawPayments()).to.revertedWith(
  295. "Only owner can call this function"
  296. )
  297. await expect(launchpadContract.connect(signers[2]).withdrawRemainingTokens()).to.revertedWith(
  298. "Only owner can call this function"
  299. )
  300. await launchpadContract.withdrawPayments()
  301. await launchpadContract.withdrawRemainingTokens()
  302. expect(await ethers.provider.getBalance(launchpadAddress)).to.equal(0n)
  303. expect(await saleTokenContract.balanceOf(launchpadAddress)).to.equal(0)
  304. })
  305. it("should allow user to buy and claim with ERC20 payment token", async () => {
  306. const { launchpadContract, saleTokenContract, buyTokenContract, owner } =
  307. await loadFixture(deployLaunchpadFixture)
  308. const signers = await hre.ethers.getSigners()
  309. const buyer = signers[1]
  310. // 创建ERC20众筹
  311. const saleTokenAddress = await saleTokenContract.getAddress()
  312. const buyTokenAddress = await buyTokenContract.getAddress()
  313. const now = Math.floor(Date.now() / 1000)
  314. const startTime = now + 100
  315. const endTime = now + 60 * 60 * 8
  316. const paymentTokenPrice = ethers.parseEther("1")
  317. const totalTokens = await saleTokenContract.balanceOf(owner)
  318. await launchpadContract.createSale(
  319. saleTokenAddress,
  320. totalTokens,
  321. totalTokens, // contributionTarget
  322. startTime,
  323. endTime,
  324. buyTokenAddress,
  325. paymentTokenPrice,
  326. 0n // feeBps
  327. )
  328. await time.increase(100)
  329. // Merkle tree
  330. const values = [[buyer.address, ethers.parseEther("100")]]
  331. const tree = StandardMerkleTree.of(values, ["address", "uint256"])
  332. const proof = tree.getProof(0)
  333. await launchpadContract.setMerkleRoot(tree.root, { from: owner })
  334. await time.increase(100)
  335. // approve
  336. await buyTokenContract
  337. .connect(buyer)
  338. .approve(await launchpadContract.getAddress(), ethers.parseEther("100"))
  339. // buy
  340. await expect(
  341. launchpadContract
  342. .connect(buyer)
  343. .contributeWithERC20(ethers.parseEther("10"), ethers.parseEther("100"), proof)
  344. ).to.emit(launchpadContract, "Contributed")
  345. // enable claim
  346. await launchpadContract.enableClaimTokens(Math.floor(Date.now() / 1000) + 60 * 60 * 8)
  347. await time.increase(60 * 60 * 8 + 1)
  348. // claim
  349. await expect(launchpadContract.connect(buyer).claimTokens()).to.emit(launchpadContract, "TokensClaimed")
  350. expect(await saleTokenContract.balanceOf(buyer.address)).to.equal(ethers.parseEther("10"))
  351. })
  352. it("should refund excess and distribute tokens proportionally when oversubscribed", async () => {
  353. const { launchpadContract, saleTokenContract, owner } = await loadFixture(deployLaunchpadFixture)
  354. const signers = await hre.ethers.getSigners()
  355. const tokenBalanceBefore = await saleTokenContract.balanceOf(owner)
  356. const saleTokenAddress = await saleTokenContract.getAddress()
  357. const now = Math.floor(Date.now() / 1000)
  358. const startTime = now + 60
  359. const endTime = now + 60 * 60 * 8
  360. const paymentTokens = ethers.ZeroAddress
  361. const paymentTokenPrice = ethers.parseEther("1")
  362. await launchpadContract.createSale(
  363. saleTokenAddress,
  364. tokenBalanceBefore,
  365. ethers.parseEther("100"), // contributionTarget
  366. startTime,
  367. endTime,
  368. paymentTokens,
  369. paymentTokenPrice,
  370. 0n // feeBps
  371. )
  372. // 设置白名单
  373. const values = [
  374. [signers[1].address, ethers.parseEther("100")],
  375. [signers[2].address, ethers.parseEther("100")],
  376. ]
  377. const tree = StandardMerkleTree.of(values, ["address", "uint256"])
  378. await launchpadContract.setMerkleRoot(tree.root, { from: owner })
  379. // 两人都买100,目标额度为100,实际总额200
  380. const proof1 = tree.getProof(0)
  381. const proof2 = tree.getProof(1)
  382. await time.increase(100)
  383. await launchpadContract
  384. .connect(signers[1])
  385. .contributeWithETH(ethers.parseEther("100"), ethers.parseEther("100"), proof1, {
  386. value: ethers.parseEther("100"),
  387. })
  388. await launchpadContract
  389. .connect(signers[2])
  390. .contributeWithETH(ethers.parseEther("100"), ethers.parseEther("100"), proof2, {
  391. value: ethers.parseEther("100"),
  392. })
  393. await launchpadContract.enableClaimTokens(Math.floor(Date.now() / 1000) + 60 * 60 * 8)
  394. await time.increase(3600 * 8)
  395. // 领取
  396. const balanceBefore = await ethers.provider.getBalance(signers[1].address)
  397. await launchpadContract.connect(signers[1]).claimTokens()
  398. // 检查领取的token数量为50(按比例分配
  399. const balanceAfter = await ethers.provider.getBalance(signers[1].address)
  400. expect(Number(ethers.formatEther(balanceAfter - balanceBefore))).to.closeTo(50, 0.001)
  401. // 断言退款金额接近50
  402. // ...可进一步断言
  403. })
  404. it("should deduct fee from user contribution when claiming tokens", async () => {
  405. const { launchpadContract, saleTokenContract, owner } = await loadFixture(deployLaunchpadFixture)
  406. const tokenBalanceBefore = await saleTokenContract.balanceOf(owner)
  407. const saleTokenAddress = await saleTokenContract.getAddress()
  408. const now = Math.floor(Date.now() / 1000)
  409. const startTime = now + 60
  410. const endTime = now + 60 * 60 * 8
  411. const paymentTokens = ethers.ZeroAddress
  412. const paymentTokenPrice = ethers.parseEther("1")
  413. await launchpadContract.createSale(
  414. saleTokenAddress,
  415. tokenBalanceBefore,
  416. ethers.parseEther("100"), // contributionTarget
  417. startTime,
  418. endTime,
  419. paymentTokens,
  420. paymentTokenPrice,
  421. 0n // feeBps
  422. )
  423. const signers = await hre.ethers.getSigners()
  424. const values = [[signers[1].address, ethers.parseEther("10")]]
  425. const tree = StandardMerkleTree.of(values, ["address", "uint256"])
  426. await launchpadContract.setMerkleRoot(tree.root, { from: owner })
  427. await launchpadContract.setFeeBps(500) // 5% fee
  428. await time.increase(100)
  429. await launchpadContract
  430. .connect(signers[1])
  431. .contributeWithETH(ethers.parseEther("10"), ethers.parseEther("10"), tree.getProof(0), {
  432. value: ethers.parseEther("10"),
  433. })
  434. await launchpadContract.enableClaimTokens(Math.floor(Date.now() / 1000) + 60 * 60 * 8)
  435. await time.increase(60 * 60 * 8 + 1)
  436. const before = await saleTokenContract.balanceOf(signers[1].address)
  437. await launchpadContract.connect(signers[1]).claimTokens()
  438. const after = await saleTokenContract.balanceOf(signers[1].address)
  439. // 断言实际到账 < 10
  440. expect(after - before).to.equal(ethers.parseEther("9.5")) // 5% fee
  441. })
  442. it("should not allow user to claim tokens twice", async () => {
  443. const { launchpadContract, saleTokenContract, owner } = await loadFixture(deployLaunchpadFixture)
  444. const tokenBalanceBefore = await saleTokenContract.balanceOf(owner)
  445. const saleTokenAddress = await saleTokenContract.getAddress()
  446. const now = Math.floor(Date.now() / 1000)
  447. const startTime = now + 60
  448. const endTime = now + 60 * 60 * 8
  449. const paymentTokens = ethers.ZeroAddress
  450. const paymentTokenPrice = ethers.parseEther("1")
  451. await launchpadContract.createSale(
  452. saleTokenAddress,
  453. tokenBalanceBefore,
  454. ethers.parseEther("100"), // contributionTarget
  455. startTime,
  456. endTime,
  457. paymentTokens,
  458. paymentTokenPrice,
  459. 0n // feeBps
  460. )
  461. const signers = await hre.ethers.getSigners()
  462. const values = [[signers[1].address, ethers.parseEther("10")]]
  463. const tree = StandardMerkleTree.of(values, ["address", "uint256"])
  464. await launchpadContract.setMerkleRoot(tree.root, { from: owner })
  465. await time.increase(100)
  466. await launchpadContract
  467. .connect(signers[1])
  468. .contributeWithETH(ethers.parseEther("10"), ethers.parseEther("10"), tree.getProof(0), {
  469. value: ethers.parseEther("10"),
  470. })
  471. await launchpadContract.enableClaimTokens(Math.floor(Date.now() / 1000) + 60 * 60 * 8)
  472. await time.increase(60 * 60 * 8)
  473. await launchpadContract.connect(signers[1]).claimTokens()
  474. await expect(launchpadContract.connect(signers[1]).claimTokens()).to.be.revertedWith("No tokens to claim")
  475. })
  476. it("should revert claim if contract has insufficient token balance", async () => {
  477. const { launchpadContract, saleTokenContract, owner } = await loadFixture(deployLaunchpadFixture)
  478. const tokenBalanceBefore = await saleTokenContract.balanceOf(owner)
  479. const saleTokenAddress = await saleTokenContract.getAddress()
  480. const now = Math.floor(Date.now() / 1000)
  481. const startTime = now + 60
  482. const endTime = now + 60 * 60 * 8
  483. const paymentTokens = ethers.ZeroAddress
  484. const paymentTokenPrice = ethers.parseEther("1")
  485. await launchpadContract.createSale(
  486. saleTokenAddress,
  487. tokenBalanceBefore,
  488. ethers.parseEther("100"), // contributionTarget
  489. startTime,
  490. endTime,
  491. paymentTokens,
  492. paymentTokenPrice,
  493. 0n // feeBps
  494. )
  495. const signers = await hre.ethers.getSigners()
  496. const values = [[signers[1].address, ethers.parseEther("10")]]
  497. const tree = StandardMerkleTree.of(values, ["address", "uint256"])
  498. await launchpadContract.setMerkleRoot(tree.root, { from: owner })
  499. await time.increase(100)
  500. await launchpadContract
  501. .connect(signers[1])
  502. .contributeWithETH(ethers.parseEther("10"), ethers.parseEther("10"), tree.getProof(0), {
  503. value: ethers.parseEther("10"),
  504. })
  505. await launchpadContract.enableClaimTokens(Math.floor(Date.now() / 1000) + 60 * 60 * 8)
  506. await time.increase(60 * 60 * 8)
  507. // owner 提前提走所有token
  508. await launchpadContract.withdrawRemainingTokens()
  509. await expect(launchpadContract.connect(signers[1]).claimTokens()).to.be.reverted // 断言revert
  510. })
  511. it("should revert claim if contract has insufficient ETH balance for refund", async () => {
  512. const { launchpadContract, saleTokenContract, owner } = await loadFixture(deployLaunchpadFixture)
  513. const tokenBalanceBefore = await saleTokenContract.balanceOf(owner)
  514. const saleTokenAddress = await saleTokenContract.getAddress()
  515. const now = Math.floor(Date.now() / 1000)
  516. const startTime = now + 60
  517. const endTime = now + 60 * 60 * 8
  518. const paymentTokens = ethers.ZeroAddress
  519. const paymentTokenPrice = ethers.parseEther("1")
  520. await launchpadContract.createSale(
  521. saleTokenAddress,
  522. tokenBalanceBefore,
  523. ethers.parseEther("100"), // contributionTarget
  524. startTime,
  525. endTime,
  526. paymentTokens,
  527. paymentTokenPrice,
  528. 0n // feeBps
  529. )
  530. const signers = await hre.ethers.getSigners()
  531. // 设置白名单
  532. const values = [
  533. [signers[1].address, ethers.parseEther("100")],
  534. [signers[2].address, ethers.parseEther("100")],
  535. ]
  536. const tree = StandardMerkleTree.of(values, ["address", "uint256"])
  537. await launchpadContract.setMerkleRoot(tree.root, { from: owner })
  538. await time.increase(100)
  539. // 两人都买100,目标额度为100,实际总额200,claim时应部分退款
  540. const proof1 = tree.getProof(0)
  541. const proof2 = tree.getProof(1)
  542. await launchpadContract
  543. .connect(signers[1])
  544. .contributeWithETH(ethers.parseEther("100"), ethers.parseEther("100"), proof1, {
  545. value: ethers.parseEther("100"),
  546. })
  547. await launchpadContract
  548. .connect(signers[2])
  549. .contributeWithETH(ethers.parseEther("100"), ethers.parseEther("100"), proof2, {
  550. value: ethers.parseEther("100"),
  551. })
  552. await launchpadContract.enableClaimTokens(Math.floor(Date.now() / 1000) + 60 * 60 * 8)
  553. await time.increase(60 * 60 * 8)
  554. // owner 提前提走所有ETH
  555. await launchpadContract.withdrawPayments()
  556. await expect(launchpadContract.connect(signers[1]).claimTokens()).to.be.reverted
  557. })
  558. it("should revert claim if contract has insufficient ERC20 balance for refund", async () => {
  559. const { launchpadContract, saleTokenContract, buyTokenContract, owner } =
  560. await loadFixture(deployLaunchpadFixture)
  561. const signers = await hre.ethers.getSigners()
  562. const buyer1 = signers[1]
  563. const buyer2 = signers[2]
  564. await buyTokenContract.mint(buyer1.address, ethers.parseEther("1000000000"))
  565. await buyTokenContract.mint(buyer2.address, ethers.parseEther("1000000000"))
  566. // 创建ERC20众筹
  567. const saleTokenAddress = await saleTokenContract.getAddress()
  568. const buyTokenAddress = await buyTokenContract.getAddress()
  569. const now = Math.floor(Date.now() / 1000)
  570. const startTime = now + 60
  571. const endTime = now + 60 * 60 * 8
  572. const paymentTokenPrice = ethers.parseEther("1")
  573. const totalTokens = await saleTokenContract.balanceOf(owner)
  574. await launchpadContract.createSale(
  575. saleTokenAddress,
  576. totalTokens,
  577. totalTokens, // contributionTarget
  578. startTime,
  579. endTime,
  580. buyTokenAddress,
  581. paymentTokenPrice,
  582. 0n // feeBps
  583. )
  584. const blockInfo = await ethers.provider.getBlock("latest")
  585. await time.increaseTo(blockInfo!.timestamp + 1)
  586. // Merkle tree
  587. const values = [
  588. [buyer1.address, ethers.parseEther("100")],
  589. [buyer2.address, ethers.parseEther("100")],
  590. ]
  591. const tree = StandardMerkleTree.of(values, ["address", "uint256"])
  592. await launchpadContract.setMerkleRoot(tree.root, { from: owner })
  593. await time.increase(100)
  594. // approve
  595. await buyTokenContract
  596. .connect(buyer1)
  597. .approve(await launchpadContract.getAddress(), ethers.parseEther("100"))
  598. await buyTokenContract
  599. .connect(buyer2)
  600. .approve(await launchpadContract.getAddress(), ethers.parseEther("100"))
  601. // buy
  602. await launchpadContract
  603. .connect(buyer1)
  604. .contributeWithERC20(ethers.parseEther("100"), ethers.parseEther("100"), tree.getProof(0))
  605. await launchpadContract
  606. .connect(buyer2)
  607. .contributeWithERC20(ethers.parseEther("100"), ethers.parseEther("100"), tree.getProof(1))
  608. await launchpadContract.enableClaimTokens(Math.floor(Date.now() / 1000) + 60 * 60 * 8)
  609. await time.increase(3600 * 8)
  610. // owner 提前提走所有ERC20
  611. await launchpadContract.withdrawPayments()
  612. await launchpadContract.connect(buyer1).claimTokens()
  613. //可能是个 bug 点,在 claim alive 的时候 是否允许 claim token
  614. await launchpadContract.withdrawRemainingTokens()
  615. await expect(launchpadContract.connect(buyer2).claimTokens()).to.be.reverted
  616. })
  617. it("should allow multiple users to claim tokens concurrently without interference", async () => {
  618. const { launchpadContract, saleTokenContract, owner } = await loadFixture(deployLaunchpadFixture)
  619. const tokenBalanceBefore = await saleTokenContract.balanceOf(owner)
  620. const saleTokenAddress = await saleTokenContract.getAddress()
  621. const now = Math.floor(Date.now() / 1000)
  622. const startTime = now + 60
  623. const endTime = now + 60 * 60 * 8
  624. const paymentTokens = ethers.ZeroAddress
  625. const paymentTokenPrice = ethers.parseEther("1")
  626. await launchpadContract.createSale(
  627. saleTokenAddress,
  628. tokenBalanceBefore,
  629. ethers.parseEther("100"), // contributionTarget
  630. startTime,
  631. endTime,
  632. paymentTokens,
  633. paymentTokenPrice,
  634. 0n // feeBps
  635. )
  636. const signers = await hre.ethers.getSigners()
  637. const values = [
  638. [signers[1].address, ethers.parseEther("10")],
  639. [signers[2].address, ethers.parseEther("10")],
  640. [signers[3].address, ethers.parseEther("10")],
  641. ]
  642. const tree = StandardMerkleTree.of(values, ["address", "uint256"])
  643. await launchpadContract.setMerkleRoot(tree.root, { from: owner })
  644. await time.increase(100)
  645. for (let i = 1; i <= 3; i++) {
  646. await launchpadContract
  647. .connect(signers[i])
  648. .contributeWithETH(values[i - 1][1].toString(), values[i - 1][1].toString(), tree.getProof(i - 1), {
  649. value: values[i - 1][1].toString(),
  650. })
  651. }
  652. await launchpadContract.enableClaimTokens(Math.floor(Date.now() / 1000) + 60 * 60 * 8)
  653. await time.increase(3600 * 8)
  654. // 多用户并发claim
  655. await Promise.all([
  656. launchpadContract.connect(signers[1]).claimTokens(),
  657. launchpadContract.connect(signers[2]).claimTokens(),
  658. launchpadContract.connect(signers[3]).claimTokens(),
  659. ])
  660. // 检查每个用户都正确领取
  661. for (let i = 1; i <= 3; i++) {
  662. expect(await saleTokenContract.balanceOf(signers[i].address)).to.equal(values[i - 1][1].toString())
  663. }
  664. })
  665. })
  666. })