ido-pool.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  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. creatorUsdc,
  96. redeemableMint,
  97. usdcMint,
  98. poolWatermelon,
  99. poolUsdc,
  100. tokenProgram: TOKEN_PROGRAM_ID,
  101. rent: anchor.web3.SYSVAR_RENT_PUBKEY,
  102. clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
  103. },
  104. signers: [poolAccount],
  105. instructions: [
  106. await program.account.poolAccount.createInstruction(poolAccount),
  107. ],
  108. }
  109. );
  110. creators_watermelon_account = await getTokenAccount(
  111. provider,
  112. creatorWatermelon
  113. );
  114. assert.ok(creators_watermelon_account.amount.eq(new anchor.BN(0)));
  115. });
  116. // We're going to need to start using the associated program account for creating token accounts
  117. // if not in testing, then definitely in production.
  118. let userUsdc = null;
  119. let userRedeemable = null;
  120. // 10 usdc
  121. const firstDeposit = new anchor.BN(10_000_349);
  122. it("Exchanges user USDC for redeemable tokens", async () => {
  123. // Wait until the IDO has opened.
  124. if (Date.now() < startIdoTs.toNumber() * 1000) {
  125. await sleep(startIdoTs.toNumber() * 1000 - Date.now() + 1000);
  126. }
  127. userUsdc = await createTokenAccount(
  128. provider,
  129. usdcMint,
  130. provider.wallet.publicKey
  131. );
  132. await mintToAccount(
  133. provider,
  134. usdcMint,
  135. userUsdc,
  136. firstDeposit,
  137. provider.wallet.publicKey
  138. );
  139. userRedeemable = await createTokenAccount(
  140. provider,
  141. redeemableMint,
  142. provider.wallet.publicKey
  143. );
  144. try {
  145. const tx = await program.rpc.exchangeUsdcForRedeemable(firstDeposit, {
  146. accounts: {
  147. poolAccount: poolAccount.publicKey,
  148. poolSigner,
  149. redeemableMint,
  150. poolUsdc,
  151. userAuthority: provider.wallet.publicKey,
  152. userUsdc,
  153. userRedeemable,
  154. tokenProgram: TOKEN_PROGRAM_ID,
  155. clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
  156. },
  157. });
  158. } catch (err) {
  159. console.log("This is the error message", err.toString());
  160. }
  161. poolUsdcAccount = await getTokenAccount(provider, poolUsdc);
  162. assert.ok(poolUsdcAccount.amount.eq(firstDeposit));
  163. userRedeemableAccount = await getTokenAccount(provider, userRedeemable);
  164. assert.ok(userRedeemableAccount.amount.eq(firstDeposit));
  165. });
  166. // 23 usdc
  167. const secondDeposit = new anchor.BN(23_000_672);
  168. let totalPoolUsdc = null;
  169. it("Exchanges a second users USDC for redeemable tokens", async () => {
  170. secondUserUsdc = await createTokenAccount(
  171. provider,
  172. usdcMint,
  173. provider.wallet.publicKey
  174. );
  175. await mintToAccount(
  176. provider,
  177. usdcMint,
  178. secondUserUsdc,
  179. secondDeposit,
  180. provider.wallet.publicKey
  181. );
  182. secondUserRedeemable = await createTokenAccount(
  183. provider,
  184. redeemableMint,
  185. provider.wallet.publicKey
  186. );
  187. await program.rpc.exchangeUsdcForRedeemable(secondDeposit, {
  188. accounts: {
  189. poolAccount: poolAccount.publicKey,
  190. poolSigner,
  191. redeemableMint,
  192. poolUsdc,
  193. userAuthority: provider.wallet.publicKey,
  194. userUsdc: secondUserUsdc,
  195. userRedeemable: secondUserRedeemable,
  196. tokenProgram: TOKEN_PROGRAM_ID,
  197. clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
  198. },
  199. });
  200. totalPoolUsdc = firstDeposit.add(secondDeposit);
  201. poolUsdcAccount = await getTokenAccount(provider, poolUsdc);
  202. assert.ok(poolUsdcAccount.amount.eq(totalPoolUsdc));
  203. secondUserRedeemableAccount = await getTokenAccount(
  204. provider,
  205. secondUserRedeemable
  206. );
  207. assert.ok(secondUserRedeemableAccount.amount.eq(secondDeposit));
  208. });
  209. const firstWithdrawal = new anchor.BN(2_000_000);
  210. it("Exchanges user Redeemable tokens for USDC", async () => {
  211. await program.rpc.exchangeRedeemableForUsdc(firstWithdrawal, {
  212. accounts: {
  213. poolAccount: poolAccount.publicKey,
  214. poolSigner,
  215. redeemableMint,
  216. poolUsdc,
  217. userAuthority: provider.wallet.publicKey,
  218. userUsdc,
  219. userRedeemable,
  220. tokenProgram: TOKEN_PROGRAM_ID,
  221. clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
  222. },
  223. });
  224. totalPoolUsdc = totalPoolUsdc.sub(firstWithdrawal);
  225. poolUsdcAccount = await getTokenAccount(provider, poolUsdc);
  226. assert.ok(poolUsdcAccount.amount.eq(totalPoolUsdc));
  227. userUsdcAccount = await getTokenAccount(provider, userUsdc);
  228. assert.ok(userUsdcAccount.amount.eq(firstWithdrawal));
  229. });
  230. it("Exchanges user Redeemable tokens for watermelon", async () => {
  231. // Wait until the IDO has opened.
  232. if (Date.now() < endIdoTs.toNumber() * 1000) {
  233. await sleep(endIdoTs.toNumber() * 1000 - Date.now() + 2000);
  234. }
  235. let firstUserRedeemable = firstDeposit.sub(firstWithdrawal);
  236. userWatermelon = await createTokenAccount(
  237. provider,
  238. watermelonMint,
  239. provider.wallet.publicKey
  240. );
  241. await program.rpc.exchangeRedeemableForWatermelon(firstUserRedeemable, {
  242. accounts: {
  243. poolAccount: poolAccount.publicKey,
  244. poolSigner,
  245. redeemableMint,
  246. poolWatermelon,
  247. userAuthority: provider.wallet.publicKey,
  248. userWatermelon,
  249. userRedeemable,
  250. tokenProgram: TOKEN_PROGRAM_ID,
  251. clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
  252. },
  253. });
  254. poolWatermelonAccount = await getTokenAccount(provider, poolWatermelon);
  255. let redeemedWatermelon = firstUserRedeemable
  256. .mul(watermelonIdoAmount)
  257. .div(totalPoolUsdc);
  258. let remainingWatermelon = watermelonIdoAmount.sub(redeemedWatermelon);
  259. assert.ok(poolWatermelonAccount.amount.eq(remainingWatermelon));
  260. userWatermelonAccount = await getTokenAccount(provider, userWatermelon);
  261. assert.ok(userWatermelonAccount.amount.eq(redeemedWatermelon));
  262. });
  263. it("Exchanges second users Redeemable tokens for watermelon", async () => {
  264. secondUserWatermelon = await createTokenAccount(
  265. provider,
  266. watermelonMint,
  267. provider.wallet.publicKey
  268. );
  269. await program.rpc.exchangeRedeemableForWatermelon(secondDeposit, {
  270. accounts: {
  271. poolAccount: poolAccount.publicKey,
  272. poolSigner,
  273. redeemableMint,
  274. poolWatermelon,
  275. userAuthority: provider.wallet.publicKey,
  276. userWatermelon: secondUserWatermelon,
  277. userRedeemable: secondUserRedeemable,
  278. tokenProgram: TOKEN_PROGRAM_ID,
  279. clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
  280. },
  281. });
  282. poolWatermelonAccount = await getTokenAccount(provider, poolWatermelon);
  283. assert.ok(poolWatermelonAccount.amount.eq(new anchor.BN(0)));
  284. });
  285. it("Withdraws total USDC from pool account", async () => {
  286. await program.rpc.withdrawPoolUsdc({
  287. accounts: {
  288. poolAccount: poolAccount.publicKey,
  289. poolSigner,
  290. distributionAuthority: provider.wallet.publicKey,
  291. creatorUsdc,
  292. poolUsdc,
  293. tokenProgram: TOKEN_PROGRAM_ID,
  294. clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
  295. },
  296. });
  297. poolUsdcAccount = await getTokenAccount(provider, poolUsdc);
  298. assert.ok(poolUsdcAccount.amount.eq(new anchor.BN(0)));
  299. creatorUsdcAccount = await getTokenAccount(provider, creatorUsdc);
  300. assert.ok(creatorUsdcAccount.amount.eq(totalPoolUsdc));
  301. });
  302. });