cfo.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  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.rpc.createOfficer(
  216. bumps,
  217. distribution,
  218. registrar,
  219. msrmRegistrar,
  220. {
  221. accounts: {
  222. officer,
  223. srmVault,
  224. usdcVault,
  225. stake,
  226. treasury,
  227. srmMint: ORDERBOOK_ENV.mintA,
  228. usdcMint: ORDERBOOK_ENV.usdc,
  229. authority: program.provider.wallet.publicKey,
  230. dexProgram: DEX_PID,
  231. swapProgram: SWAP_PID,
  232. tokenProgram: TOKEN_PID,
  233. systemProgram: SystemProgram.programId,
  234. rent: SYSVAR_RENT_PUBKEY,
  235. },
  236. }
  237. );
  238. officerAccount = await program.account.officer.fetch(officer);
  239. assert.ok(
  240. officerAccount.authority.equals(program.provider.wallet.publicKey)
  241. );
  242. assert.ok(
  243. JSON.stringify(officerAccount.distribution) ===
  244. JSON.stringify(distribution)
  245. );
  246. });
  247. it("Creates a token account for the officer associated with the market", async () => {
  248. await program.rpc.createOfficerToken(bBump, {
  249. accounts: {
  250. officer,
  251. token: bVault,
  252. mint: ORDERBOOK_ENV.mintB,
  253. payer: program.provider.wallet.publicKey,
  254. systemProgram: SystemProgram.programId,
  255. tokenProgram: TOKEN_PID,
  256. rent: SYSVAR_RENT_PUBKEY,
  257. },
  258. });
  259. const tokenAccount = await B_TOKEN_CLIENT.getAccountInfo(bVault);
  260. assert.ok(tokenAccount.state === 1);
  261. assert.ok(tokenAccount.isInitialized);
  262. });
  263. it("Creates an open orders account for the officer", async () => {
  264. await program.rpc.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. });
  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.rpc.sweepFees({
  297. accounts: {
  298. officer,
  299. sweepVault,
  300. mint: ORDERBOOK_ENV.usdc,
  301. dex: {
  302. market: ORDERBOOK_ENV.marketA._decoded.ownAddress,
  303. pcVault: ORDERBOOK_ENV.marketA._decoded.quoteVault,
  304. sweepAuthority,
  305. vaultSigner: ORDERBOOK_ENV.marketAVaultSigner,
  306. dexProgram: DEX_PID,
  307. tokenProgram: TOKEN_PID,
  308. },
  309. },
  310. });
  311. const afterTokenAccount = await serumCmn.getTokenAccount(
  312. program.provider,
  313. sweepVault
  314. );
  315. assert.ok(
  316. afterTokenAccount.amount.sub(beforeTokenAccount.amount).toString() ===
  317. FEES
  318. );
  319. });
  320. it("Creates a market auth token", async () => {
  321. await program.rpc.authorizeMarket(marketAuthBump, {
  322. accounts: {
  323. officer,
  324. authority: program.provider.wallet.publicKey,
  325. marketAuth,
  326. payer: program.provider.wallet.publicKey,
  327. market: ORDERBOOK_ENV.marketA.address,
  328. systemProgram: SystemProgram.programId,
  329. },
  330. });
  331. await program.rpc.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. });
  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.rpc.swapToUsdc(minExchangeRate, {
  361. accounts: {
  362. officer,
  363. market: {
  364. market: marketBClient.address,
  365. openOrders: openOrdersB,
  366. requestQueue: marketBClient.decoded.requestQueue,
  367. eventQueue: marketBClient.decoded.eventQueue,
  368. bids: marketBClient.decoded.bids,
  369. asks: marketBClient.decoded.asks,
  370. orderPayerTokenAccount: bVault,
  371. coinVault: marketBClient.decoded.baseVault,
  372. pcVault: marketBClient.decoded.quoteVault,
  373. vaultSigner: ORDERBOOK_ENV.marketBVaultSigner,
  374. },
  375. marketAuth: marketAuthB,
  376. usdcVault,
  377. fromVault: bVault,
  378. usdcMint: ORDERBOOK_ENV.usdc,
  379. swapProgram: SWAP_PID,
  380. dexProgram: DEX_PID,
  381. tokenProgram: TOKEN_PID,
  382. instructions: SYSVAR_INSTRUCTIONS_PUBKEY,
  383. rent: SYSVAR_RENT_PUBKEY,
  384. },
  385. });
  386. const bVaultAfter = await B_TOKEN_CLIENT.getAccountInfo(bVault);
  387. const usdcVaultAfter = await USDC_TOKEN_CLIENT.getAccountInfo(usdcVault);
  388. assert.ok(bVaultBefore.amount.toNumber() === 616035558100);
  389. assert.ok(usdcVaultBefore.amount.toNumber() === 6160355581);
  390. assert.ok(bVaultAfter.amount.toNumber() === 615884458100);
  391. assert.ok(usdcVaultAfter.amount.toNumber() === 7060634298);
  392. });
  393. it("Swaps to SRM", async () => {
  394. const srmVaultBefore = await SRM_TOKEN_CLIENT.getAccountInfo(srmVault);
  395. const usdcVaultBefore = await USDC_TOKEN_CLIENT.getAccountInfo(usdcVault);
  396. const minExchangeRate = {
  397. rate: new anchor.BN(0),
  398. fromDecimals: 6,
  399. quoteDecimals: 6,
  400. strict: false,
  401. };
  402. await program.rpc.swapToSrm(minExchangeRate, {
  403. accounts: {
  404. officer,
  405. market: {
  406. market: marketAClient.address,
  407. openOrders,
  408. requestQueue: marketAClient.decoded.requestQueue,
  409. eventQueue: marketAClient.decoded.eventQueue,
  410. bids: marketAClient.decoded.bids,
  411. asks: marketAClient.decoded.asks,
  412. orderPayerTokenAccount: usdcVault,
  413. coinVault: marketAClient.decoded.baseVault,
  414. pcVault: marketAClient.decoded.quoteVault,
  415. vaultSigner: ORDERBOOK_ENV.marketAVaultSigner,
  416. },
  417. marketAuth,
  418. usdcVault,
  419. srmVault,
  420. usdcMint: ORDERBOOK_ENV.usdc,
  421. srmMint: ORDERBOOK_ENV.mintA,
  422. swapProgram: SWAP_PID,
  423. dexProgram: DEX_PID,
  424. tokenProgram: TOKEN_PID,
  425. instructions: SYSVAR_INSTRUCTIONS_PUBKEY,
  426. rent: SYSVAR_RENT_PUBKEY,
  427. },
  428. });
  429. const srmVaultAfter = await SRM_TOKEN_CLIENT.getAccountInfo(srmVault);
  430. const usdcVaultAfter = await USDC_TOKEN_CLIENT.getAccountInfo(usdcVault);
  431. assert.ok(srmVaultBefore.amount.toNumber() === 0);
  432. assert.ok(srmVaultAfter.amount.toNumber() === 1152000000);
  433. assert.ok(usdcVaultBefore.amount.toNumber() === 7060634298);
  434. assert.ok(usdcVaultAfter.amount.toNumber() === 530863);
  435. });
  436. it("Distributes the tokens to categories", async () => {
  437. const srmVaultBefore = await SRM_TOKEN_CLIENT.getAccountInfo(srmVault);
  438. const treasuryBefore = await SRM_TOKEN_CLIENT.getAccountInfo(treasury);
  439. const stakeBefore = await SRM_TOKEN_CLIENT.getAccountInfo(stake);
  440. const mintInfoBefore = await SRM_TOKEN_CLIENT.getMintInfo();
  441. await program.rpc.distribute({
  442. accounts: {
  443. officer,
  444. treasury,
  445. stake,
  446. srmVault,
  447. srmMint: ORDERBOOK_ENV.mintA,
  448. tokenProgram: TOKEN_PID,
  449. dexProgram: DEX_PID,
  450. },
  451. });
  452. const srmVaultAfter = await SRM_TOKEN_CLIENT.getAccountInfo(srmVault);
  453. const treasuryAfter = await SRM_TOKEN_CLIENT.getAccountInfo(treasury);
  454. const stakeAfter = await SRM_TOKEN_CLIENT.getAccountInfo(stake);
  455. const mintInfoAfter = await SRM_TOKEN_CLIENT.getMintInfo();
  456. const beforeAmount = 1152000000;
  457. assert.ok(srmVaultBefore.amount.toNumber() === beforeAmount);
  458. assert.ok(srmVaultAfter.amount.toNumber() === 0); // Fully distributed.
  459. assert.ok(
  460. stakeAfter.amount.toNumber() ===
  461. beforeAmount * (distribution.stake / 100.0)
  462. );
  463. assert.ok(
  464. treasuryAfter.amount.toNumber() ===
  465. beforeAmount * (distribution.treasury / 100.0)
  466. );
  467. // Check burn amount.
  468. assert.ok(mintInfoBefore.supply.toString() === "1000000000000000000");
  469. assert.ok(
  470. mintInfoBefore.supply.sub(mintInfoAfter.supply).toNumber() ===
  471. beforeAmount * (distribution.burn / 100.0)
  472. );
  473. });
  474. });