lockup.js 29 KB

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