cfo.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. const { assert } = require("chai");
  2. const { Token } = require("@solana/spl-token");
  3. const anchor = require("@project-serum/anchor");
  4. const serumCmn = require("@project-serum/common");
  5. const { Market } = require("@project-serum/serum");
  6. const utf8 = anchor.utils.bytes.utf8;
  7. const { PublicKey, SystemProgram, Keypair, SYSVAR_RENT_PUBKEY } = anchor.web3;
  8. const utils = require("./utils");
  9. const { setupStakePool } = require("./utils/stake");
  10. const DEX_PID = new PublicKey("9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin");
  11. const SWAP_PID = new PublicKey("22Y43yTVxuUkoRKdm9thyRhQ3SdgQS7c7kB6UNCiaczD");
  12. const TOKEN_PID = new PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
  13. const REGISTRY_PID = new PublicKey(
  14. "GrAkKfEpTKQuVHG2Y97Y2FF4i7y7Q5AHLK94JBy7Y5yv"
  15. );
  16. const LOCKUP_PID = new PublicKey(
  17. "6ebQNeTPZ1j7k3TtkCCtEPRvG7GQsucQrZ7sSEDQi9Ks"
  18. );
  19. const SYSVAR_INSTRUCTIONS_PUBKEY = new PublicKey(
  20. "Sysvar1nstructions1111111111111111111111111"
  21. );
  22. const FEES = "6160355581";
  23. describe("cfo", () => {
  24. const provider = anchor.AnchorProvider.env();
  25. anchor.setProvider(provider);
  26. const program = anchor.workspace.Cfo;
  27. // hack so we don't have to update serum-common library
  28. // to the new AnchorProvider class and Provider interface
  29. program.provider.send = provider.sendAndConfirm;
  30. const sweepAuthority = program.provider.wallet.publicKey;
  31. let officer, srmVault, usdcVault, bVault, stake, treasury;
  32. let officerBump, srmBump, usdcBump, bBump, stakeBump, treasuryBump;
  33. let openOrders, openOrdersBump;
  34. let openOrdersB, openOrdersBumpB;
  35. let USDC_TOKEN_CLIENT, A_TOKEN_CLIENT, B_TOKEN_CLIENT;
  36. let officerAccount;
  37. let marketAClient, marketBClient;
  38. let marketAuth, marketAuthBump;
  39. let marketAuthB, marketAuthBumpB;
  40. let distribution;
  41. // Accounts used to setup the orderbook.
  42. let ORDERBOOK_ENV,
  43. // Accounts used for A -> USDC swap transactions.
  44. SWAP_A_USDC_ACCOUNTS,
  45. // Accounts used for USDC -> A swap transactions.
  46. SWAP_USDC_A_ACCOUNTS,
  47. // Serum DEX vault PDA for market A/USDC.
  48. marketAVaultSigner,
  49. // Serum DEX vault PDA for market B/USDC.
  50. marketBVaultSigner;
  51. let registrar, msrmRegistrar;
  52. it("BOILERPLATE: Sets up a market with funded fees", async () => {
  53. ORDERBOOK_ENV = await utils.initMarket({
  54. provider: program.provider,
  55. });
  56. console.log("Token A: ", ORDERBOOK_ENV.marketA.baseMintAddress.toString());
  57. console.log(
  58. "Token USDC: ",
  59. ORDERBOOK_ENV.marketA.quoteMintAddress.toString()
  60. );
  61. USDC_TOKEN_CLIENT = new Token(
  62. program.provider.connection,
  63. ORDERBOOK_ENV.usdc,
  64. TOKEN_PID,
  65. program.provider.wallet.payer
  66. );
  67. SRM_TOKEN_CLIENT = new Token(
  68. program.provider.connection,
  69. ORDERBOOK_ENV.mintA,
  70. TOKEN_PID,
  71. program.provider.wallet.payer
  72. );
  73. B_TOKEN_CLIENT = new Token(
  74. program.provider.connection,
  75. ORDERBOOK_ENV.mintB,
  76. TOKEN_PID,
  77. program.provider.wallet.payer
  78. );
  79. await USDC_TOKEN_CLIENT.transfer(
  80. ORDERBOOK_ENV.godUsdc,
  81. ORDERBOOK_ENV.marketA._decoded.quoteVault,
  82. program.provider.wallet.payer,
  83. [],
  84. 10000000000000
  85. );
  86. const tokenAccount = await USDC_TOKEN_CLIENT.getAccountInfo(
  87. ORDERBOOK_ENV.marketA._decoded.quoteVault
  88. );
  89. assert.strictEqual(tokenAccount.amount.toString(), "10000902263700");
  90. });
  91. it("BOILERPLATE: Executes trades to generate fees", async () => {
  92. await utils.runTradeBot(
  93. ORDERBOOK_ENV.marketA._decoded.ownAddress,
  94. program.provider,
  95. 1
  96. );
  97. marketAClient = await Market.load(
  98. program.provider.connection,
  99. ORDERBOOK_ENV.marketA.address,
  100. { commitment: "processed" },
  101. DEX_PID
  102. );
  103. marketBClient = await Market.load(
  104. program.provider.connection,
  105. ORDERBOOK_ENV.marketB.address,
  106. { commitment: "processed" },
  107. DEX_PID
  108. );
  109. assert.strictEqual(
  110. marketAClient._decoded.quoteFeesAccrued.toString(),
  111. FEES
  112. );
  113. });
  114. it("BOILERPLATE: Sets up the staking pools", async () => {
  115. await setupStakePool(ORDERBOOK_ENV.mintA, ORDERBOOK_ENV.godA);
  116. registrar = ORDERBOOK_ENV.usdc;
  117. msrmRegistrar = registrar;
  118. });
  119. it("BOILERPLATE: Finds PDA addresses", async () => {
  120. const [_officer, _officerBump] = await PublicKey.findProgramAddress(
  121. [DEX_PID.toBuffer()],
  122. program.programId
  123. );
  124. const [_openOrders, _openOrdersBump] = await PublicKey.findProgramAddress(
  125. [
  126. utf8.encode("open-orders"),
  127. _officer.toBuffer(),
  128. ORDERBOOK_ENV.marketA.address.toBuffer(),
  129. ],
  130. program.programId
  131. );
  132. const [_openOrdersB, _openOrdersBumpB] = await PublicKey.findProgramAddress(
  133. [
  134. utf8.encode("open-orders"),
  135. _officer.toBuffer(),
  136. ORDERBOOK_ENV.marketB.address.toBuffer(),
  137. ],
  138. program.programId
  139. );
  140. const [_srmVault, _srmBump] = await PublicKey.findProgramAddress(
  141. [
  142. utf8.encode("token"),
  143. _officer.toBuffer(),
  144. ORDERBOOK_ENV.mintA.toBuffer(),
  145. ],
  146. program.programId
  147. );
  148. const [_bVault, _bBump] = await PublicKey.findProgramAddress(
  149. [
  150. utf8.encode("token"),
  151. _officer.toBuffer(),
  152. ORDERBOOK_ENV.mintB.toBuffer(),
  153. ],
  154. program.programId
  155. );
  156. const [_usdcVault, _usdcBump] = await PublicKey.findProgramAddress(
  157. [
  158. utf8.encode("token"),
  159. _officer.toBuffer(),
  160. ORDERBOOK_ENV.usdc.toBuffer(),
  161. ],
  162. program.programId
  163. );
  164. const [_stake, _stakeBump] = await PublicKey.findProgramAddress(
  165. [utf8.encode("stake"), _officer.toBuffer()],
  166. program.programId
  167. );
  168. const [_treasury, _treasuryBump] = await PublicKey.findProgramAddress(
  169. [utf8.encode("treasury"), _officer.toBuffer()],
  170. program.programId
  171. );
  172. const [_marketAuth, _marketAuthBump] = await PublicKey.findProgramAddress(
  173. [
  174. utf8.encode("market-auth"),
  175. _officer.toBuffer(),
  176. ORDERBOOK_ENV.marketA.address.toBuffer(),
  177. ],
  178. program.programId
  179. );
  180. const [_marketAuthB, _marketAuthBumpB] = await PublicKey.findProgramAddress(
  181. [
  182. utf8.encode("market-auth"),
  183. _officer.toBuffer(),
  184. ORDERBOOK_ENV.marketB.address.toBuffer(),
  185. ],
  186. program.programId
  187. );
  188. officer = _officer;
  189. officerBump = _officerBump;
  190. openOrders = _openOrders;
  191. openOrdersBump = _openOrdersBump;
  192. openOrdersB = _openOrdersB;
  193. openOrdersBumpB = _openOrdersBumpB;
  194. srmVault = _srmVault;
  195. srmBump = _srmBump;
  196. usdcVault = _usdcVault;
  197. usdcBump = _usdcBump;
  198. bVault = _bVault;
  199. bBump = _bBump;
  200. stake = _stake;
  201. stakeBump = _stakeBump;
  202. treasury = _treasury;
  203. treasuryBump = _treasuryBump;
  204. marketAuth = _marketAuth;
  205. marketAuthBump = _marketAuthBump;
  206. marketAuthB = _marketAuthB;
  207. marketAuthBumpB = _marketAuthBumpB;
  208. });
  209. it("Creates a CFO!", async () => {
  210. distribution = {
  211. burn: 80,
  212. stake: 20,
  213. treasury: 0,
  214. };
  215. const bumps = {
  216. bump: officerBump,
  217. srm: srmBump,
  218. usdc: usdcBump,
  219. stake: stakeBump,
  220. treasury: treasuryBump,
  221. };
  222. await program.methods
  223. .createOfficer(bumps, distribution, registrar, msrmRegistrar)
  224. .accounts({
  225. officer,
  226. srmVault,
  227. usdcVault,
  228. stake,
  229. treasury,
  230. srmMint: ORDERBOOK_ENV.mintA,
  231. usdcMint: ORDERBOOK_ENV.usdc,
  232. authority: program.provider.wallet.publicKey,
  233. dexProgram: DEX_PID,
  234. swapProgram: SWAP_PID,
  235. tokenProgram: TOKEN_PID,
  236. systemProgram: SystemProgram.programId,
  237. rent: SYSVAR_RENT_PUBKEY,
  238. })
  239. .rpc();
  240. officerAccount = await program.account.officer.fetch(officer);
  241. assert.isTrue(
  242. officerAccount.authority.equals(program.provider.wallet.publicKey)
  243. );
  244. assert.strictEqual(
  245. JSON.stringify(officerAccount.distribution),
  246. JSON.stringify(distribution)
  247. );
  248. });
  249. it("Creates a token account for the officer associated with the market", async () => {
  250. await program.methods
  251. .createOfficerToken(bBump)
  252. .accounts({
  253. officer,
  254. token: bVault,
  255. mint: ORDERBOOK_ENV.mintB,
  256. payer: program.provider.wallet.publicKey,
  257. systemProgram: SystemProgram.programId,
  258. tokenProgram: TOKEN_PID,
  259. rent: SYSVAR_RENT_PUBKEY,
  260. })
  261. .rpc();
  262. const tokenAccount = await B_TOKEN_CLIENT.getAccountInfo(bVault);
  263. assert.strictEqual(tokenAccount.state, 1);
  264. assert.isTrue(tokenAccount.isInitialized);
  265. });
  266. it("Creates an open orders account for the officer", async () => {
  267. await program.methods
  268. .createOfficerOpenOrders(openOrdersBump)
  269. .accounts({
  270. officer,
  271. openOrders,
  272. payer: program.provider.wallet.publicKey,
  273. dexProgram: DEX_PID,
  274. systemProgram: SystemProgram.programId,
  275. rent: SYSVAR_RENT_PUBKEY,
  276. market: ORDERBOOK_ENV.marketA.address,
  277. })
  278. .rpc();
  279. await program.rpc.createOfficerOpenOrders(openOrdersBumpB, {
  280. accounts: {
  281. officer,
  282. openOrders: openOrdersB,
  283. payer: program.provider.wallet.publicKey,
  284. dexProgram: DEX_PID,
  285. systemProgram: SystemProgram.programId,
  286. rent: SYSVAR_RENT_PUBKEY,
  287. market: ORDERBOOK_ENV.marketB.address,
  288. },
  289. });
  290. });
  291. it("Sweeps fees", async () => {
  292. const [sweepVault, bump] = await PublicKey.findProgramAddress(
  293. [utf8.encode("token"), officer.toBuffer(), ORDERBOOK_ENV.usdc.toBuffer()],
  294. program.programId
  295. );
  296. const beforeTokenAccount = await serumCmn.getTokenAccount(
  297. program.provider,
  298. sweepVault
  299. );
  300. await program.methods
  301. .sweepFees()
  302. .accounts({
  303. officer,
  304. sweepVault,
  305. mint: ORDERBOOK_ENV.usdc,
  306. dex: {
  307. market: ORDERBOOK_ENV.marketA._decoded.ownAddress,
  308. pcVault: ORDERBOOK_ENV.marketA._decoded.quoteVault,
  309. sweepAuthority,
  310. vaultSigner: ORDERBOOK_ENV.marketAVaultSigner,
  311. dexProgram: DEX_PID,
  312. tokenProgram: TOKEN_PID,
  313. },
  314. })
  315. .rpc();
  316. const afterTokenAccount = await serumCmn.getTokenAccount(
  317. program.provider,
  318. sweepVault
  319. );
  320. assert.strictEqual(
  321. afterTokenAccount.amount.sub(beforeTokenAccount.amount).toString(),
  322. FEES
  323. );
  324. });
  325. it("Creates a market auth token", async () => {
  326. await program.methods
  327. .authorizeMarket(marketAuthBump)
  328. .accounts({
  329. officer,
  330. authority: program.provider.wallet.publicKey,
  331. marketAuth,
  332. payer: program.provider.wallet.publicKey,
  333. market: ORDERBOOK_ENV.marketA.address,
  334. systemProgram: SystemProgram.programId,
  335. })
  336. .rpc();
  337. await program.methods
  338. .authorizeMarket(marketAuthBumpB)
  339. .accounts({
  340. officer,
  341. authority: program.provider.wallet.publicKey,
  342. marketAuth: marketAuthB,
  343. payer: program.provider.wallet.publicKey,
  344. market: ORDERBOOK_ENV.marketB.address,
  345. systemProgram: SystemProgram.programId,
  346. })
  347. .rpc();
  348. });
  349. it("Transfers into the mintB vault", async () => {
  350. await B_TOKEN_CLIENT.transfer(
  351. ORDERBOOK_ENV.godB,
  352. bVault,
  353. program.provider.wallet.payer,
  354. [],
  355. 616035558100
  356. );
  357. });
  358. it("Swaps from B token to USDC", async () => {
  359. const bVaultBefore = await B_TOKEN_CLIENT.getAccountInfo(bVault);
  360. const usdcVaultBefore = await USDC_TOKEN_CLIENT.getAccountInfo(usdcVault);
  361. const minExchangeRate = {
  362. rate: new anchor.BN(0),
  363. fromDecimals: 6,
  364. quoteDecimals: 6,
  365. strict: false,
  366. };
  367. await program.methods
  368. .swapToUsdc(minExchangeRate)
  369. .accounts({
  370. officer,
  371. market: {
  372. market: marketBClient.address,
  373. openOrders: openOrdersB,
  374. requestQueue: marketBClient.decoded.requestQueue,
  375. eventQueue: marketBClient.decoded.eventQueue,
  376. bids: marketBClient.decoded.bids,
  377. asks: marketBClient.decoded.asks,
  378. orderPayerTokenAccount: bVault,
  379. coinVault: marketBClient.decoded.baseVault,
  380. pcVault: marketBClient.decoded.quoteVault,
  381. vaultSigner: ORDERBOOK_ENV.marketBVaultSigner,
  382. },
  383. marketAuth: marketAuthB,
  384. usdcVault,
  385. fromVault: bVault,
  386. usdcMint: ORDERBOOK_ENV.usdc,
  387. swapProgram: SWAP_PID,
  388. dexProgram: DEX_PID,
  389. tokenProgram: TOKEN_PID,
  390. instructions: SYSVAR_INSTRUCTIONS_PUBKEY,
  391. rent: SYSVAR_RENT_PUBKEY,
  392. })
  393. .rpc();
  394. const bVaultAfter = await B_TOKEN_CLIENT.getAccountInfo(bVault);
  395. const usdcVaultAfter = await USDC_TOKEN_CLIENT.getAccountInfo(usdcVault);
  396. assert.strictEqual(bVaultBefore.amount.toNumber(), 616035558100);
  397. assert.strictEqual(usdcVaultBefore.amount.toNumber(), 6160355581);
  398. assert.strictEqual(bVaultAfter.amount.toNumber(), 615884458100);
  399. assert.strictEqual(usdcVaultAfter.amount.toNumber(), 7060634298);
  400. });
  401. it("Swaps to SRM", async () => {
  402. const srmVaultBefore = await SRM_TOKEN_CLIENT.getAccountInfo(srmVault);
  403. const usdcVaultBefore = await USDC_TOKEN_CLIENT.getAccountInfo(usdcVault);
  404. const minExchangeRate = {
  405. rate: new anchor.BN(0),
  406. fromDecimals: 6,
  407. quoteDecimals: 6,
  408. strict: false,
  409. };
  410. await program.methods
  411. .swapToSrm(minExchangeRate)
  412. .accounts({
  413. officer,
  414. market: {
  415. market: marketAClient.address,
  416. openOrders,
  417. requestQueue: marketAClient.decoded.requestQueue,
  418. eventQueue: marketAClient.decoded.eventQueue,
  419. bids: marketAClient.decoded.bids,
  420. asks: marketAClient.decoded.asks,
  421. orderPayerTokenAccount: usdcVault,
  422. coinVault: marketAClient.decoded.baseVault,
  423. pcVault: marketAClient.decoded.quoteVault,
  424. vaultSigner: ORDERBOOK_ENV.marketAVaultSigner,
  425. },
  426. marketAuth,
  427. usdcVault,
  428. srmVault,
  429. usdcMint: ORDERBOOK_ENV.usdc,
  430. srmMint: ORDERBOOK_ENV.mintA,
  431. swapProgram: SWAP_PID,
  432. dexProgram: DEX_PID,
  433. tokenProgram: TOKEN_PID,
  434. instructions: SYSVAR_INSTRUCTIONS_PUBKEY,
  435. rent: SYSVAR_RENT_PUBKEY,
  436. })
  437. .rpc();
  438. const srmVaultAfter = await SRM_TOKEN_CLIENT.getAccountInfo(srmVault);
  439. const usdcVaultAfter = await USDC_TOKEN_CLIENT.getAccountInfo(usdcVault);
  440. assert.strictEqual(srmVaultBefore.amount.toNumber(), 0);
  441. assert.strictEqual(srmVaultAfter.amount.toNumber(), 1152000000);
  442. assert.strictEqual(usdcVaultBefore.amount.toNumber(), 7060634298);
  443. assert.strictEqual(usdcVaultAfter.amount.toNumber(), 530863);
  444. });
  445. it("Distributes the tokens to categories", async () => {
  446. const srmVaultBefore = await SRM_TOKEN_CLIENT.getAccountInfo(srmVault);
  447. const treasuryBefore = await SRM_TOKEN_CLIENT.getAccountInfo(treasury);
  448. const stakeBefore = await SRM_TOKEN_CLIENT.getAccountInfo(stake);
  449. const mintInfoBefore = await SRM_TOKEN_CLIENT.getMintInfo();
  450. await program.methods
  451. .distribute()
  452. .accounts({
  453. officer,
  454. treasury,
  455. stake,
  456. srmVault,
  457. srmMint: ORDERBOOK_ENV.mintA,
  458. tokenProgram: TOKEN_PID,
  459. dexProgram: DEX_PID,
  460. })
  461. .rpc();
  462. const srmVaultAfter = await SRM_TOKEN_CLIENT.getAccountInfo(srmVault);
  463. const treasuryAfter = await SRM_TOKEN_CLIENT.getAccountInfo(treasury);
  464. const stakeAfter = await SRM_TOKEN_CLIENT.getAccountInfo(stake);
  465. const mintInfoAfter = await SRM_TOKEN_CLIENT.getMintInfo();
  466. const beforeAmount = 1152000000;
  467. assert.strictEqual(srmVaultBefore.amount.toNumber(), beforeAmount);
  468. assert.strictEqual(srmVaultAfter.amount.toNumber(), 0); // Fully distributed.
  469. assert.strictEqual(
  470. stakeAfter.amount.toNumber(),
  471. beforeAmount * (distribution.stake / 100.0)
  472. );
  473. assert.strictEqual(
  474. treasuryAfter.amount.toNumber(),
  475. beforeAmount * (distribution.treasury / 100.0)
  476. );
  477. // Check burn amount.
  478. assert.strictEqual(mintInfoBefore.supply.toString(), "1000000000000000000");
  479. assert.strictEqual(
  480. mintInfoBefore.supply.sub(mintInfoAfter.supply).toNumber(),
  481. beforeAmount * (distribution.burn / 100.0)
  482. );
  483. });
  484. });