lockup.js 29 KB

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