ido-pool.js 10 KB

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