stake-pool.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  1. import assert from "assert";
  2. import { splStakePoolProgram } from "@coral-xyz/spl-stake-pool";
  3. import { splTokenProgram } from "@coral-xyz/spl-token";
  4. import { BN } from "@coral-xyz/anchor";
  5. import {
  6. Keypair,
  7. LAMPORTS_PER_SOL,
  8. PublicKey,
  9. StakeProgram,
  10. STAKE_CONFIG_ID,
  11. SystemProgram,
  12. SYSVAR_CLOCK_PUBKEY,
  13. SYSVAR_RENT_PUBKEY,
  14. SYSVAR_STAKE_HISTORY_PUBKEY,
  15. VoteProgram,
  16. } from "@solana/web3.js";
  17. import { SPL_STAKE_POOL_PROGRAM_ID, SPL_TOKEN_PROGRAM_ID } from "../constants";
  18. import {
  19. createAta,
  20. createMint,
  21. createTokenAccount,
  22. getProvider,
  23. loadKp,
  24. sendAndConfirmTx,
  25. simulateTx,
  26. test,
  27. } from "../utils";
  28. export async function stakePoolTests() {
  29. const provider = await getProvider();
  30. const program = splStakePoolProgram({
  31. provider,
  32. programId: SPL_STAKE_POOL_PROGRAM_ID,
  33. });
  34. const tokenProgram = splTokenProgram({
  35. provider,
  36. programId: SPL_TOKEN_PROGRAM_ID,
  37. });
  38. const kp = await loadKp();
  39. const VALIDATOR_LIST_COUNT = 10;
  40. const TRANSIENT_STAKE_SEED = 0;
  41. let stakePoolPk: PublicKey;
  42. let withdrawAuthorityPk: PublicKey;
  43. let poolMintPk: PublicKey;
  44. let managerPoolTokenAccountPk: PublicKey;
  45. let validatorListPk: PublicKey;
  46. let reserveStakePk: PublicKey;
  47. let stakeAccountPk: PublicKey;
  48. let voteAccountPk: PublicKey;
  49. let transientStakePk: PublicKey;
  50. let userPoolTokenAccountPk: PublicKey;
  51. async function initialize() {
  52. const stakePoolKp = new Keypair();
  53. stakePoolPk = stakePoolKp.publicKey;
  54. const createStakePoolAccountIx =
  55. await program.account.stakePool.createInstruction(stakePoolKp, 5000);
  56. [withdrawAuthorityPk] = await PublicKey.findProgramAddress(
  57. [stakePoolPk.toBuffer(), Buffer.from("withdraw")],
  58. program.programId
  59. );
  60. poolMintPk = await createMint(withdrawAuthorityPk);
  61. userPoolTokenAccountPk = await createAta(poolMintPk, kp.publicKey);
  62. // Fee account
  63. managerPoolTokenAccountPk = await createTokenAccount(poolMintPk);
  64. const validatorListKp = new Keypair();
  65. validatorListPk = validatorListKp.publicKey;
  66. const createValidatorListAccountIx =
  67. await program.account.validatorList.createInstruction(
  68. validatorListKp,
  69. 5 + 4 + 73 * VALIDATOR_LIST_COUNT
  70. );
  71. const reserveStakeKp = new Keypair();
  72. reserveStakePk = reserveStakeKp.publicKey;
  73. const createReserveStakeAccountIxs = StakeProgram.createAccount({
  74. authorized: {
  75. staker: withdrawAuthorityPk,
  76. withdrawer: withdrawAuthorityPk,
  77. },
  78. fromPubkey: kp.publicKey,
  79. lamports:
  80. (await provider.connection.getMinimumBalanceForRentExemption(
  81. StakeProgram.space
  82. )) +
  83. LAMPORTS_PER_SOL * 11,
  84. stakePubkey: reserveStakePk,
  85. }).instructions;
  86. const initStakePoolIx = await program.methods
  87. .initialize(
  88. {
  89. denominator: new BN(10),
  90. numerator: new BN(1),
  91. },
  92. {
  93. denominator: new BN(10),
  94. numerator: new BN(1),
  95. },
  96. {
  97. denominator: new BN(10),
  98. numerator: new BN(1),
  99. },
  100. 10,
  101. VALIDATOR_LIST_COUNT
  102. )
  103. .accounts({
  104. stakePool: stakePoolKp.publicKey,
  105. manager: kp.publicKey,
  106. staker: kp.publicKey,
  107. stakePoolWithdrawAuthority: withdrawAuthorityPk,
  108. validatorList: validatorListKp.publicKey,
  109. reserveStake: reserveStakePk,
  110. poolMint: poolMintPk,
  111. managerPoolAccount: managerPoolTokenAccountPk,
  112. tokenProgram: tokenProgram.programId,
  113. })
  114. .instruction();
  115. await sendAndConfirmTx(
  116. [
  117. createStakePoolAccountIx,
  118. ...createReserveStakeAccountIxs,
  119. createValidatorListAccountIx,
  120. initStakePoolIx,
  121. ],
  122. [kp, stakePoolKp, validatorListKp, reserveStakeKp]
  123. );
  124. }
  125. async function addValidatorToPool() {
  126. const voteAccountKp = new Keypair();
  127. voteAccountPk = voteAccountKp.publicKey;
  128. const identityKp = new Keypair();
  129. const createVoteAccountIxs = VoteProgram.createAccount({
  130. fromPubkey: kp.publicKey,
  131. lamports: await provider.connection.getMinimumBalanceForRentExemption(
  132. VoteProgram.space
  133. ),
  134. voteInit: {
  135. authorizedVoter: kp.publicKey,
  136. authorizedWithdrawer: kp.publicKey,
  137. commission: 1,
  138. nodePubkey: identityKp.publicKey,
  139. },
  140. votePubkey: voteAccountKp.publicKey,
  141. }).instructions;
  142. [stakeAccountPk] = await PublicKey.findProgramAddress(
  143. [voteAccountPk.toBuffer(), stakePoolPk.toBuffer()],
  144. program.programId
  145. );
  146. [transientStakePk] = await PublicKey.findProgramAddress(
  147. [
  148. Buffer.from("transient"),
  149. voteAccountPk.toBuffer(),
  150. stakePoolPk.toBuffer(),
  151. new BN(TRANSIENT_STAKE_SEED).toBuffer("le", 8), // Transient seed suffix start
  152. ],
  153. program.programId
  154. );
  155. const addValidatorIx = await program.methods
  156. .addValidatorToPool()
  157. .accounts({
  158. stakePool: stakePoolPk,
  159. staker: kp.publicKey,
  160. funder: kp.publicKey,
  161. stakePoolWithdraw: withdrawAuthorityPk,
  162. validatorList: validatorListPk,
  163. stake: stakeAccountPk,
  164. validator: voteAccountPk,
  165. rent: SYSVAR_RENT_PUBKEY,
  166. clock: SYSVAR_CLOCK_PUBKEY,
  167. sysvarStakeHistory: SYSVAR_STAKE_HISTORY_PUBKEY,
  168. stakeConfig: STAKE_CONFIG_ID,
  169. systemProgram: SystemProgram.programId,
  170. stakeProgram: StakeProgram.programId,
  171. })
  172. .instruction();
  173. await sendAndConfirmTx(
  174. [...createVoteAccountIxs, addValidatorIx],
  175. [kp, voteAccountKp, identityKp]
  176. );
  177. }
  178. async function removeValidatorFromPool() {
  179. const destinationStakeAccountKp = new Keypair();
  180. const createDestinationStakeAccountIx = SystemProgram.createAccount({
  181. fromPubkey: kp.publicKey,
  182. lamports: 0,
  183. newAccountPubkey: destinationStakeAccountKp.publicKey,
  184. programId: StakeProgram.programId,
  185. space: StakeProgram.space,
  186. });
  187. const removeValidatorIx = await program.methods
  188. .removeValidatorFromPool()
  189. .accounts({
  190. stakePool: stakePoolPk,
  191. staker: kp.publicKey,
  192. stakePoolWithdraw: withdrawAuthorityPk,
  193. newStakeAuthority: kp.publicKey,
  194. validatorList: validatorListPk,
  195. stakeAccount: stakeAccountPk,
  196. transientStakeAccount: transientStakePk,
  197. destinationStakeAccount: destinationStakeAccountKp.publicKey,
  198. clock: SYSVAR_CLOCK_PUBKEY,
  199. stakeProgram: StakeProgram.programId,
  200. })
  201. .instruction();
  202. await sendAndConfirmTx(
  203. [createDestinationStakeAccountIx, removeValidatorIx],
  204. [kp, destinationStakeAccountKp]
  205. );
  206. }
  207. async function increaseValidatorStake() {
  208. await program.methods
  209. .increaseValidatorStake(
  210. new BN(LAMPORTS_PER_SOL),
  211. new BN(TRANSIENT_STAKE_SEED)
  212. )
  213. .accounts({
  214. stakePool: stakePoolPk,
  215. staker: kp.publicKey,
  216. stakePoolWithdrawAuthority: withdrawAuthorityPk,
  217. validatorList: validatorListPk,
  218. reserveStake: reserveStakePk,
  219. transientStake: transientStakePk,
  220. validatorStake: stakeAccountPk,
  221. validator: voteAccountPk,
  222. clock: SYSVAR_CLOCK_PUBKEY,
  223. rent: SYSVAR_RENT_PUBKEY,
  224. sysvarStakeHistory: SYSVAR_STAKE_HISTORY_PUBKEY,
  225. stakeConfig: STAKE_CONFIG_ID,
  226. systemProgram: SystemProgram.programId,
  227. stakeProgram: StakeProgram.programId,
  228. })
  229. .rpc();
  230. }
  231. async function decreaseValidatorStake() {
  232. const decreaseValidatorStakeIx = await program.methods
  233. .decreaseValidatorStake(
  234. new BN(LAMPORTS_PER_SOL),
  235. new BN(TRANSIENT_STAKE_SEED + 1)
  236. )
  237. .accounts({
  238. stakePool: stakePoolPk,
  239. staker: kp.publicKey,
  240. stakePoolWithdrawAuthority: withdrawAuthorityPk,
  241. validatorList: validatorListPk,
  242. validatorStake: stakeAccountPk,
  243. transientStake: (
  244. await PublicKey.findProgramAddress(
  245. [
  246. Buffer.from("transient"),
  247. voteAccountPk.toBuffer(),
  248. stakePoolPk.toBuffer(),
  249. new BN(TRANSIENT_STAKE_SEED + 1).toBuffer("le", 8),
  250. ],
  251. program.programId
  252. )
  253. )[0],
  254. clock: SYSVAR_CLOCK_PUBKEY,
  255. rent: SYSVAR_RENT_PUBKEY,
  256. systemProgram: SystemProgram.programId,
  257. stakeProgram: StakeProgram.programId,
  258. })
  259. .instruction();
  260. await simulateTx([decreaseValidatorStakeIx], [kp]);
  261. }
  262. async function setPreferredValidator() {
  263. await program.methods
  264. .setPreferredValidator({ deposit: {} }, null)
  265. .accounts({
  266. stakePoolAddress: stakePoolPk,
  267. staker: kp.publicKey,
  268. validatorListAddress: validatorListPk,
  269. })
  270. .rpc();
  271. }
  272. async function updateValidatorListBalance() {
  273. await program.methods
  274. .updateValidatorListBalance(0, true)
  275. .accounts({
  276. stakePool: stakePoolPk,
  277. stakePoolWithdrawAuthority: withdrawAuthorityPk,
  278. validatorListAddress: validatorListPk,
  279. reserveStake: reserveStakePk,
  280. clock: SYSVAR_CLOCK_PUBKEY,
  281. sysvarStakeHistory: SYSVAR_STAKE_HISTORY_PUBKEY,
  282. stakeProgram: StakeProgram.programId,
  283. })
  284. .rpc();
  285. }
  286. async function updateStakePoolBalance() {
  287. await program.methods
  288. .updateStakePoolBalance()
  289. .accounts({
  290. stakePool: stakePoolPk,
  291. withdrawAuthority: withdrawAuthorityPk,
  292. validatorListStorage: validatorListPk,
  293. reserveStake: reserveStakePk,
  294. managerFeeAccount: managerPoolTokenAccountPk,
  295. stakePoolMint: poolMintPk,
  296. tokenProgram: tokenProgram.programId,
  297. })
  298. .rpc();
  299. }
  300. async function cleanupRemovedValidatorEntries() {
  301. await program.methods
  302. .cleanupRemovedValidatorEntries()
  303. .accounts({
  304. stakePool: stakePoolPk,
  305. validatorListStorage: validatorListPk,
  306. })
  307. .rpc();
  308. }
  309. async function depositStake() {
  310. const DEPOSIT_AMOUNT = LAMPORTS_PER_SOL;
  311. const [poolDepositAuthorityPk] = await PublicKey.findProgramAddress(
  312. [stakePoolPk.toBuffer(), Buffer.from("deposit")],
  313. program.programId
  314. );
  315. const userStakeAccountKp = new Keypair();
  316. const createUserStakeAccountIxs = StakeProgram.createAccount({
  317. authorized: { staker: kp.publicKey, withdrawer: kp.publicKey },
  318. fromPubkey: kp.publicKey,
  319. lamports:
  320. (await provider.connection.getMinimumBalanceForRentExemption(
  321. StakeProgram.space
  322. )) + DEPOSIT_AMOUNT,
  323. stakePubkey: userStakeAccountKp.publicKey,
  324. }).instructions;
  325. const delegateUserStakeAccountIxs = StakeProgram.delegate({
  326. authorizedPubkey: kp.publicKey,
  327. stakePubkey: userStakeAccountKp.publicKey,
  328. votePubkey: voteAccountPk,
  329. }).instructions;
  330. const authorizeStakerIxs = StakeProgram.authorize({
  331. authorizedPubkey: kp.publicKey,
  332. newAuthorizedPubkey: poolDepositAuthorityPk,
  333. stakeAuthorizationType: { index: 0 },
  334. stakePubkey: userStakeAccountKp.publicKey,
  335. }).instructions;
  336. const authorizeWithdrawerIxs = StakeProgram.authorize({
  337. authorizedPubkey: kp.publicKey,
  338. newAuthorizedPubkey: poolDepositAuthorityPk,
  339. stakeAuthorizationType: { index: 1 },
  340. stakePubkey: userStakeAccountKp.publicKey,
  341. }).instructions;
  342. const depositStakeIx = await program.methods
  343. .depositStake()
  344. .accounts({
  345. stakePool: stakePoolPk,
  346. validatorListStorage: validatorListPk,
  347. stakePoolDepositAuthority: poolDepositAuthorityPk,
  348. stakePoolWithdrawAuthority: withdrawAuthorityPk,
  349. depositStakeAddress: userStakeAccountKp.publicKey,
  350. validatorStakeAccount: stakeAccountPk,
  351. reserveStakeAccount: reserveStakePk,
  352. poolTokensTo: userPoolTokenAccountPk,
  353. managerFeeAccount: managerPoolTokenAccountPk,
  354. referrerPoolTokensAccount: managerPoolTokenAccountPk,
  355. poolMint: poolMintPk,
  356. clock: SYSVAR_CLOCK_PUBKEY,
  357. sysvarStakeHistory: SYSVAR_STAKE_HISTORY_PUBKEY,
  358. tokenProgram: tokenProgram.programId,
  359. stakeProgram: StakeProgram.programId,
  360. })
  361. .instruction();
  362. await sendAndConfirmTx(
  363. [
  364. ...createUserStakeAccountIxs,
  365. ...delegateUserStakeAccountIxs,
  366. ...authorizeStakerIxs,
  367. ...authorizeWithdrawerIxs,
  368. depositStakeIx,
  369. ],
  370. [kp, userStakeAccountKp]
  371. );
  372. }
  373. async function withdrawStake() {
  374. const userStakeAccountKp = new Keypair();
  375. const createUserStakeAccountIx = SystemProgram.createAccount({
  376. fromPubkey: kp.publicKey,
  377. lamports: await provider.connection.getMinimumBalanceForRentExemption(
  378. StakeProgram.space
  379. ),
  380. newAccountPubkey: userStakeAccountKp.publicKey,
  381. programId: StakeProgram.programId,
  382. space: StakeProgram.space,
  383. });
  384. const withdrawStakeIx = await program.methods
  385. .withdrawStake(new BN(1))
  386. .accounts({
  387. stakePool: stakePoolPk,
  388. validatorListStorage: validatorListPk,
  389. stakePoolWithdraw: withdrawAuthorityPk,
  390. stakeToSplit: stakeAccountPk,
  391. stakeToReceive: userStakeAccountKp.publicKey,
  392. userStakeAuthority: kp.publicKey,
  393. userTransferAuthority: kp.publicKey,
  394. userPoolTokenAccount: userPoolTokenAccountPk,
  395. managerFeeAccount: managerPoolTokenAccountPk,
  396. poolMint: poolMintPk,
  397. clock: SYSVAR_CLOCK_PUBKEY,
  398. tokenProgram: tokenProgram.programId,
  399. stakeProgram: StakeProgram.programId,
  400. })
  401. .instruction();
  402. await sendAndConfirmTx(
  403. [createUserStakeAccountIx, withdrawStakeIx],
  404. [kp, userStakeAccountKp]
  405. );
  406. }
  407. async function setManager() {
  408. await program.methods
  409. .setManager()
  410. .accounts({
  411. stakePool: stakePoolPk,
  412. manager: kp.publicKey,
  413. newManager: kp.publicKey,
  414. newFeeReceiver: managerPoolTokenAccountPk,
  415. })
  416. .rpc();
  417. }
  418. async function setFee() {
  419. await program.methods
  420. .setFee({ solReferral: [5] })
  421. .accounts({
  422. stakePool: stakePoolPk,
  423. manager: kp.publicKey,
  424. })
  425. .rpc();
  426. }
  427. async function setStaker() {
  428. await program.methods
  429. .setStaker()
  430. .accounts({
  431. stakePool: stakePoolPk,
  432. setStakerAuthority: kp.publicKey,
  433. newStaker: kp.publicKey,
  434. })
  435. .rpc();
  436. }
  437. async function depositSol() {
  438. await program.methods
  439. .depositSol(new BN(1))
  440. .accounts({
  441. stakePool: stakePoolPk,
  442. stakePoolWithdrawAuthority: withdrawAuthorityPk,
  443. reserveStakeAccount: reserveStakePk,
  444. lamportsFrom: kp.publicKey,
  445. poolTokensTo: userPoolTokenAccountPk,
  446. managerFeeAccount: managerPoolTokenAccountPk,
  447. referrerPoolTokensAccount: managerPoolTokenAccountPk,
  448. poolMint: poolMintPk,
  449. systemProgram: SystemProgram.programId,
  450. tokenProgram: tokenProgram.programId,
  451. })
  452. .rpc();
  453. }
  454. async function setFundingAuthority() {
  455. await program.methods
  456. .setFundingAuthority({ stakeDeposit: {} })
  457. .accounts({
  458. stakePool: stakePoolPk,
  459. manager: kp.publicKey,
  460. })
  461. .rpc();
  462. }
  463. async function withdrawSol() {
  464. await program.methods
  465. .withdrawSol(new BN(1))
  466. .accounts({
  467. stakePool: stakePoolPk,
  468. stakePoolWithdrawAuthority: withdrawAuthorityPk,
  469. userTransferAuthority: kp.publicKey,
  470. poolTokensFrom: userPoolTokenAccountPk,
  471. reserveStakeAccount: reserveStakePk,
  472. lamportsTo: kp.publicKey,
  473. managerFeeAccount: managerPoolTokenAccountPk,
  474. poolMint: poolMintPk,
  475. clock: SYSVAR_CLOCK_PUBKEY,
  476. sysvarStakeHistory: SYSVAR_STAKE_HISTORY_PUBKEY,
  477. stakeProgram: StakeProgram.programId,
  478. tokenProgram: tokenProgram.programId,
  479. })
  480. .rpc();
  481. }
  482. // TODO: this should work but it's not tested
  483. // async function createTokenMetadata() {
  484. // await program.methods.createTokenMetadata(
  485. // "acheron",
  486. // "ACH",
  487. // "https://github.com/acheroncrypto"
  488. // ).accounts({
  489. // stakePool: stakePoolPk,
  490. // manager: kp.publicKey,
  491. // poolMint: poolMintPk,
  492. // payer: kp.publicKey,
  493. // tokenMetadata: ,
  494. // mplTokenMetadata: ,
  495. // systemProgram: SystemProgram.programId,
  496. // rent: SYSVAR_RENT_PUBKEY
  497. // }).rpc();
  498. // }
  499. // TODO: this should work but it's not tested
  500. // async function updateTokenMetadata() {
  501. // await program.methods.updateTokenMetadata(
  502. // "acheron",
  503. // "ACH",
  504. // "https://twitter.com/acheroncrypto"
  505. // ).accounts({
  506. // stakePool: stakePoolPk,
  507. // manager: kp.publicKey,
  508. // stakePoolWithdrawAuthority: withdrawAuthorityPk,
  509. // tokenMetadata: ,
  510. // mplTokenMetadata: ,
  511. // }).rpc();
  512. // }
  513. async function fetchStakePool() {
  514. const stakePool = await program.account.stakePool.fetch(stakePoolPk);
  515. assert(stakePool.manager.equals(kp.publicKey));
  516. }
  517. async function fetchValidatorList() {
  518. const validatorList = await program.account.validatorList.fetch(
  519. validatorListPk
  520. );
  521. assert(validatorList.header.maxValidators === VALIDATOR_LIST_COUNT);
  522. }
  523. await test(initialize);
  524. await test(addValidatorToPool);
  525. await test(removeValidatorFromPool);
  526. // Re-adding validator for other tests to pass
  527. await test(addValidatorToPool);
  528. await test(increaseValidatorStake);
  529. await test(decreaseValidatorStake);
  530. await test(setPreferredValidator);
  531. await test(updateValidatorListBalance);
  532. await test(updateStakePoolBalance);
  533. await test(cleanupRemovedValidatorEntries);
  534. await test(depositStake);
  535. await test(withdrawStake);
  536. await test(setManager);
  537. await test(setFee);
  538. await test(setStaker);
  539. await test(depositSol);
  540. await test(setFundingAuthority);
  541. await test(withdrawSol);
  542. // await test(createTokenMetadata);
  543. // await test(updateTokenMetadata);
  544. await test(fetchStakePool);
  545. await test(fetchValidatorList);
  546. }