cfo.js 16 KB

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