lockup.js 29 KB

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