ido-pool.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. const anchor = require("@project-serum/anchor");
  2. const assert = require("assert");
  3. const {
  4. TOKEN_PROGRAM_ID,
  5. sleep,
  6. getTokenAccount,
  7. createMint,
  8. createTokenAccount,
  9. } = require("./utils");
  10. describe("ido-pool", () => {
  11. const provider = anchor.Provider.local();
  12. // Configure the client to use the local cluster.
  13. anchor.setProvider(provider);
  14. const program = anchor.workspace.IdoPool;
  15. // All mints default to 6 decimal places.
  16. const watermelonIdoAmount = new anchor.BN(5000000);
  17. // These are all of the variables we assume exist in the world already and
  18. // are available to the client.
  19. let usdcMintToken = null;
  20. let usdcMint = null;
  21. let watermelonMintToken = null;
  22. let watermelonMint = null;
  23. let creatorUsdc = null;
  24. let creatorWatermelon = null;
  25. it("Initializes the state-of-the-world", async () => {
  26. usdcMintToken = await createMint(provider);
  27. watermelonMintToken = await createMint(provider);
  28. usdcMint = usdcMintToken.publicKey;
  29. watermelonMint = watermelonMintToken.publicKey;
  30. creatorUsdc = await createTokenAccount(
  31. provider,
  32. usdcMint,
  33. provider.wallet.publicKey
  34. );
  35. creatorWatermelon = await createTokenAccount(
  36. provider,
  37. watermelonMint,
  38. provider.wallet.publicKey
  39. );
  40. // Mint Watermelon tokens the will be distributed from the IDO pool.
  41. await watermelonMintToken.mintTo(
  42. creatorWatermelon,
  43. provider.wallet.publicKey,
  44. [],
  45. watermelonIdoAmount.toString(),
  46. );
  47. creator_watermelon_account = await getTokenAccount(
  48. provider,
  49. creatorWatermelon
  50. );
  51. assert.ok(creator_watermelon_account.amount.eq(watermelonIdoAmount));
  52. });
  53. // These are all variables the client will have to create to initialize the
  54. // IDO pool
  55. let poolSigner = null;
  56. let redeemableMintToken = null;
  57. let redeemableMint = null;
  58. let poolWatermelon = null;
  59. let poolUsdc = null;
  60. let poolAccount = null;
  61. let startIdoTs = null;
  62. let endDepositsTs = null;
  63. let endIdoTs = null;
  64. it("Initializes the IDO pool", async () => {
  65. // We use the watermelon mint address as the seed, could use something else though.
  66. const [_poolSigner, nonce] = await anchor.web3.PublicKey.findProgramAddress(
  67. [watermelonMint.toBuffer()],
  68. program.programId
  69. );
  70. poolSigner = _poolSigner;
  71. // Pool doesn't need a Redeemable SPL token account because it only
  72. // burns and mints redeemable tokens, it never stores them.
  73. redeemableMintToken = await createMint(provider, poolSigner);
  74. redeemableMint = redeemableMintToken.publicKey;
  75. poolWatermelon = await createTokenAccount(
  76. provider,
  77. watermelonMint,
  78. poolSigner
  79. );
  80. poolUsdc = await createTokenAccount(provider, usdcMint, poolSigner);
  81. poolAccount = anchor.web3.Keypair.generate();
  82. const nowBn = new anchor.BN(Date.now() / 1000);
  83. startIdoTs = nowBn.add(new anchor.BN(5));
  84. endDepositsTs = nowBn.add(new anchor.BN(10));
  85. endIdoTs = nowBn.add(new anchor.BN(15));
  86. // Atomically create the new account and initialize it with the program.
  87. await program.rpc.initializePool(
  88. watermelonIdoAmount,
  89. nonce,
  90. startIdoTs,
  91. endDepositsTs,
  92. endIdoTs,
  93. {
  94. accounts: {
  95. poolAccount: poolAccount.publicKey,
  96. poolSigner,
  97. distributionAuthority: provider.wallet.publicKey,
  98. creatorWatermelon,
  99. redeemableMint,
  100. usdcMint,
  101. poolWatermelon,
  102. poolUsdc,
  103. tokenProgram: TOKEN_PROGRAM_ID,
  104. rent: anchor.web3.SYSVAR_RENT_PUBKEY,
  105. clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
  106. },
  107. signers: [poolAccount],
  108. instructions: [
  109. await program.account.poolAccount.createInstruction(poolAccount),
  110. ],
  111. }
  112. );
  113. creators_watermelon_account = await getTokenAccount(
  114. provider,
  115. creatorWatermelon
  116. );
  117. assert.ok(creators_watermelon_account.amount.eq(new anchor.BN(0)));
  118. });
  119. // We're going to need to start using the associated program account for creating token accounts
  120. // if not in testing, then definitely in production.
  121. let userUsdc = null;
  122. let userRedeemable = null;
  123. // 10 usdc
  124. const firstDeposit = new anchor.BN(10_000_349);
  125. it("Exchanges user USDC for redeemable tokens", async () => {
  126. // Wait until the IDO has opened.
  127. if (Date.now() < startIdoTs.toNumber() * 1000) {
  128. await sleep(startIdoTs.toNumber() * 1000 - Date.now() + 1000);
  129. }
  130. userUsdc = await createTokenAccount(
  131. provider,
  132. usdcMint,
  133. provider.wallet.publicKey
  134. );
  135. await usdcMintToken.mintTo(
  136. userUsdc,
  137. provider.wallet.publicKey,
  138. [],
  139. firstDeposit.toString(),
  140. );
  141. userRedeemable = await createTokenAccount(
  142. provider,
  143. redeemableMint,
  144. provider.wallet.publicKey
  145. );
  146. try {
  147. const tx = await program.rpc.exchangeUsdcForRedeemable(firstDeposit, {
  148. accounts: {
  149. poolAccount: poolAccount.publicKey,
  150. poolSigner,
  151. redeemableMint,
  152. poolUsdc,
  153. userAuthority: provider.wallet.publicKey,
  154. userUsdc,
  155. userRedeemable,
  156. tokenProgram: TOKEN_PROGRAM_ID,
  157. clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
  158. },
  159. });
  160. } catch (err) {
  161. console.log("This is the error message", err.toString());
  162. }
  163. poolUsdcAccount = await getTokenAccount(provider, poolUsdc);
  164. assert.ok(poolUsdcAccount.amount.eq(firstDeposit));
  165. userRedeemableAccount = await getTokenAccount(provider, userRedeemable);
  166. assert.ok(userRedeemableAccount.amount.eq(firstDeposit));
  167. });
  168. // 23 usdc
  169. const secondDeposit = new anchor.BN(23_000_672);
  170. let totalPoolUsdc = null;
  171. it("Exchanges a second users USDC for redeemable tokens", async () => {
  172. secondUserUsdc = await createTokenAccount(
  173. provider,
  174. usdcMint,
  175. provider.wallet.publicKey
  176. );
  177. await usdcMintToken.mintTo(
  178. secondUserUsdc,
  179. provider.wallet.publicKey,
  180. [],
  181. secondDeposit.toString(),
  182. );
  183. secondUserRedeemable = await createTokenAccount(
  184. provider,
  185. redeemableMint,
  186. provider.wallet.publicKey
  187. );
  188. await program.rpc.exchangeUsdcForRedeemable(secondDeposit, {
  189. accounts: {
  190. poolAccount: poolAccount.publicKey,
  191. poolSigner,
  192. redeemableMint,
  193. poolUsdc,
  194. userAuthority: provider.wallet.publicKey,
  195. userUsdc: secondUserUsdc,
  196. userRedeemable: secondUserRedeemable,
  197. tokenProgram: TOKEN_PROGRAM_ID,
  198. clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
  199. },
  200. });
  201. totalPoolUsdc = firstDeposit.add(secondDeposit);
  202. poolUsdcAccount = await getTokenAccount(provider, poolUsdc);
  203. assert.ok(poolUsdcAccount.amount.eq(totalPoolUsdc));
  204. secondUserRedeemableAccount = await getTokenAccount(
  205. provider,
  206. secondUserRedeemable
  207. );
  208. assert.ok(secondUserRedeemableAccount.amount.eq(secondDeposit));
  209. });
  210. const firstWithdrawal = new anchor.BN(2_000_000);
  211. it("Exchanges user Redeemable tokens for USDC", async () => {
  212. await program.rpc.exchangeRedeemableForUsdc(firstWithdrawal, {
  213. accounts: {
  214. poolAccount: poolAccount.publicKey,
  215. poolSigner,
  216. redeemableMint,
  217. poolUsdc,
  218. userAuthority: provider.wallet.publicKey,
  219. userUsdc,
  220. userRedeemable,
  221. tokenProgram: TOKEN_PROGRAM_ID,
  222. clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
  223. },
  224. });
  225. totalPoolUsdc = totalPoolUsdc.sub(firstWithdrawal);
  226. poolUsdcAccount = await getTokenAccount(provider, poolUsdc);
  227. assert.ok(poolUsdcAccount.amount.eq(totalPoolUsdc));
  228. userUsdcAccount = await getTokenAccount(provider, userUsdc);
  229. assert.ok(userUsdcAccount.amount.eq(firstWithdrawal));
  230. });
  231. it("Exchanges user Redeemable tokens for watermelon", async () => {
  232. // Wait until the IDO has opened.
  233. if (Date.now() < endIdoTs.toNumber() * 1000) {
  234. await sleep(endIdoTs.toNumber() * 1000 - Date.now() + 2000);
  235. }
  236. let firstUserRedeemable = firstDeposit.sub(firstWithdrawal);
  237. userWatermelon = await createTokenAccount(
  238. provider,
  239. watermelonMint,
  240. provider.wallet.publicKey
  241. );
  242. await program.rpc.exchangeRedeemableForWatermelon(firstUserRedeemable, {
  243. accounts: {
  244. poolAccount: poolAccount.publicKey,
  245. poolSigner,
  246. redeemableMint,
  247. poolWatermelon,
  248. userAuthority: provider.wallet.publicKey,
  249. userWatermelon,
  250. userRedeemable,
  251. tokenProgram: TOKEN_PROGRAM_ID,
  252. clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
  253. },
  254. });
  255. poolWatermelonAccount = await getTokenAccount(provider, poolWatermelon);
  256. let redeemedWatermelon = firstUserRedeemable
  257. .mul(watermelonIdoAmount)
  258. .div(totalPoolUsdc);
  259. let remainingWatermelon = watermelonIdoAmount.sub(redeemedWatermelon);
  260. assert.ok(poolWatermelonAccount.amount.eq(remainingWatermelon));
  261. userWatermelonAccount = await getTokenAccount(provider, userWatermelon);
  262. assert.ok(userWatermelonAccount.amount.eq(redeemedWatermelon));
  263. });
  264. it("Exchanges second users Redeemable tokens for watermelon", async () => {
  265. secondUserWatermelon = await createTokenAccount(
  266. provider,
  267. watermelonMint,
  268. provider.wallet.publicKey
  269. );
  270. await program.rpc.exchangeRedeemableForWatermelon(secondDeposit, {
  271. accounts: {
  272. poolAccount: poolAccount.publicKey,
  273. poolSigner,
  274. redeemableMint,
  275. poolWatermelon,
  276. userAuthority: provider.wallet.publicKey,
  277. userWatermelon: secondUserWatermelon,
  278. userRedeemable: secondUserRedeemable,
  279. tokenProgram: TOKEN_PROGRAM_ID,
  280. clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
  281. },
  282. });
  283. poolWatermelonAccount = await getTokenAccount(provider, poolWatermelon);
  284. assert.ok(poolWatermelonAccount.amount.eq(new anchor.BN(0)));
  285. });
  286. it("Withdraws total USDC from pool account", async () => {
  287. await program.rpc.withdrawPoolUsdc({
  288. accounts: {
  289. poolAccount: poolAccount.publicKey,
  290. poolSigner,
  291. distributionAuthority: provider.wallet.publicKey,
  292. creatorUsdc,
  293. poolUsdc,
  294. tokenProgram: TOKEN_PROGRAM_ID,
  295. clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
  296. },
  297. });
  298. poolUsdcAccount = await getTokenAccount(provider, poolUsdc);
  299. assert.ok(poolUsdcAccount.amount.eq(new anchor.BN(0)));
  300. creatorUsdcAccount = await getTokenAccount(provider, creatorUsdc);
  301. assert.ok(creatorUsdcAccount.amount.eq(totalPoolUsdc));
  302. });
  303. });