lockup.js 30 KB

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