lockup.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968
  1. const anchor = require("@coral-xyz/anchor");
  2. const serumCmn = require("@project-serum/common");
  3. const { TOKEN_PROGRAM_ID } = require("@solana/spl-token");
  4. const utils = require("./utils");
  5. const { assert } = require("chai");
  6. const nativeAssert = require("assert");
  7. describe("Lockup and Registry", () => {
  8. // Read the provider from the configured environments.
  9. const provider = anchor.AnchorProvider.env();
  10. // hack so we don't have to update serum-common library
  11. // to the new AnchorProvider class and Provider interface
  12. provider.send = provider.sendAndConfirm;
  13. // Configure the client to use the provider.
  14. anchor.setProvider(provider);
  15. const lockup = anchor.workspace.Lockup;
  16. const registry = anchor.workspace.Registry;
  17. let lockupAddress = null;
  18. const WHITELIST_SIZE = 10;
  19. let mint = null;
  20. let god = null;
  21. it("Sets up initial test state", async () => {
  22. const [_mint, _god] = await serumCmn.createMintAndVault(
  23. provider,
  24. new anchor.BN(1000000)
  25. );
  26. mint = _mint;
  27. god = _god;
  28. });
  29. it("Is initialized!", async () => {
  30. await lockup.state.rpc.new({
  31. accounts: {
  32. authority: provider.wallet.publicKey,
  33. },
  34. });
  35. lockupAddress = await lockup.state.address();
  36. const lockupAccount = await lockup.state.fetch();
  37. assert.isTrue(lockupAccount.authority.equals(provider.wallet.publicKey));
  38. assert.strictEqual(lockupAccount.whitelist.length, WHITELIST_SIZE);
  39. lockupAccount.whitelist.forEach((e) => {
  40. assert.isTrue(e.programId.equals(anchor.web3.PublicKey.default));
  41. });
  42. });
  43. it("Deletes the default whitelisted addresses", async () => {
  44. const defaultEntry = { programId: anchor.web3.PublicKey.default };
  45. await lockup.state.rpc.whitelistDelete(defaultEntry, {
  46. accounts: {
  47. authority: provider.wallet.publicKey,
  48. },
  49. });
  50. });
  51. it("Sets a new authority", async () => {
  52. const newAuthority = anchor.web3.Keypair.generate();
  53. await lockup.state.rpc.setAuthority(newAuthority.publicKey, {
  54. accounts: {
  55. authority: provider.wallet.publicKey,
  56. },
  57. });
  58. let lockupAccount = await lockup.state.fetch();
  59. assert.isTrue(lockupAccount.authority.equals(newAuthority.publicKey));
  60. await lockup.state.rpc.setAuthority(provider.wallet.publicKey, {
  61. accounts: {
  62. authority: newAuthority.publicKey,
  63. },
  64. signers: [newAuthority],
  65. });
  66. lockupAccount = await lockup.state.fetch();
  67. assert.isTrue(lockupAccount.authority.equals(provider.wallet.publicKey));
  68. });
  69. const entries = [];
  70. it("Adds to the whitelist", async () => {
  71. const generateEntry = async () => {
  72. let programId = anchor.web3.Keypair.generate().publicKey;
  73. return {
  74. programId,
  75. };
  76. };
  77. for (let k = 0; k < WHITELIST_SIZE; k += 1) {
  78. entries.push(await generateEntry());
  79. }
  80. const accounts = {
  81. authority: provider.wallet.publicKey,
  82. };
  83. await lockup.state.rpc.whitelistAdd(entries[0], { accounts });
  84. let lockupAccount = await lockup.state.fetch();
  85. assert.lengthOf(lockupAccount.whitelist, 1);
  86. assert.deepEqual(lockupAccount.whitelist, [entries[0]]);
  87. for (let k = 1; k < WHITELIST_SIZE; k += 1) {
  88. await lockup.state.rpc.whitelistAdd(entries[k], { accounts });
  89. }
  90. lockupAccount = await lockup.state.fetch();
  91. assert.deepEqual(lockupAccount.whitelist, entries);
  92. await nativeAssert.rejects(
  93. async () => {
  94. const e = await generateEntry();
  95. await lockup.state.rpc.whitelistAdd(e, { accounts });
  96. },
  97. (err) => {
  98. assert.strictEqual(err.error.errorCode.number, 6008);
  99. assert.strictEqual(err.error.errorMessage, "Whitelist is full");
  100. return true;
  101. }
  102. );
  103. });
  104. it("Removes from the whitelist", async () => {
  105. await lockup.state.rpc.whitelistDelete(entries[0], {
  106. accounts: {
  107. authority: provider.wallet.publicKey,
  108. },
  109. });
  110. let lockupAccount = await lockup.state.fetch();
  111. assert.deepStrictEqual(lockupAccount.whitelist, entries.slice(1));
  112. });
  113. const vesting = anchor.web3.Keypair.generate();
  114. let vestingAccount = null;
  115. let vestingSigner = null;
  116. it("Creates a vesting account", async () => {
  117. const startTs = new anchor.BN(Date.now() / 1000);
  118. const endTs = new anchor.BN(startTs.toNumber() + 5);
  119. const periodCount = new anchor.BN(2);
  120. const beneficiary = provider.wallet.publicKey;
  121. const depositAmount = new anchor.BN(100);
  122. const vault = anchor.web3.Keypair.generate();
  123. let [_vestingSigner, nonce] =
  124. await anchor.web3.PublicKey.findProgramAddress(
  125. [vesting.publicKey.toBuffer()],
  126. lockup.programId
  127. );
  128. vestingSigner = _vestingSigner;
  129. await lockup.rpc.createVesting(
  130. beneficiary,
  131. depositAmount,
  132. nonce,
  133. startTs,
  134. endTs,
  135. periodCount,
  136. null, // Lock realizor is None.
  137. {
  138. accounts: {
  139. vesting: vesting.publicKey,
  140. vault: vault.publicKey,
  141. depositor: god,
  142. depositorAuthority: provider.wallet.publicKey,
  143. tokenProgram: TOKEN_PROGRAM_ID,
  144. rent: anchor.web3.SYSVAR_RENT_PUBKEY,
  145. clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
  146. },
  147. signers: [vesting, vault],
  148. instructions: [
  149. await lockup.account.vesting.createInstruction(vesting),
  150. ...(await serumCmn.createTokenAccountInstrs(
  151. provider,
  152. vault.publicKey,
  153. mint,
  154. vestingSigner
  155. )),
  156. ],
  157. }
  158. );
  159. vestingAccount = await lockup.account.vesting.fetch(vesting.publicKey);
  160. assert.isTrue(vestingAccount.beneficiary.equals(provider.wallet.publicKey));
  161. assert.isTrue(vestingAccount.mint.equals(mint));
  162. assert.isTrue(vestingAccount.grantor.equals(provider.wallet.publicKey));
  163. assert.isTrue(vestingAccount.outstanding.eq(depositAmount));
  164. assert.isTrue(vestingAccount.startBalance.eq(depositAmount));
  165. assert.isTrue(vestingAccount.whitelistOwned.eq(new anchor.BN(0)));
  166. assert.strictEqual(vestingAccount.nonce, nonce);
  167. assert.isTrue(vestingAccount.createdTs.gt(new anchor.BN(0)));
  168. assert.isTrue(vestingAccount.startTs.eq(startTs));
  169. assert.isTrue(vestingAccount.endTs.eq(endTs));
  170. assert.isNull(vestingAccount.realizor);
  171. });
  172. it("Fails to withdraw from a vesting account before vesting", async () => {
  173. await nativeAssert.rejects(
  174. async () => {
  175. await lockup.rpc.withdraw(new anchor.BN(100), {
  176. accounts: {
  177. vesting: vesting.publicKey,
  178. beneficiary: provider.wallet.publicKey,
  179. token: god,
  180. vault: vestingAccount.vault,
  181. vestingSigner: vestingSigner,
  182. tokenProgram: TOKEN_PROGRAM_ID,
  183. clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
  184. },
  185. });
  186. },
  187. (err) => {
  188. assert.strictEqual(err.error.errorCode.number, 6007);
  189. assert.strictEqual(
  190. err.error.errorMessage,
  191. "Insufficient withdrawal balance."
  192. );
  193. return true;
  194. }
  195. );
  196. });
  197. it("Waits for a vesting period to pass", async () => {
  198. await serumCmn.sleep(10 * 1000);
  199. });
  200. it("Withdraws from the vesting account", async () => {
  201. const token = await serumCmn.createTokenAccount(
  202. provider,
  203. mint,
  204. provider.wallet.publicKey
  205. );
  206. await lockup.rpc.withdraw(new anchor.BN(100), {
  207. accounts: {
  208. vesting: vesting.publicKey,
  209. beneficiary: provider.wallet.publicKey,
  210. token,
  211. vault: vestingAccount.vault,
  212. vestingSigner,
  213. tokenProgram: TOKEN_PROGRAM_ID,
  214. clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
  215. },
  216. });
  217. vestingAccount = await lockup.account.vesting.fetch(vesting.publicKey);
  218. assert.isTrue(vestingAccount.outstanding.eq(new anchor.BN(0)));
  219. const vaultAccount = await serumCmn.getTokenAccount(
  220. provider,
  221. vestingAccount.vault
  222. );
  223. assert.isTrue(vaultAccount.amount.eq(new anchor.BN(0)));
  224. const tokenAccount = await serumCmn.getTokenAccount(provider, token);
  225. assert.isTrue(tokenAccount.amount.eq(new anchor.BN(100)));
  226. });
  227. const registrar = anchor.web3.Keypair.generate();
  228. const rewardQ = anchor.web3.Keypair.generate();
  229. const withdrawalTimelock = new anchor.BN(4);
  230. const stakeRate = new anchor.BN(2);
  231. const rewardQLen = 170;
  232. let registrarAccount = null;
  233. let registrarSigner = null;
  234. let nonce = null;
  235. let poolMint = null;
  236. it("Creates registry genesis", async () => {
  237. const [_registrarSigner, _nonce] =
  238. await anchor.web3.PublicKey.findProgramAddress(
  239. [registrar.publicKey.toBuffer()],
  240. registry.programId
  241. );
  242. registrarSigner = _registrarSigner;
  243. nonce = _nonce;
  244. poolMint = await serumCmn.createMint(provider, registrarSigner);
  245. });
  246. it("Initializes registry's global state", async () => {
  247. await registry.state.rpc.new({
  248. accounts: { lockupProgram: lockup.programId },
  249. });
  250. const state = await registry.state.fetch();
  251. assert.isTrue(state.lockupProgram.equals(lockup.programId));
  252. // Should not allow a second initializatoin.
  253. await nativeAssert.rejects(
  254. async () => {
  255. await registry.state.rpc.new(lockup.programId);
  256. },
  257. (err) => {
  258. return true;
  259. }
  260. );
  261. });
  262. it("Initializes the registrar", async () => {
  263. await registry.rpc.initialize(
  264. mint,
  265. provider.wallet.publicKey,
  266. nonce,
  267. withdrawalTimelock,
  268. stakeRate,
  269. rewardQLen,
  270. {
  271. accounts: {
  272. registrar: registrar.publicKey,
  273. poolMint,
  274. rewardEventQ: rewardQ.publicKey,
  275. rent: anchor.web3.SYSVAR_RENT_PUBKEY,
  276. },
  277. signers: [registrar, rewardQ],
  278. instructions: [
  279. await registry.account.registrar.createInstruction(registrar),
  280. await registry.account.rewardQueue.createInstruction(rewardQ, 8250),
  281. ],
  282. }
  283. );
  284. registrarAccount = await registry.account.registrar.fetch(
  285. registrar.publicKey
  286. );
  287. assert.isTrue(registrarAccount.authority.equals(provider.wallet.publicKey));
  288. assert.strictEqual(registrarAccount.nonce, nonce);
  289. assert.isTrue(registrarAccount.mint.equals(mint));
  290. assert.isTrue(registrarAccount.poolMint.equals(poolMint));
  291. assert.isTrue(registrarAccount.stakeRate.eq(stakeRate));
  292. assert.isTrue(registrarAccount.rewardEventQ.equals(rewardQ.publicKey));
  293. assert.isTrue(registrarAccount.withdrawalTimelock.eq(withdrawalTimelock));
  294. });
  295. const member = anchor.web3.Keypair.generate();
  296. let memberAccount = null;
  297. let memberSigner = null;
  298. let balances = null;
  299. let balancesLocked = null;
  300. it("Creates a member", async () => {
  301. const [_memberSigner, nonce] =
  302. await anchor.web3.PublicKey.findProgramAddress(
  303. [registrar.publicKey.toBuffer(), member.publicKey.toBuffer()],
  304. registry.programId
  305. );
  306. memberSigner = _memberSigner;
  307. const [mainTx, _balances] = await utils.createBalanceSandbox(
  308. provider,
  309. registrarAccount,
  310. memberSigner
  311. );
  312. const [lockedTx, _balancesLocked] = await utils.createBalanceSandbox(
  313. provider,
  314. registrarAccount,
  315. memberSigner
  316. );
  317. balances = _balances;
  318. balancesLocked = _balancesLocked;
  319. const tx = registry.transaction.createMember(nonce, {
  320. accounts: {
  321. registrar: registrar.publicKey,
  322. member: member.publicKey,
  323. beneficiary: provider.wallet.publicKey,
  324. memberSigner,
  325. balances,
  326. balancesLocked,
  327. tokenProgram: TOKEN_PROGRAM_ID,
  328. rent: anchor.web3.SYSVAR_RENT_PUBKEY,
  329. },
  330. instructions: [await registry.account.member.createInstruction(member)],
  331. });
  332. const signers = [member, provider.wallet.payer];
  333. const allTxs = [mainTx, lockedTx, { tx, signers }];
  334. let txSigs = await provider.sendAll(allTxs);
  335. memberAccount = await registry.account.member.fetch(member.publicKey);
  336. assert.isTrue(memberAccount.registrar.equals(registrar.publicKey));
  337. assert.isTrue(memberAccount.beneficiary.equals(provider.wallet.publicKey));
  338. assert.isTrue(memberAccount.metadata.equals(anchor.web3.PublicKey.default));
  339. assert.strictEqual(
  340. JSON.stringify(memberAccount.balances),
  341. JSON.stringify(balances)
  342. );
  343. assert.strictEqual(
  344. JSON.stringify(memberAccount.balancesLocked),
  345. JSON.stringify(balancesLocked)
  346. );
  347. assert.strictEqual(memberAccount.rewardsCursor, 0);
  348. assert.isTrue(memberAccount.lastStakeTs.eq(new anchor.BN(0)));
  349. });
  350. it("Deposits (unlocked) to a member", async () => {
  351. const depositAmount = new anchor.BN(120);
  352. await registry.rpc.deposit(depositAmount, {
  353. accounts: {
  354. depositor: god,
  355. depositorAuthority: provider.wallet.publicKey,
  356. tokenProgram: TOKEN_PROGRAM_ID,
  357. vault: memberAccount.balances.vault,
  358. beneficiary: provider.wallet.publicKey,
  359. member: member.publicKey,
  360. },
  361. });
  362. const memberVault = await serumCmn.getTokenAccount(
  363. provider,
  364. memberAccount.balances.vault
  365. );
  366. assert.isTrue(memberVault.amount.eq(depositAmount));
  367. });
  368. /*
  369. it("Stakes to a member (unlocked)", async () => {
  370. const stakeAmount = new anchor.BN(10);
  371. await registry.rpc.stake(stakeAmount, false, {
  372. accounts: {
  373. // Stake instance.
  374. registrar: registrar.publicKey,
  375. rewardEventQ: rewardQ.publicKey,
  376. poolMint,
  377. // Member.
  378. member: member.publicKey,
  379. beneficiary: provider.wallet.publicKey,
  380. balances,
  381. balancesLocked,
  382. // Program signers.
  383. memberSigner,
  384. registrarSigner,
  385. // Misc.
  386. clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
  387. tokenProgram: TOKEN_PROGRAM_ID,
  388. },
  389. });
  390. const vault = await serumCmn.getTokenAccount(
  391. provider,
  392. memberAccount.balances.vault
  393. );
  394. const vaultStake = await serumCmn.getTokenAccount(
  395. provider,
  396. memberAccount.balances.vaultStake
  397. );
  398. const spt = await serumCmn.getTokenAccount(
  399. provider,
  400. memberAccount.balances.spt
  401. );
  402. assert.isTrue(vault.amount.eq(new anchor.BN(100)));
  403. assert.isTrue(vaultStake.amount.eq(new anchor.BN(20)));
  404. assert.isTrue(spt.amount.eq(new anchor.BN(10)));
  405. });
  406. const unlockedVendor = anchor.web3.Keypair.generate();
  407. const unlockedVendorVault = anchor.web3.Keypair.generate();
  408. let unlockedVendorSigner = null;
  409. it("Drops an unlocked reward", async () => {
  410. const rewardKind = {
  411. unlocked: {},
  412. };
  413. const rewardAmount = new anchor.BN(200);
  414. const expiry = new anchor.BN(Date.now() / 1000 + 5);
  415. const [_vendorSigner, nonce] =
  416. await anchor.web3.PublicKey.findProgramAddress(
  417. [registrar.publicKey.toBuffer(), unlockedVendor.publicKey.toBuffer()],
  418. registry.programId
  419. );
  420. unlockedVendorSigner = _vendorSigner;
  421. await registry.rpc.dropReward(
  422. rewardKind,
  423. rewardAmount,
  424. expiry,
  425. provider.wallet.publicKey,
  426. nonce,
  427. {
  428. accounts: {
  429. registrar: registrar.publicKey,
  430. rewardEventQ: rewardQ.publicKey,
  431. poolMint,
  432. vendor: unlockedVendor.publicKey,
  433. vendorVault: unlockedVendorVault.publicKey,
  434. depositor: god,
  435. depositorAuthority: provider.wallet.publicKey,
  436. tokenProgram: TOKEN_PROGRAM_ID,
  437. clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
  438. rent: anchor.web3.SYSVAR_RENT_PUBKEY,
  439. },
  440. signers: [unlockedVendorVault, unlockedVendor],
  441. instructions: [
  442. ...(await serumCmn.createTokenAccountInstrs(
  443. provider,
  444. unlockedVendorVault.publicKey,
  445. mint,
  446. unlockedVendorSigner
  447. )),
  448. await registry.account.rewardVendor.createInstruction(unlockedVendor),
  449. ],
  450. }
  451. );
  452. const vendorAccount = await registry.account.rewardVendor.fetch(
  453. unlockedVendor.publicKey
  454. );
  455. assert.isTrue(vendorAccount.registrar.equals(registrar.publicKey));
  456. assert.isTrue(vendorAccount.vault.equals(unlockedVendorVault.publicKey));
  457. assert.strictEqual(vendorAccount.nonce, nonce);
  458. assert.isTrue(vendorAccount.poolTokenSupply.eq(new anchor.BN(10)));
  459. assert.isTrue(vendorAccount.expiryTs.eq(expiry));
  460. assert.isTrue(
  461. vendorAccount.expiryReceiver.equals(provider.wallet.publicKey)
  462. );
  463. assert.isTrue(vendorAccount.total.eq(rewardAmount));
  464. assert.isFalse(vendorAccount.expired);
  465. assert.strictEqual(vendorAccount.rewardEventQCursor, 0);
  466. assert.deepEqual(vendorAccount.kind, rewardKind);
  467. const rewardQAccount = await registry.account.rewardQueue.fetch(
  468. rewardQ.publicKey
  469. );
  470. assert.strictEqual(rewardQAccount.head, 1);
  471. assert.strictEqual(rewardQAccount.tail, 0);
  472. const e = rewardQAccount.events[0];
  473. assert.isTrue(e.vendor.equals(unlockedVendor.publicKey));
  474. assert.strictEqual(e.locked, false);
  475. });
  476. it("Collects an unlocked reward", async () => {
  477. const token = await serumCmn.createTokenAccount(
  478. provider,
  479. mint,
  480. provider.wallet.publicKey
  481. );
  482. await registry.rpc.claimReward({
  483. accounts: {
  484. to: token,
  485. cmn: {
  486. registrar: registrar.publicKey,
  487. member: member.publicKey,
  488. beneficiary: provider.wallet.publicKey,
  489. balances,
  490. balancesLocked,
  491. vendor: unlockedVendor.publicKey,
  492. vault: unlockedVendorVault.publicKey,
  493. vendorSigner: unlockedVendorSigner,
  494. tokenProgram: TOKEN_PROGRAM_ID,
  495. clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
  496. },
  497. },
  498. });
  499. let tokenAccount = await serumCmn.getTokenAccount(provider, token);
  500. assert.isTrue(tokenAccount.amount.eq(new anchor.BN(200)));
  501. const memberAccount = await registry.account.member.fetch(member.publicKey);
  502. assert.strictEqual(memberAccount.rewardsCursor, 1);
  503. });
  504. const lockedVendor = anchor.web3.Keypair.generate();
  505. const lockedVendorVault = anchor.web3.Keypair.generate();
  506. let lockedVendorSigner = null;
  507. let lockedRewardAmount = null;
  508. let lockedRewardKind = null;
  509. it("Drops a locked reward", async () => {
  510. lockedRewardKind = {
  511. locked: {
  512. startTs: new anchor.BN(Date.now() / 1000),
  513. endTs: new anchor.BN(Date.now() / 1000 + 6),
  514. periodCount: new anchor.BN(2),
  515. },
  516. };
  517. lockedRewardAmount = new anchor.BN(200);
  518. const expiry = new anchor.BN(Date.now() / 1000 + 5);
  519. const [_vendorSigner, nonce] =
  520. await anchor.web3.PublicKey.findProgramAddress(
  521. [registrar.publicKey.toBuffer(), lockedVendor.publicKey.toBuffer()],
  522. registry.programId
  523. );
  524. lockedVendorSigner = _vendorSigner;
  525. await registry.rpc.dropReward(
  526. lockedRewardKind,
  527. lockedRewardAmount,
  528. expiry,
  529. provider.wallet.publicKey,
  530. nonce,
  531. {
  532. accounts: {
  533. registrar: registrar.publicKey,
  534. rewardEventQ: rewardQ.publicKey,
  535. poolMint,
  536. vendor: lockedVendor.publicKey,
  537. vendorVault: lockedVendorVault.publicKey,
  538. depositor: god,
  539. depositorAuthority: provider.wallet.publicKey,
  540. tokenProgram: TOKEN_PROGRAM_ID,
  541. clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
  542. rent: anchor.web3.SYSVAR_RENT_PUBKEY,
  543. },
  544. signers: [lockedVendorVault, lockedVendor],
  545. instructions: [
  546. ...(await serumCmn.createTokenAccountInstrs(
  547. provider,
  548. lockedVendorVault.publicKey,
  549. mint,
  550. lockedVendorSigner
  551. )),
  552. await registry.account.rewardVendor.createInstruction(lockedVendor),
  553. ],
  554. }
  555. );
  556. const vendorAccount = await registry.account.rewardVendor.fetch(
  557. lockedVendor.publicKey
  558. );
  559. assert.isTrue(vendorAccount.registrar.equals(registrar.publicKey));
  560. assert.isTrue(vendorAccount.vault.equals(lockedVendorVault.publicKey));
  561. assert.strictEqual(vendorAccount.nonce, nonce);
  562. assert.isTrue(vendorAccount.poolTokenSupply.eq(new anchor.BN(10)));
  563. assert.isTrue(vendorAccount.expiryTs.eq(expiry));
  564. assert.isTrue(
  565. vendorAccount.expiryReceiver.equals(provider.wallet.publicKey)
  566. );
  567. assert.isTrue(vendorAccount.total.eq(lockedRewardAmount));
  568. assert.isFalse(vendorAccount.expired);
  569. assert.strictEqual(vendorAccount.rewardEventQCursor, 1);
  570. assert.strictEqual(
  571. JSON.stringify(vendorAccount.kind),
  572. JSON.stringify(lockedRewardKind)
  573. );
  574. const rewardQAccount = await registry.account.rewardQueue.fetch(
  575. rewardQ.publicKey
  576. );
  577. assert.strictEqual(rewardQAccount.head, 2);
  578. assert.strictEqual(rewardQAccount.tail, 0);
  579. const e = rewardQAccount.events[1];
  580. assert.isTrue(e.vendor.equals(lockedVendor.publicKey));
  581. assert.isTrue(e.locked);
  582. });
  583. let vendoredVesting = null;
  584. let vendoredVestingVault = null;
  585. let vendoredVestingSigner = null;
  586. it("Claims a locked reward", async () => {
  587. vendoredVesting = anchor.web3.Keypair.generate();
  588. vendoredVestingVault = anchor.web3.Keypair.generate();
  589. let [_vendoredVestingSigner, nonce] =
  590. await anchor.web3.PublicKey.findProgramAddress(
  591. [vendoredVesting.publicKey.toBuffer()],
  592. lockup.programId
  593. );
  594. vendoredVestingSigner = _vendoredVestingSigner;
  595. const remainingAccounts = lockup.instruction.createVesting
  596. .accounts({
  597. vesting: vendoredVesting.publicKey,
  598. vault: vendoredVestingVault.publicKey,
  599. depositor: lockedVendorVault.publicKey,
  600. depositorAuthority: lockedVendorSigner,
  601. tokenProgram: TOKEN_PROGRAM_ID,
  602. rent: anchor.web3.SYSVAR_RENT_PUBKEY,
  603. clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
  604. })
  605. // Change the signer status on the vendor signer since it's signed by the program, not the
  606. // client.
  607. .map((meta) =>
  608. meta.pubkey === lockedVendorSigner ? { ...meta, isSigner: false } : meta
  609. );
  610. await registry.rpc.claimRewardLocked(nonce, {
  611. accounts: {
  612. registry: await registry.state.address(),
  613. lockupProgram: lockup.programId,
  614. cmn: {
  615. registrar: registrar.publicKey,
  616. member: member.publicKey,
  617. beneficiary: provider.wallet.publicKey,
  618. balances,
  619. balancesLocked,
  620. vendor: lockedVendor.publicKey,
  621. vault: lockedVendorVault.publicKey,
  622. vendorSigner: lockedVendorSigner,
  623. tokenProgram: TOKEN_PROGRAM_ID,
  624. clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
  625. },
  626. },
  627. remainingAccounts,
  628. signers: [vendoredVesting, vendoredVestingVault],
  629. instructions: [
  630. await lockup.account.vesting.createInstruction(vendoredVesting),
  631. ...(await serumCmn.createTokenAccountInstrs(
  632. provider,
  633. vendoredVestingVault.publicKey,
  634. mint,
  635. vendoredVestingSigner
  636. )),
  637. ],
  638. });
  639. const lockupAccount = await lockup.account.vesting.fetch(
  640. vendoredVesting.publicKey
  641. );
  642. assert.isTrue(lockupAccount.beneficiary.equals(provider.wallet.publicKey));
  643. assert.isTrue(lockupAccount.mint.equals(mint));
  644. assert.isTrue(lockupAccount.vault.equals(vendoredVestingVault.publicKey));
  645. assert.isTrue(lockupAccount.outstanding.eq(lockedRewardAmount));
  646. assert.isTrue(lockupAccount.startBalance.eq(lockedRewardAmount));
  647. assert.isTrue(lockupAccount.endTs.eq(lockedRewardKind.locked.endTs));
  648. assert.isTrue(
  649. lockupAccount.periodCount.eq(lockedRewardKind.locked.periodCount)
  650. );
  651. assert.isTrue(lockupAccount.whitelistOwned.eq(new anchor.BN(0)));
  652. assert.isTrue(lockupAccount.realizor.program.equals(registry.programId));
  653. assert.isTrue(lockupAccount.realizor.metadata.equals(member.publicKey));
  654. });
  655. it("Waits for the lockup period to pass", async () => {
  656. await serumCmn.sleep(10 * 1000);
  657. });
  658. it("Should fail to unlock an unrealized lockup reward", async () => {
  659. const token = await serumCmn.createTokenAccount(
  660. provider,
  661. mint,
  662. provider.wallet.publicKey
  663. );
  664. await nativeAssert.rejects(
  665. async () => {
  666. const withdrawAmount = new anchor.BN(10);
  667. await lockup.rpc.withdraw(withdrawAmount, {
  668. accounts: {
  669. vesting: vendoredVesting.publicKey,
  670. beneficiary: provider.wallet.publicKey,
  671. token,
  672. vault: vendoredVestingVault.publicKey,
  673. vestingSigner: vendoredVestingSigner,
  674. tokenProgram: TOKEN_PROGRAM_ID,
  675. clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
  676. },
  677. // TODO: trait methods generated on the client. Until then, we need to manually
  678. // specify the account metas here.
  679. remainingAccounts: [
  680. { pubkey: registry.programId, isWritable: false, isSigner: false },
  681. { pubkey: member.publicKey, isWritable: false, isSigner: false },
  682. { pubkey: balances.spt, isWritable: false, isSigner: false },
  683. { pubkey: balancesLocked.spt, isWritable: false, isSigner: false },
  684. ],
  685. });
  686. },
  687. (err) => {
  688. // Solana doesn't propagate errors across CPI. So we receive the registry's error code,
  689. // not the lockup's.
  690. assert.strictEqual(err.error.errorCode.number, 6020);
  691. assert.strictEqual(err.error.errorCode.code, "UnrealizedReward");
  692. assert.strictEqual(
  693. err.error.errorMessage,
  694. "Locked rewards cannot be realized until one unstaked all tokens."
  695. );
  696. expect(err.error.origin).to.deep.equal({
  697. file: "programs/registry/src/lib.rs",
  698. line: 63,
  699. });
  700. assert.strictEqual(
  701. err.program.toString(),
  702. "HmbTLCmaGvZhKnn1Zfa1JVnp7vkMV4DYVxPLWBVoN65L"
  703. );
  704. expect(err.programErrorStack.map((pk) => pk.toString())).to.deep.equal([
  705. "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS",
  706. "HmbTLCmaGvZhKnn1Zfa1JVnp7vkMV4DYVxPLWBVoN65L",
  707. ]);
  708. return true;
  709. }
  710. );
  711. });
  712. const pendingWithdrawal = anchor.web3.Keypair.generate();
  713. it("Unstakes (unlocked)", async () => {
  714. const unstakeAmount = new anchor.BN(10);
  715. await registry.rpc.startUnstake(unstakeAmount, false, {
  716. accounts: {
  717. registrar: registrar.publicKey,
  718. rewardEventQ: rewardQ.publicKey,
  719. poolMint,
  720. pendingWithdrawal: pendingWithdrawal.publicKey,
  721. member: member.publicKey,
  722. beneficiary: provider.wallet.publicKey,
  723. balances,
  724. balancesLocked,
  725. memberSigner,
  726. tokenProgram: TOKEN_PROGRAM_ID,
  727. clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
  728. rent: anchor.web3.SYSVAR_RENT_PUBKEY,
  729. },
  730. signers: [pendingWithdrawal],
  731. instructions: [
  732. await registry.account.pendingWithdrawal.createInstruction(
  733. pendingWithdrawal
  734. ),
  735. ],
  736. });
  737. const vaultPw = await serumCmn.getTokenAccount(
  738. provider,
  739. memberAccount.balances.vaultPw
  740. );
  741. const vaultStake = await serumCmn.getTokenAccount(
  742. provider,
  743. memberAccount.balances.vaultStake
  744. );
  745. const spt = await serumCmn.getTokenAccount(
  746. provider,
  747. memberAccount.balances.spt
  748. );
  749. assert.isTrue(vaultPw.amount.eq(new anchor.BN(20)));
  750. assert.isTrue(vaultStake.amount.eq(new anchor.BN(0)));
  751. assert.isTrue(spt.amount.eq(new anchor.BN(0)));
  752. });
  753. const tryEndUnstake = async () => {
  754. await registry.rpc.endUnstake({
  755. accounts: {
  756. registrar: registrar.publicKey,
  757. member: member.publicKey,
  758. beneficiary: provider.wallet.publicKey,
  759. pendingWithdrawal: pendingWithdrawal.publicKey,
  760. vault: balances.vault,
  761. vaultPw: balances.vaultPw,
  762. memberSigner,
  763. clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
  764. tokenProgram: TOKEN_PROGRAM_ID,
  765. },
  766. });
  767. };
  768. it("Fails to end unstaking before timelock", async () => {
  769. await nativeAssert.rejects(
  770. async () => {
  771. await tryEndUnstake();
  772. },
  773. (err) => {
  774. assert.strictEqual(err.error.errorCode.number, 6009);
  775. assert.strictEqual(
  776. err.error.errorMessage,
  777. "The unstake timelock has not yet expired."
  778. );
  779. return true;
  780. }
  781. );
  782. });
  783. it("Waits for the unstake period to end", async () => {
  784. await serumCmn.sleep(5000);
  785. });
  786. it("Unstake finalizes (unlocked)", async () => {
  787. await tryEndUnstake();
  788. const vault = await serumCmn.getTokenAccount(
  789. provider,
  790. memberAccount.balances.vault
  791. );
  792. const vaultPw = await serumCmn.getTokenAccount(
  793. provider,
  794. memberAccount.balances.vaultPw
  795. );
  796. assert.isTrue(vault.amount.eq(new anchor.BN(120)));
  797. assert.isTrue(vaultPw.amount.eq(new anchor.BN(0)));
  798. });
  799. it("Withdraws deposits (unlocked)", async () => {
  800. const token = await serumCmn.createTokenAccount(
  801. provider,
  802. mint,
  803. provider.wallet.publicKey
  804. );
  805. const withdrawAmount = new anchor.BN(100);
  806. await registry.rpc.withdraw(withdrawAmount, {
  807. accounts: {
  808. registrar: registrar.publicKey,
  809. member: member.publicKey,
  810. beneficiary: provider.wallet.publicKey,
  811. vault: memberAccount.balances.vault,
  812. memberSigner,
  813. depositor: token,
  814. tokenProgram: TOKEN_PROGRAM_ID,
  815. },
  816. });
  817. const tokenAccount = await serumCmn.getTokenAccount(provider, token);
  818. assert.isTrue(tokenAccount.amount.eq(withdrawAmount));
  819. });
  820. it("Should successfully unlock a locked reward after unstaking", async () => {
  821. const token = await serumCmn.createTokenAccount(
  822. provider,
  823. mint,
  824. provider.wallet.publicKey
  825. );
  826. const withdrawAmount = new anchor.BN(7);
  827. await lockup.rpc.withdraw(withdrawAmount, {
  828. accounts: {
  829. vesting: vendoredVesting.publicKey,
  830. beneficiary: provider.wallet.publicKey,
  831. token,
  832. vault: vendoredVestingVault.publicKey,
  833. vestingSigner: vendoredVestingSigner,
  834. tokenProgram: TOKEN_PROGRAM_ID,
  835. clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
  836. },
  837. // TODO: trait methods generated on the client. Until then, we need to manually
  838. // specify the account metas here.
  839. remainingAccounts: [
  840. { pubkey: registry.programId, isWritable: false, isSigner: false },
  841. { pubkey: member.publicKey, isWritable: false, isSigner: false },
  842. { pubkey: balances.spt, isWritable: false, isSigner: false },
  843. { pubkey: balancesLocked.spt, isWritable: false, isSigner: false },
  844. ],
  845. });
  846. const tokenAccount = await serumCmn.getTokenAccount(provider, token);
  847. assert.isTrue(tokenAccount.amount.eq(withdrawAmount));
  848. });
  849. */
  850. });