ido-pool.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603
  1. const anchor = require("@coral-xyz/anchor");
  2. const { assert } = require("chai");
  3. const {
  4. ASSOCIATED_TOKEN_PROGRAM_ID,
  5. TOKEN_PROGRAM_ID,
  6. Token,
  7. } = require("@solana/spl-token");
  8. const {
  9. sleep,
  10. getTokenAccount,
  11. createMint,
  12. createTokenAccount,
  13. } = require("./utils");
  14. const { token } = require("@coral-xyz/anchor/dist/cjs/utils");
  15. describe("ido-pool", () => {
  16. const provider = anchor.AnchorProvider.local();
  17. // Configure the client to use the local cluster.
  18. anchor.setProvider(provider);
  19. const program = anchor.workspace.IdoPool;
  20. // All mints default to 6 decimal places.
  21. const watermelonIdoAmount = new anchor.BN(5000000);
  22. // These are all of the variables we assume exist in the world already and
  23. // are available to the client.
  24. let usdcMintAccount = null;
  25. let usdcMint = null;
  26. let watermelonMintAccount = null;
  27. let watermelonMint = null;
  28. let idoAuthorityUsdc = null;
  29. let idoAuthorityWatermelon = null;
  30. it("Initializes the state-of-the-world", async () => {
  31. usdcMintAccount = await createMint(provider);
  32. watermelonMintAccount = await createMint(provider);
  33. usdcMint = usdcMintAccount.publicKey;
  34. watermelonMint = watermelonMintAccount.publicKey;
  35. idoAuthorityUsdc = await createTokenAccount(
  36. provider,
  37. usdcMint,
  38. provider.wallet.publicKey
  39. );
  40. idoAuthorityWatermelon = await createTokenAccount(
  41. provider,
  42. watermelonMint,
  43. provider.wallet.publicKey
  44. );
  45. // Mint Watermelon tokens that will be distributed from the IDO pool.
  46. await watermelonMintAccount.mintTo(
  47. idoAuthorityWatermelon,
  48. provider.wallet.publicKey,
  49. [],
  50. watermelonIdoAmount.toString()
  51. );
  52. idoAuthority_watermelon_account = await getTokenAccount(
  53. provider,
  54. idoAuthorityWatermelon
  55. );
  56. assert.isTrue(
  57. idoAuthority_watermelon_account.amount.eq(watermelonIdoAmount)
  58. );
  59. });
  60. // These are all variables the client will need to create in order to
  61. // initialize the IDO pool
  62. let idoTimes;
  63. let idoName = "test_ido";
  64. it("Initializes the IDO pool", async () => {
  65. let bumps = new PoolBumps();
  66. const [idoAccount, idoAccountBump] =
  67. await anchor.web3.PublicKey.findProgramAddress(
  68. [Buffer.from(idoName)],
  69. program.programId
  70. );
  71. bumps.idoAccount = idoAccountBump;
  72. const [redeemableMint, redeemableMintBump] =
  73. await anchor.web3.PublicKey.findProgramAddress(
  74. [Buffer.from(idoName), Buffer.from("redeemable_mint")],
  75. program.programId
  76. );
  77. bumps.redeemableMint = redeemableMintBump;
  78. const [poolWatermelon, poolWatermelonBump] =
  79. await anchor.web3.PublicKey.findProgramAddress(
  80. [Buffer.from(idoName), Buffer.from("pool_watermelon")],
  81. program.programId
  82. );
  83. bumps.poolWatermelon = poolWatermelonBump;
  84. const [poolUsdc, poolUsdcBump] =
  85. await anchor.web3.PublicKey.findProgramAddress(
  86. [Buffer.from(idoName), Buffer.from("pool_usdc")],
  87. program.programId
  88. );
  89. bumps.poolUsdc = poolUsdcBump;
  90. idoTimes = new IdoTimes();
  91. const nowBn = new anchor.BN(Date.now() / 1000);
  92. idoTimes.startIdo = nowBn.add(new anchor.BN(5));
  93. idoTimes.endDeposits = nowBn.add(new anchor.BN(10));
  94. idoTimes.endIdo = nowBn.add(new anchor.BN(15));
  95. idoTimes.endEscrow = nowBn.add(new anchor.BN(16));
  96. await program.rpc.initializePool(
  97. idoName,
  98. bumps,
  99. watermelonIdoAmount,
  100. idoTimes,
  101. {
  102. accounts: {
  103. idoAuthority: provider.wallet.publicKey,
  104. idoAuthorityWatermelon,
  105. idoAccount,
  106. watermelonMint,
  107. usdcMint,
  108. redeemableMint,
  109. poolWatermelon,
  110. poolUsdc,
  111. systemProgram: anchor.web3.SystemProgram.programId,
  112. tokenProgram: TOKEN_PROGRAM_ID,
  113. },
  114. }
  115. );
  116. idoAuthorityWatermelonAccount = await getTokenAccount(
  117. provider,
  118. idoAuthorityWatermelon
  119. );
  120. assert.isTrue(idoAuthorityWatermelonAccount.amount.eq(new anchor.BN(0)));
  121. });
  122. // We're going to need to start using the associated program account for creating token accounts
  123. // if not in testing, then definitely in production.
  124. let userUsdc = null;
  125. // 10 usdc
  126. const firstDeposit = new anchor.BN(10_000_349);
  127. it("Exchanges user USDC for redeemable tokens", async () => {
  128. // Wait until the IDO has opened.
  129. if (Date.now() < idoTimes.startIdo.toNumber() * 1000) {
  130. await sleep(idoTimes.startIdo.toNumber() * 1000 - Date.now() + 2000);
  131. }
  132. const [idoAccount] = await anchor.web3.PublicKey.findProgramAddress(
  133. [Buffer.from(idoName)],
  134. program.programId
  135. );
  136. const [redeemableMint] = await anchor.web3.PublicKey.findProgramAddress(
  137. [Buffer.from(idoName), Buffer.from("redeemable_mint")],
  138. program.programId
  139. );
  140. const [poolUsdc] = await anchor.web3.PublicKey.findProgramAddress(
  141. [Buffer.from(idoName), Buffer.from("pool_usdc")],
  142. program.programId
  143. );
  144. userUsdc = await Token.getAssociatedTokenAddress(
  145. ASSOCIATED_TOKEN_PROGRAM_ID,
  146. TOKEN_PROGRAM_ID,
  147. usdcMint,
  148. program.provider.wallet.publicKey
  149. );
  150. // Get the instructions to add to the RPC call
  151. let createUserUsdcInstr = Token.createAssociatedTokenAccountInstruction(
  152. ASSOCIATED_TOKEN_PROGRAM_ID,
  153. TOKEN_PROGRAM_ID,
  154. usdcMint,
  155. userUsdc,
  156. program.provider.wallet.publicKey,
  157. program.provider.wallet.publicKey
  158. );
  159. let createUserUsdcTrns = new anchor.web3.Transaction().add(
  160. createUserUsdcInstr
  161. );
  162. await provider.sendAndConfirm(createUserUsdcTrns);
  163. await usdcMintAccount.mintTo(
  164. userUsdc,
  165. provider.wallet.publicKey,
  166. [],
  167. firstDeposit.toString()
  168. );
  169. // Check if we inited correctly
  170. userUsdcAccount = await getTokenAccount(provider, userUsdc);
  171. assert.isTrue(userUsdcAccount.amount.eq(firstDeposit));
  172. const [userRedeemable] = await anchor.web3.PublicKey.findProgramAddress(
  173. [
  174. provider.wallet.publicKey.toBuffer(),
  175. Buffer.from(idoName),
  176. Buffer.from("user_redeemable"),
  177. ],
  178. program.programId
  179. );
  180. try {
  181. const tx = await program.rpc.exchangeUsdcForRedeemable(firstDeposit, {
  182. accounts: {
  183. userAuthority: provider.wallet.publicKey,
  184. userUsdc,
  185. userRedeemable,
  186. idoAccount,
  187. usdcMint,
  188. redeemableMint,
  189. watermelonMint,
  190. poolUsdc,
  191. tokenProgram: TOKEN_PROGRAM_ID,
  192. },
  193. instructions: [
  194. program.instruction.initUserRedeemable({
  195. accounts: {
  196. userAuthority: provider.wallet.publicKey,
  197. userRedeemable,
  198. idoAccount,
  199. redeemableMint,
  200. systemProgram: anchor.web3.SystemProgram.programId,
  201. tokenProgram: TOKEN_PROGRAM_ID,
  202. },
  203. }),
  204. ],
  205. });
  206. } catch (err) {
  207. console.log("This is the error message", err.toString());
  208. }
  209. poolUsdcAccount = await getTokenAccount(provider, poolUsdc);
  210. assert.isTrue(poolUsdcAccount.amount.eq(firstDeposit));
  211. userRedeemableAccount = await getTokenAccount(provider, userRedeemable);
  212. assert.isTrue(userRedeemableAccount.amount.eq(firstDeposit));
  213. });
  214. // 23 usdc
  215. const secondDeposit = new anchor.BN(23_000_672);
  216. let totalPoolUsdc, secondUserKeypair, secondUserUsdc;
  217. it("Exchanges a second users USDC for redeemable tokens", async () => {
  218. const [idoAccount] = await anchor.web3.PublicKey.findProgramAddress(
  219. [Buffer.from(idoName)],
  220. program.programId
  221. );
  222. const [redeemableMint] = await anchor.web3.PublicKey.findProgramAddress(
  223. [Buffer.from(idoName), Buffer.from("redeemable_mint")],
  224. program.programId
  225. );
  226. const [poolUsdc] = await anchor.web3.PublicKey.findProgramAddress(
  227. [Buffer.from(idoName), Buffer.from("pool_usdc")],
  228. program.programId
  229. );
  230. secondUserKeypair = anchor.web3.Keypair.generate();
  231. transferSolInstr = anchor.web3.SystemProgram.transfer({
  232. fromPubkey: provider.wallet.publicKey,
  233. lamports: 100_000_000_000, // 100 sol
  234. toPubkey: secondUserKeypair.publicKey,
  235. });
  236. secondUserUsdc = await Token.getAssociatedTokenAddress(
  237. ASSOCIATED_TOKEN_PROGRAM_ID,
  238. TOKEN_PROGRAM_ID,
  239. usdcMint,
  240. secondUserKeypair.publicKey
  241. );
  242. createSecondUserUsdcInstr = Token.createAssociatedTokenAccountInstruction(
  243. ASSOCIATED_TOKEN_PROGRAM_ID,
  244. TOKEN_PROGRAM_ID,
  245. usdcMint,
  246. secondUserUsdc,
  247. secondUserKeypair.publicKey,
  248. provider.wallet.publicKey
  249. );
  250. let createSecondUserUsdcTrns = new anchor.web3.Transaction();
  251. createSecondUserUsdcTrns.add(transferSolInstr);
  252. createSecondUserUsdcTrns.add(createSecondUserUsdcInstr);
  253. await provider.sendAndConfirm(createSecondUserUsdcTrns);
  254. await usdcMintAccount.mintTo(
  255. secondUserUsdc,
  256. provider.wallet.publicKey,
  257. [],
  258. secondDeposit.toString()
  259. );
  260. // Checking the transfer went through
  261. secondUserUsdcAccount = await getTokenAccount(provider, secondUserUsdc);
  262. assert.isTrue(secondUserUsdcAccount.amount.eq(secondDeposit));
  263. const [secondUserRedeemable] =
  264. await anchor.web3.PublicKey.findProgramAddress(
  265. [
  266. secondUserKeypair.publicKey.toBuffer(),
  267. Buffer.from(idoName),
  268. Buffer.from("user_redeemable"),
  269. ],
  270. program.programId
  271. );
  272. await program.rpc.exchangeUsdcForRedeemable(secondDeposit, {
  273. accounts: {
  274. userAuthority: secondUserKeypair.publicKey,
  275. userUsdc: secondUserUsdc,
  276. userRedeemable: secondUserRedeemable,
  277. idoAccount,
  278. usdcMint,
  279. redeemableMint,
  280. watermelonMint,
  281. poolUsdc,
  282. tokenProgram: TOKEN_PROGRAM_ID,
  283. },
  284. instructions: [
  285. program.instruction.initUserRedeemable({
  286. accounts: {
  287. userAuthority: secondUserKeypair.publicKey,
  288. userRedeemable: secondUserRedeemable,
  289. idoAccount,
  290. redeemableMint,
  291. systemProgram: anchor.web3.SystemProgram.programId,
  292. tokenProgram: TOKEN_PROGRAM_ID,
  293. },
  294. }),
  295. ],
  296. signers: [secondUserKeypair],
  297. });
  298. secondUserRedeemableAccount = await getTokenAccount(
  299. provider,
  300. secondUserRedeemable
  301. );
  302. assert.isTrue(secondUserRedeemableAccount.amount.eq(secondDeposit));
  303. totalPoolUsdc = firstDeposit.add(secondDeposit);
  304. poolUsdcAccount = await getTokenAccount(provider, poolUsdc);
  305. assert.isTrue(poolUsdcAccount.amount.eq(totalPoolUsdc));
  306. });
  307. const firstWithdrawal = new anchor.BN(2_000_000);
  308. it("Exchanges user Redeemable tokens for USDC", async () => {
  309. const [idoAccount] = await anchor.web3.PublicKey.findProgramAddress(
  310. [Buffer.from(idoName)],
  311. program.programId
  312. );
  313. const [redeemableMint] = await anchor.web3.PublicKey.findProgramAddress(
  314. [Buffer.from(idoName), Buffer.from("redeemable_mint")],
  315. program.programId
  316. );
  317. const [poolUsdc] = await anchor.web3.PublicKey.findProgramAddress(
  318. [Buffer.from(idoName), Buffer.from("pool_usdc")],
  319. program.programId
  320. );
  321. const [userRedeemable] = await anchor.web3.PublicKey.findProgramAddress(
  322. [
  323. provider.wallet.publicKey.toBuffer(),
  324. Buffer.from(idoName),
  325. Buffer.from("user_redeemable"),
  326. ],
  327. program.programId
  328. );
  329. const [escrowUsdc] = await anchor.web3.PublicKey.findProgramAddress(
  330. [
  331. provider.wallet.publicKey.toBuffer(),
  332. Buffer.from(idoName),
  333. Buffer.from("escrow_usdc"),
  334. ],
  335. program.programId
  336. );
  337. await program.rpc.exchangeRedeemableForUsdc(firstWithdrawal, {
  338. accounts: {
  339. userAuthority: provider.wallet.publicKey,
  340. escrowUsdc,
  341. userRedeemable,
  342. idoAccount,
  343. usdcMint,
  344. redeemableMint,
  345. watermelonMint,
  346. poolUsdc,
  347. tokenProgram: TOKEN_PROGRAM_ID,
  348. },
  349. instructions: [
  350. program.instruction.initEscrowUsdc({
  351. accounts: {
  352. userAuthority: provider.wallet.publicKey,
  353. escrowUsdc,
  354. idoAccount,
  355. usdcMint,
  356. systemProgram: anchor.web3.SystemProgram.programId,
  357. tokenProgram: TOKEN_PROGRAM_ID,
  358. },
  359. }),
  360. ],
  361. });
  362. totalPoolUsdc = totalPoolUsdc.sub(firstWithdrawal);
  363. poolUsdcAccount = await getTokenAccount(provider, poolUsdc);
  364. assert.isTrue(poolUsdcAccount.amount.eq(totalPoolUsdc));
  365. escrowUsdcAccount = await getTokenAccount(provider, escrowUsdc);
  366. assert.isTrue(escrowUsdcAccount.amount.eq(firstWithdrawal));
  367. });
  368. it("Exchanges user Redeemable tokens for watermelon", async () => {
  369. // Wait until the IDO has ended.
  370. if (Date.now() < idoTimes.endIdo.toNumber() * 1000) {
  371. await sleep(idoTimes.endIdo.toNumber() * 1000 - Date.now() + 3000);
  372. }
  373. const [idoAccount] = await anchor.web3.PublicKey.findProgramAddress(
  374. [Buffer.from(idoName)],
  375. program.programId
  376. );
  377. const [poolWatermelon] = await anchor.web3.PublicKey.findProgramAddress(
  378. [Buffer.from(idoName), Buffer.from("pool_watermelon")],
  379. program.programId
  380. );
  381. const [redeemableMint] = await anchor.web3.PublicKey.findProgramAddress(
  382. [Buffer.from(idoName), Buffer.from("redeemable_mint")],
  383. program.programId
  384. );
  385. const [userRedeemable] = await anchor.web3.PublicKey.findProgramAddress(
  386. [
  387. provider.wallet.publicKey.toBuffer(),
  388. Buffer.from(idoName),
  389. Buffer.from("user_redeemable"),
  390. ],
  391. program.programId
  392. );
  393. let firstUserRedeemable = firstDeposit.sub(firstWithdrawal);
  394. // TODO we've been lazy here and not used an ATA as we did with USDC
  395. userWatermelon = await createTokenAccount(
  396. provider,
  397. watermelonMint,
  398. provider.wallet.publicKey
  399. );
  400. await program.rpc.exchangeRedeemableForWatermelon(firstUserRedeemable, {
  401. accounts: {
  402. payer: provider.wallet.publicKey,
  403. userAuthority: provider.wallet.publicKey,
  404. userWatermelon,
  405. userRedeemable,
  406. idoAccount,
  407. watermelonMint,
  408. redeemableMint,
  409. poolWatermelon,
  410. tokenProgram: TOKEN_PROGRAM_ID,
  411. },
  412. });
  413. poolWatermelonAccount = await getTokenAccount(provider, poolWatermelon);
  414. let redeemedWatermelon = firstUserRedeemable
  415. .mul(watermelonIdoAmount)
  416. .div(totalPoolUsdc);
  417. let remainingWatermelon = watermelonIdoAmount.sub(redeemedWatermelon);
  418. assert.isTrue(poolWatermelonAccount.amount.eq(remainingWatermelon));
  419. userWatermelonAccount = await getTokenAccount(provider, userWatermelon);
  420. assert.isTrue(userWatermelonAccount.amount.eq(redeemedWatermelon));
  421. });
  422. it("Exchanges second user's Redeemable tokens for watermelon", async () => {
  423. const [idoAccount] = await anchor.web3.PublicKey.findProgramAddress(
  424. [Buffer.from(idoName)],
  425. program.programId
  426. );
  427. const [redeemableMint] = await anchor.web3.PublicKey.findProgramAddress(
  428. [Buffer.from(idoName), Buffer.from("redeemable_mint")],
  429. program.programId
  430. );
  431. const [secondUserRedeemable] =
  432. await anchor.web3.PublicKey.findProgramAddress(
  433. [
  434. secondUserKeypair.publicKey.toBuffer(),
  435. Buffer.from(idoName),
  436. Buffer.from("user_redeemable"),
  437. ],
  438. program.programId
  439. );
  440. const [poolWatermelon] = await anchor.web3.PublicKey.findProgramAddress(
  441. [Buffer.from(idoName), Buffer.from("pool_watermelon")],
  442. program.programId
  443. );
  444. secondUserWatermelon = await createTokenAccount(
  445. provider,
  446. watermelonMint,
  447. secondUserKeypair.publicKey
  448. );
  449. await program.rpc.exchangeRedeemableForWatermelon(secondDeposit, {
  450. accounts: {
  451. payer: provider.wallet.publicKey,
  452. userAuthority: secondUserKeypair.publicKey,
  453. userWatermelon: secondUserWatermelon,
  454. userRedeemable: secondUserRedeemable,
  455. idoAccount,
  456. watermelonMint,
  457. redeemableMint,
  458. poolWatermelon,
  459. tokenProgram: TOKEN_PROGRAM_ID,
  460. },
  461. });
  462. poolWatermelonAccount = await getTokenAccount(provider, poolWatermelon);
  463. assert.isTrue(poolWatermelonAccount.amount.eq(new anchor.BN(0)));
  464. });
  465. it("Withdraws total USDC from pool account", async () => {
  466. const [idoAccount] = await anchor.web3.PublicKey.findProgramAddress(
  467. [Buffer.from(idoName)],
  468. program.programId
  469. );
  470. const [poolUsdc] = await anchor.web3.PublicKey.findProgramAddress(
  471. [Buffer.from(idoName), Buffer.from("pool_usdc")],
  472. program.programId
  473. );
  474. await program.rpc.withdrawPoolUsdc({
  475. accounts: {
  476. idoAuthority: provider.wallet.publicKey,
  477. idoAuthorityUsdc,
  478. idoAccount,
  479. usdcMint,
  480. watermelonMint,
  481. poolUsdc,
  482. tokenProgram: TOKEN_PROGRAM_ID,
  483. },
  484. });
  485. poolUsdcAccount = await getTokenAccount(provider, poolUsdc);
  486. assert.isTrue(poolUsdcAccount.amount.eq(new anchor.BN(0)));
  487. idoAuthorityUsdcAccount = await getTokenAccount(provider, idoAuthorityUsdc);
  488. assert.isTrue(idoAuthorityUsdcAccount.amount.eq(totalPoolUsdc));
  489. });
  490. it("Withdraws USDC from the escrow account after waiting period is over", async () => {
  491. // Wait until the escrow period is over.
  492. if (Date.now() < idoTimes.endEscrow.toNumber() * 1000 + 1000) {
  493. await sleep(idoTimes.endEscrow.toNumber() * 1000 - Date.now() + 4000);
  494. }
  495. const [idoAccount] = await anchor.web3.PublicKey.findProgramAddress(
  496. [Buffer.from(idoName)],
  497. program.programId
  498. );
  499. const [escrowUsdc] = await anchor.web3.PublicKey.findProgramAddress(
  500. [
  501. provider.wallet.publicKey.toBuffer(),
  502. Buffer.from(idoName),
  503. Buffer.from("escrow_usdc"),
  504. ],
  505. program.programId
  506. );
  507. await program.rpc.withdrawFromEscrow(firstWithdrawal, {
  508. accounts: {
  509. payer: provider.wallet.publicKey,
  510. userAuthority: provider.wallet.publicKey,
  511. userUsdc,
  512. escrowUsdc,
  513. idoAccount,
  514. usdcMint,
  515. tokenProgram: TOKEN_PROGRAM_ID,
  516. },
  517. });
  518. userUsdcAccount = await getTokenAccount(provider, userUsdc);
  519. assert.isTrue(userUsdcAccount.amount.eq(firstWithdrawal));
  520. });
  521. function PoolBumps() {
  522. this.idoAccount;
  523. this.redeemableMint;
  524. this.poolWatermelon;
  525. this.poolUsdc;
  526. }
  527. function IdoTimes() {
  528. this.startIdo;
  529. this.endDeposts;
  530. this.endIdo;
  531. this.endEscrow;
  532. }
  533. });