cfo.js 16 KB

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