optional.ts 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845
  1. import * as anchor from "@coral-xyz/anchor";
  2. import {
  3. Program,
  4. web3,
  5. BN,
  6. AnchorError,
  7. LangErrorCode,
  8. LangErrorMessage,
  9. translateError,
  10. parseIdlErrors,
  11. } from "@coral-xyz/anchor";
  12. import { Optional } from "../target/types/optional";
  13. import { AllowMissingOptionals } from "../target/types/allow_missing_optionals";
  14. import { assert, expect } from "chai";
  15. describe("Optional", () => {
  16. // configure the client to use the local cluster
  17. anchor.setProvider(anchor.AnchorProvider.env());
  18. const anchorProvider = anchor.AnchorProvider.env();
  19. const program = anchor.workspace.Optional as Program<Optional>;
  20. const DATA_PDA_PREFIX = "data_pda";
  21. const makeDataPdaSeeds = (dataAccount: web3.PublicKey) => {
  22. return [Buffer.from(DATA_PDA_PREFIX), dataAccount.toBuffer()];
  23. };
  24. const findDataPda = (
  25. dataAccount: web3.PublicKey
  26. ): [web3.PublicKey, number] => {
  27. return web3.PublicKey.findProgramAddressSync(
  28. makeDataPdaSeeds(dataAccount),
  29. program.programId
  30. );
  31. };
  32. // payer of the transactions
  33. const payerWallet = (program.provider as anchor.AnchorProvider).wallet;
  34. const payer = payerWallet.publicKey;
  35. const systemProgram = web3.SystemProgram.programId;
  36. const requiredKeypair1 = web3.Keypair.generate();
  37. const requiredKeypair2 = web3.Keypair.generate();
  38. let createRequiredIx1: web3.TransactionInstruction;
  39. let createRequiredIx2: web3.TransactionInstruction;
  40. const dataAccountKeypair1 = web3.Keypair.generate();
  41. const dataAccountKeypair2 = web3.Keypair.generate();
  42. const dataPda1 = findDataPda(dataAccountKeypair1.publicKey);
  43. const dataPda2 = findDataPda(dataAccountKeypair2.publicKey);
  44. const initializeValue1 = new BN(10);
  45. const initializeValue2 = new BN(100);
  46. const initializeKey = web3.PublicKey.default;
  47. const createRequired = async (
  48. requiredKeypair?: web3.Keypair
  49. ): Promise<[web3.Keypair, web3.TransactionInstruction]> => {
  50. const keypair = requiredKeypair ?? new web3.Keypair();
  51. const createIx = await program.account.dataAccount.createInstruction(
  52. keypair
  53. );
  54. return [keypair, createIx];
  55. };
  56. before("Setup async stuff", async () => {
  57. createRequiredIx1 = (await createRequired(requiredKeypair1))[1];
  58. createRequiredIx2 = (await createRequired(requiredKeypair2))[1];
  59. });
  60. describe("Missing optionals feature tests", async () => {
  61. it("Fails with missing optional accounts at the end by default", async () => {
  62. const [requiredKeypair, createRequiredIx] = await createRequired();
  63. const initializeIx = await program.methods
  64. .initialize(initializeValue1, initializeKey)
  65. .accountsPartial({
  66. payer: null,
  67. optionalAccount: null,
  68. systemProgram,
  69. required: requiredKeypair.publicKey,
  70. optionalPda: null,
  71. })
  72. .signers([requiredKeypair])
  73. .instruction();
  74. initializeIx.keys.pop();
  75. const initializeTxn = new web3.Transaction()
  76. .add(createRequiredIx)
  77. .add(initializeIx);
  78. try {
  79. await anchorProvider
  80. .sendAndConfirm(initializeTxn, [requiredKeypair])
  81. .catch((e) => {
  82. throw translateError(e, parseIdlErrors(program.idl));
  83. });
  84. assert.fail(
  85. "Unexpected success in creating a transaction that should have failed with `AccountNotEnoughKeys` error"
  86. );
  87. } catch (e) {
  88. // @ts-ignore
  89. assert.isTrue(e instanceof AnchorError, e.toString());
  90. const err: AnchorError = <AnchorError>e;
  91. const errorCode = LangErrorCode.AccountNotEnoughKeys;
  92. assert.strictEqual(
  93. err.error.errorMessage,
  94. LangErrorMessage.get(errorCode)
  95. );
  96. assert.strictEqual(err.error.errorCode.number, errorCode);
  97. }
  98. });
  99. it("Succeeds with missing optional accounts at the end with the feature on", async () => {
  100. const allowMissingOptionals = anchor.workspace
  101. .AllowMissingOptionals as Program<AllowMissingOptionals>;
  102. const doStuffIx = await allowMissingOptionals.methods
  103. .doStuff()
  104. .accountsPartial({
  105. payer,
  106. systemProgram,
  107. optional2: null,
  108. })
  109. .instruction();
  110. doStuffIx.keys.pop();
  111. doStuffIx.keys.pop();
  112. const doStuffTxn = new web3.Transaction().add(doStuffIx);
  113. await anchorProvider.sendAndConfirm(doStuffTxn);
  114. });
  115. });
  116. describe("Initialize tests", async () => {
  117. it("Initialize with required null fails anchor-ts validation", async () => {
  118. const [requiredKeypair, createRequiredIx] = await createRequired();
  119. try {
  120. await program.methods
  121. .initialize(initializeValue1, initializeKey)
  122. .preInstructions([createRequiredIx])
  123. .accountsPartial({
  124. payer,
  125. systemProgram,
  126. // @ts-ignore
  127. required: null, //requiredKeypair.publicKey,
  128. optionalPda: null,
  129. optionalAccount: null,
  130. })
  131. .signers([requiredKeypair])
  132. .rpc();
  133. assert.fail(
  134. "Unexpected success in creating a transaction that should have failed at the client level"
  135. );
  136. } catch (e) {
  137. const errMsg = "Account `required` not provided";
  138. // @ts-ignore
  139. let error: string = e.toString();
  140. assert(error.includes(errMsg), `Unexpected error: ${e}`);
  141. }
  142. });
  143. it("Can initialize with no payer and no optionals", async () => {
  144. const [requiredKeypair, createRequiredIx] = await createRequired();
  145. await program.methods
  146. .initialize(initializeValue1, initializeKey)
  147. .preInstructions([createRequiredIx])
  148. .accountsPartial({
  149. payer: null,
  150. systemProgram,
  151. required: requiredKeypair.publicKey,
  152. optionalPda: null,
  153. optionalAccount: null,
  154. })
  155. .signers([requiredKeypair])
  156. .rpc();
  157. let required = await program.account.dataAccount.fetch(
  158. requiredKeypair.publicKey
  159. );
  160. expect(required.data.toNumber()).to.equal(0);
  161. });
  162. it("Can initialize with no optionals", async () => {
  163. const [requiredKeypair, createRequiredIx] = await createRequired();
  164. await program.methods
  165. .initialize(initializeValue1, initializeKey)
  166. .preInstructions([createRequiredIx])
  167. .accountsPartial({
  168. payer: null,
  169. systemProgram: null,
  170. required: requiredKeypair.publicKey,
  171. optionalPda: null,
  172. optionalAccount: null,
  173. })
  174. .signers([requiredKeypair])
  175. .rpc();
  176. let required = await program.account.dataAccount.fetch(
  177. requiredKeypair.publicKey
  178. );
  179. expect(required.data.toNumber()).to.equal(0);
  180. });
  181. it("Initialize with optionals and missing system program fails optional checks", async () => {
  182. const [requiredKeypair, createRequiredIx] = await createRequired();
  183. const dataAccount = new web3.Keypair();
  184. try {
  185. await program.methods
  186. .initialize(initializeValue1, initializeKey)
  187. .preInstructions([createRequiredIx])
  188. .accountsStrict({
  189. payer,
  190. systemProgram: null,
  191. required: requiredKeypair.publicKey,
  192. optionalPda: null,
  193. optionalAccount: dataAccount.publicKey,
  194. })
  195. .signers([requiredKeypair, dataAccount])
  196. .rpc();
  197. assert.fail(
  198. "Unexpected success in creating a transaction that should have failed with `ConstraintAccountIsNone` error"
  199. );
  200. } catch (e) {
  201. // @ts-ignore
  202. assert.isTrue(e instanceof AnchorError, e.toString());
  203. const err: AnchorError = <AnchorError>e;
  204. const errorCode = LangErrorCode.ConstraintAccountIsNone;
  205. assert.strictEqual(
  206. err.error.errorMessage,
  207. LangErrorMessage.get(errorCode)
  208. );
  209. assert.strictEqual(err.error.errorCode.number, errorCode);
  210. }
  211. });
  212. it("Unwrapping None account in constraint panics", async () => {
  213. const [requiredKeypair, createRequiredIx] = await createRequired();
  214. const dataAccount = new web3.Keypair();
  215. const [dataPda] = findDataPda(dataAccount.publicKey);
  216. try {
  217. await program.methods
  218. .initialize(initializeValue1, initializeKey)
  219. .preInstructions([createRequiredIx])
  220. .accountsPartial({
  221. payer,
  222. systemProgram,
  223. required: requiredKeypair.publicKey,
  224. optionalPda: dataPda,
  225. optionalAccount: null,
  226. })
  227. .signers([requiredKeypair])
  228. .rpc();
  229. assert.fail(
  230. "Unexpected success in creating a transaction that should have failed with `ProgramFailedToComplete` error"
  231. );
  232. } catch (e) {
  233. const errMsg = "Program failed to complete";
  234. // @ts-ignore
  235. let error: string = e.toString();
  236. assert(error.includes(errMsg), `Unexpected error: ${e}`);
  237. }
  238. });
  239. it("Can initialize with required and optional account", async () => {
  240. await program.methods
  241. .initialize(initializeValue1, initializeKey)
  242. .preInstructions([createRequiredIx1])
  243. .accountsPartial({
  244. payer,
  245. systemProgram,
  246. required: requiredKeypair1.publicKey,
  247. optionalPda: null,
  248. optionalAccount: dataAccountKeypair1.publicKey,
  249. })
  250. .signers([requiredKeypair1, dataAccountKeypair1])
  251. .rpc();
  252. const requiredDataAccount = await program.account.dataAccount.fetch(
  253. requiredKeypair1.publicKey
  254. );
  255. expect(requiredDataAccount.data.toNumber()).to.equal(0);
  256. const optionalDataAccount = await program.account.dataAccount.fetch(
  257. dataAccountKeypair1.publicKey
  258. );
  259. expect(optionalDataAccount.data.toNumber()).to.equal(
  260. initializeValue1.muln(2).toNumber()
  261. );
  262. });
  263. it("Invalid seeds with all accounts provided fails", async () => {
  264. try {
  265. await program.methods
  266. .initialize(initializeValue2, initializeKey)
  267. .preInstructions([createRequiredIx2])
  268. .accountsPartial({
  269. payer,
  270. systemProgram,
  271. required: requiredKeypair2.publicKey,
  272. optionalPda: dataPda1[0],
  273. optionalAccount: dataAccountKeypair2.publicKey,
  274. })
  275. .signers([requiredKeypair2, dataAccountKeypair2])
  276. .rpc();
  277. assert.fail(
  278. "Unexpected success in creating a transaction that should have failed with `ConstraintSeeds` error"
  279. );
  280. } catch (e) {
  281. // @ts-ignore
  282. assert.isTrue(e instanceof AnchorError, e.toString());
  283. const err: AnchorError = <AnchorError>e;
  284. const errorCode = LangErrorCode.ConstraintSeeds;
  285. assert.strictEqual(
  286. err.error.errorMessage,
  287. LangErrorMessage.get(errorCode)
  288. );
  289. assert.strictEqual(err.error.errorCode.number, errorCode);
  290. }
  291. });
  292. it("Can initialize with all accounts provided", async () => {
  293. await program.methods
  294. .initialize(initializeValue2, initializeKey)
  295. .preInstructions([createRequiredIx2])
  296. .accountsPartial({
  297. payer,
  298. systemProgram,
  299. required: requiredKeypair2.publicKey,
  300. optionalPda: dataPda2[0],
  301. optionalAccount: dataAccountKeypair2.publicKey,
  302. })
  303. .signers([requiredKeypair2, dataAccountKeypair2])
  304. .rpc();
  305. const requiredDataAccount = await program.account.dataAccount.fetch(
  306. requiredKeypair2.publicKey
  307. );
  308. expect(requiredDataAccount.data.toNumber()).to.equal(0);
  309. const optionalDataAccount = await program.account.dataAccount.fetch(
  310. dataAccountKeypair2.publicKey
  311. );
  312. expect(optionalDataAccount.data.toNumber()).to.equal(
  313. initializeValue2.toNumber()
  314. );
  315. const optionalDataPda = await program.account.dataPda.fetch(dataPda2[0]);
  316. expect(optionalDataPda.dataAccount.toString()).to.equal(
  317. initializeKey.toString()
  318. );
  319. });
  320. });
  321. describe("Update tests", async () => {
  322. it("Can update with invalid explicit pda bump with no pda", async () => {
  323. await program.methods
  324. .update(initializeValue2, initializeKey, dataPda2[1] - 1)
  325. .accountsPartial({
  326. payer,
  327. optionalPda: null,
  328. optionalAccount: null,
  329. })
  330. .rpc();
  331. });
  332. it("Errors with invalid explicit pda bump with pda included", async () => {
  333. try {
  334. await program.methods
  335. .update(initializeValue2, initializeKey, dataPda2[1] - 1)
  336. .accountsPartial({
  337. payer,
  338. optionalPda: dataPda2[0],
  339. optionalAccount: dataAccountKeypair2.publicKey,
  340. })
  341. .signers([dataAccountKeypair2])
  342. .rpc();
  343. assert.fail(
  344. "Unexpected success in creating a transaction that should have failed with `ConstraintSeeds` error"
  345. );
  346. } catch (e) {
  347. // @ts-ignore
  348. assert.isTrue(e instanceof AnchorError, e.toString());
  349. const err: AnchorError = <AnchorError>e;
  350. const errorCode = LangErrorCode.ConstraintSeeds;
  351. assert.strictEqual(
  352. err.error.errorMessage,
  353. LangErrorMessage.get(errorCode)
  354. );
  355. assert.strictEqual(err.error.errorCode.number, errorCode);
  356. }
  357. });
  358. it("Fails with a missing signer", async () => {
  359. try {
  360. let txn = await program.methods
  361. .update(initializeValue2, initializeKey, dataPda2[1])
  362. .accountsPartial({
  363. payer,
  364. optionalPda: dataPda2[0],
  365. optionalAccount: dataAccountKeypair2.publicKey,
  366. })
  367. .transaction();
  368. txn.instructions[0].keys.forEach((meta) => {
  369. if (meta.pubkey.equals(dataAccountKeypair2.publicKey)) {
  370. meta.isSigner = false;
  371. }
  372. });
  373. await anchorProvider.sendAndConfirm(txn);
  374. assert.fail(
  375. "Unexpected success in creating a transaction that should have failed with `ConstraintSigner` error"
  376. );
  377. } catch (e) {
  378. // @ts-ignore
  379. assert.isTrue(e instanceof web3.SendTransactionError, e.toString());
  380. const err: web3.SendTransactionError = <web3.SendTransactionError>e;
  381. const anchorError = AnchorError.parse(err.logs!)!;
  382. const errorCode = LangErrorCode.ConstraintSigner;
  383. assert.strictEqual(
  384. anchorError.error.errorMessage,
  385. LangErrorMessage.get(errorCode)
  386. );
  387. assert.strictEqual(anchorError.error.errorCode.number, errorCode);
  388. }
  389. });
  390. it("Can trigger raw constraint violations with references to optional accounts", async () => {
  391. try {
  392. await program.methods
  393. .update(initializeValue2, initializeKey, dataPda2[1])
  394. .accountsPartial({
  395. payer: null,
  396. optionalPda: dataPda2[0],
  397. optionalAccount: dataAccountKeypair2.publicKey,
  398. })
  399. .signers([dataAccountKeypair2])
  400. .rpc();
  401. assert.fail(
  402. "Unexpected success in creating a transaction that should have failed with `ConstraintRaw` error"
  403. );
  404. } catch (e) {
  405. // @ts-ignore
  406. assert.isTrue(e instanceof AnchorError, e.toString());
  407. const err: AnchorError = <AnchorError>e;
  408. const errorCode = LangErrorCode.ConstraintRaw;
  409. assert.strictEqual(
  410. err.error.errorMessage,
  411. LangErrorMessage.get(errorCode)
  412. );
  413. assert.strictEqual(err.error.errorCode.number, errorCode);
  414. }
  415. });
  416. it("Can update an optional account", async () => {
  417. await program.methods
  418. .update(initializeValue2.muln(3), initializeKey, dataPda2[1])
  419. .accountsPartial({
  420. payer,
  421. optionalPda: null,
  422. optionalAccount: dataAccountKeypair2.publicKey,
  423. })
  424. .signers([dataAccountKeypair2])
  425. .rpc();
  426. const dataAccount = await program.account.dataAccount.fetch(
  427. dataAccountKeypair2.publicKey
  428. );
  429. expect(dataAccount.data.toNumber()).to.equal(
  430. initializeValue2.muln(3).toNumber()
  431. );
  432. });
  433. it("Can update both accounts", async () => {
  434. const newKey = web3.PublicKey.unique();
  435. await program.methods
  436. .update(initializeValue2, newKey, dataPda2[1])
  437. .accountsPartial({
  438. payer,
  439. optionalPda: dataPda2[0],
  440. optionalAccount: dataAccountKeypair2.publicKey,
  441. })
  442. .signers([dataAccountKeypair2])
  443. .rpc();
  444. const dataPda = await program.account.dataPda.fetch(dataPda2[0]);
  445. expect(dataPda.dataAccount.toString()).to.equal(newKey.toString());
  446. const dataAccount = await program.account.dataAccount.fetch(
  447. dataAccountKeypair2.publicKey
  448. );
  449. expect(dataAccount.data.toNumber()).to.equal(initializeValue2.toNumber());
  450. });
  451. });
  452. describe("Realloc tests", async () => {
  453. it("Realloc with no payer fails", async () => {
  454. try {
  455. await program.methods
  456. .realloc(new BN(100))
  457. .accountsPartial({
  458. payer: null,
  459. required: dataAccountKeypair1.publicKey,
  460. optionalPda: null,
  461. optionalAccount: dataAccountKeypair2.publicKey,
  462. systemProgram,
  463. })
  464. .signers([dataAccountKeypair2])
  465. .rpc();
  466. assert.fail(
  467. "Unexpected success in creating a transaction that should have failed with `ConstraintAccountIsNone` error"
  468. );
  469. } catch (e) {
  470. // @ts-ignore
  471. assert.isTrue(e instanceof AnchorError, e.toString());
  472. const err: AnchorError = <AnchorError>e;
  473. const errorCode = LangErrorCode.ConstraintAccountIsNone;
  474. assert.strictEqual(
  475. err.error.errorMessage,
  476. LangErrorMessage.get(errorCode)
  477. );
  478. assert.strictEqual(err.error.errorCode.number, errorCode);
  479. }
  480. });
  481. it("Realloc with no system program fails", async () => {
  482. try {
  483. await program.methods
  484. .realloc(new BN(100))
  485. .accountsStrict({
  486. payer,
  487. required: dataAccountKeypair1.publicKey,
  488. optionalPda: null,
  489. optionalAccount: dataAccountKeypair2.publicKey,
  490. systemProgram: null,
  491. })
  492. .signers([dataAccountKeypair2])
  493. .rpc();
  494. assert.fail(
  495. "Unexpected success in creating a transaction that should have failed with `ConstraintAccountIsNone` error"
  496. );
  497. } catch (e) {
  498. // @ts-ignore
  499. assert.isTrue(e instanceof AnchorError, e.toString());
  500. const err: AnchorError = <AnchorError>e;
  501. const errorCode = LangErrorCode.ConstraintAccountIsNone;
  502. assert.strictEqual(
  503. err.error.errorMessage,
  504. LangErrorMessage.get(errorCode)
  505. );
  506. assert.strictEqual(err.error.errorCode.number, errorCode);
  507. }
  508. });
  509. it("Wrong type of account is caught for optional accounts", async () => {
  510. try {
  511. await program.methods
  512. .realloc(new BN(100))
  513. .accountsPartial({
  514. payer,
  515. required: dataAccountKeypair1.publicKey,
  516. optionalPda: dataAccountKeypair2.publicKey,
  517. optionalAccount: null,
  518. systemProgram,
  519. })
  520. .rpc();
  521. assert.fail(
  522. "Unexpected success in creating a transaction that should have failed with `AccountDiscriminatorMismatch` error"
  523. );
  524. } catch (e) {
  525. // @ts-ignore
  526. assert.isTrue(e instanceof AnchorError, e.toString());
  527. const err: AnchorError = <AnchorError>e;
  528. const errorCode = LangErrorCode.AccountDiscriminatorMismatch;
  529. assert.strictEqual(
  530. err.error.errorMessage,
  531. LangErrorMessage.get(errorCode)
  532. );
  533. assert.strictEqual(err.error.errorCode.number, errorCode);
  534. }
  535. });
  536. it("Can realloc with optional accounts", async () => {
  537. const newLength = 100;
  538. await program.methods
  539. .realloc(new BN(newLength))
  540. .accountsPartial({
  541. payer,
  542. required: dataAccountKeypair1.publicKey,
  543. optionalPda: null,
  544. optionalAccount: dataAccountKeypair2.publicKey,
  545. systemProgram,
  546. })
  547. .signers([dataAccountKeypair2])
  548. .rpc();
  549. const dataAccount = await program.provider.connection.getAccountInfo(
  550. dataAccountKeypair2.publicKey
  551. );
  552. assert.exists(dataAccount);
  553. expect(dataAccount!.data.length).to.equal(newLength);
  554. });
  555. it("Can realloc back to original size with optional accounts", async () => {
  556. const newLength = program.account.dataAccount.size;
  557. await program.methods
  558. .realloc(new BN(newLength))
  559. .accountsPartial({
  560. payer,
  561. required: dataAccountKeypair1.publicKey,
  562. optionalPda: null,
  563. optionalAccount: dataAccountKeypair2.publicKey,
  564. systemProgram,
  565. })
  566. .signers([dataAccountKeypair2])
  567. .rpc();
  568. const dataAccount = await program.provider.connection.getAccountInfo(
  569. dataAccountKeypair2.publicKey
  570. );
  571. assert.exists(dataAccount);
  572. expect(dataAccount!.data.length).to.equal(newLength);
  573. });
  574. it("Can realloc multiple optional accounts", async () => {
  575. const newLength = 100;
  576. await program.methods
  577. .realloc(new BN(newLength))
  578. .accountsPartial({
  579. payer,
  580. required: dataAccountKeypair1.publicKey,
  581. optionalPda: dataPda2[0],
  582. optionalAccount: dataAccountKeypair2.publicKey,
  583. systemProgram,
  584. })
  585. .signers([dataAccountKeypair2])
  586. .rpc();
  587. const dataAccount = await program.provider.connection.getAccountInfo(
  588. dataAccountKeypair2.publicKey
  589. );
  590. assert.exists(dataAccount);
  591. expect(dataAccount!.data.length).to.equal(newLength);
  592. const dataPda = await program.provider.connection.getAccountInfo(
  593. dataPda2[0]
  594. );
  595. assert.exists(dataPda);
  596. expect(dataPda!.data.length).to.equal(newLength);
  597. });
  598. });
  599. describe("Close tests", async () => {
  600. const requiredKeypair3 = web3.Keypair.generate();
  601. const requiredKeypair4 = web3.Keypair.generate();
  602. let createRequiredIx3: web3.TransactionInstruction;
  603. let createRequiredIx4: web3.TransactionInstruction;
  604. const dataAccountKeypair3 = web3.Keypair.generate();
  605. const dataAccountKeypair4 = web3.Keypair.generate();
  606. const dataPda3 = findDataPda(dataAccountKeypair3.publicKey);
  607. const dataPda4 = findDataPda(dataAccountKeypair4.publicKey);
  608. const initializeValue3 = new BN(50);
  609. const initializeValue4 = new BN(1000);
  610. before("Setup additional accounts", async () => {
  611. createRequiredIx3 = (await createRequired(requiredKeypair3))[1];
  612. createRequiredIx4 = (await createRequired(requiredKeypair4))[1];
  613. const assertInitSuccess = async (
  614. requiredPubkey: web3.PublicKey,
  615. dataPdaPubkey: web3.PublicKey,
  616. dataAccountPubkey: web3.PublicKey,
  617. initializeValue: BN
  618. ) => {
  619. const requiredDataAccount = await program.account.dataAccount.fetch(
  620. requiredPubkey
  621. );
  622. expect(requiredDataAccount.data.toNumber()).to.equal(0);
  623. const optionalDataAccount = await program.account.dataAccount.fetch(
  624. dataAccountPubkey
  625. );
  626. expect(optionalDataAccount.data.toNumber()).to.equal(
  627. initializeValue.toNumber()
  628. );
  629. const optionalDataPda = await program.account.dataPda.fetch(
  630. dataPdaPubkey
  631. );
  632. expect(optionalDataPda.dataAccount.toString()).to.equal(
  633. initializeKey.toString()
  634. );
  635. };
  636. await program.methods
  637. .initialize(initializeValue3, initializeKey)
  638. .preInstructions([createRequiredIx3])
  639. .accountsPartial({
  640. payer,
  641. systemProgram,
  642. required: requiredKeypair3.publicKey,
  643. optionalPda: dataPda3[0],
  644. optionalAccount: dataAccountKeypair3.publicKey,
  645. })
  646. .signers([requiredKeypair3, dataAccountKeypair3])
  647. .rpc();
  648. await assertInitSuccess(
  649. requiredKeypair3.publicKey,
  650. dataPda3[0],
  651. dataAccountKeypair3.publicKey,
  652. initializeValue3
  653. );
  654. await program.methods
  655. .initialize(initializeValue4, initializeKey)
  656. .preInstructions([createRequiredIx4])
  657. .accountsPartial({
  658. payer,
  659. systemProgram,
  660. required: requiredKeypair4.publicKey,
  661. optionalPda: dataPda4[0],
  662. optionalAccount: dataAccountKeypair4.publicKey,
  663. })
  664. .signers([requiredKeypair4, dataAccountKeypair4])
  665. .rpc();
  666. await assertInitSuccess(
  667. requiredKeypair4.publicKey,
  668. dataPda4[0],
  669. dataAccountKeypair4.publicKey,
  670. initializeValue4
  671. );
  672. await program.methods
  673. .update(initializeValue3, dataAccountKeypair3.publicKey, dataPda3[1])
  674. .accountsPartial({
  675. payer,
  676. optionalPda: dataPda3[0],
  677. optionalAccount: dataAccountKeypair3.publicKey,
  678. })
  679. .signers([dataAccountKeypair3])
  680. .rpc();
  681. const optionalPda3 = await program.account.dataPda.fetch(dataPda3[0]);
  682. expect(optionalPda3.dataAccount.toString()).to.equal(
  683. dataAccountKeypair3.publicKey.toString()
  684. );
  685. await program.methods
  686. .update(initializeValue4, dataAccountKeypair4.publicKey, dataPda4[1])
  687. .accountsPartial({
  688. payer,
  689. optionalPda: dataPda4[0],
  690. optionalAccount: dataAccountKeypair4.publicKey,
  691. })
  692. .signers([dataAccountKeypair4])
  693. .rpc();
  694. const optionalPda4 = await program.account.dataPda.fetch(dataPda4[0]);
  695. expect(optionalPda4.dataAccount.toString()).to.equal(
  696. dataAccountKeypair4.publicKey.toString()
  697. );
  698. });
  699. it("Close with no close target fails", async () => {
  700. try {
  701. await program.methods
  702. .close()
  703. .accountsPartial({
  704. payer: null,
  705. optionalPda: null,
  706. dataAccount: dataAccountKeypair3.publicKey,
  707. systemProgram,
  708. })
  709. .signers([dataAccountKeypair3])
  710. .rpc();
  711. assert.fail(
  712. "Unexpected success in creating a transaction that should have failed with `ConstraintRaw` error"
  713. );
  714. } catch (e) {
  715. // @ts-ignore
  716. assert.isTrue(e instanceof AnchorError, e.toString());
  717. const err: AnchorError = <AnchorError>e;
  718. const errorCode = LangErrorCode.ConstraintAccountIsNone;
  719. assert.strictEqual(
  720. err.error.errorMessage,
  721. LangErrorMessage.get(errorCode)
  722. );
  723. assert.strictEqual(err.error.errorCode.number, errorCode);
  724. }
  725. });
  726. it("Has one constraints are caught with optional accounts", async () => {
  727. try {
  728. await program.methods
  729. .close()
  730. .accountsPartial({
  731. payer,
  732. optionalPda: dataPda4[0],
  733. dataAccount: dataAccountKeypair3.publicKey,
  734. systemProgram,
  735. })
  736. .signers([dataAccountKeypair3])
  737. .rpc();
  738. assert.fail(
  739. "Unexpected success in creating a transaction that should have failed with `ConstraintHasOne` error"
  740. );
  741. } catch (e) {
  742. // @ts-ignore
  743. assert.isTrue(e instanceof AnchorError, e.toString());
  744. const err: AnchorError = <AnchorError>e;
  745. const errorCode = LangErrorCode.ConstraintHasOne;
  746. assert.strictEqual(
  747. err.error.errorMessage,
  748. LangErrorMessage.get(errorCode)
  749. );
  750. assert.strictEqual(err.error.errorCode.number, errorCode);
  751. }
  752. });
  753. it("Can close an optional account", async () => {
  754. await program.methods
  755. .close()
  756. .accountsPartial({
  757. payer,
  758. optionalPda: null,
  759. dataAccount: dataAccountKeypair3.publicKey,
  760. systemProgram,
  761. })
  762. .signers([dataAccountKeypair3])
  763. .rpc();
  764. const dataAccount = await program.provider.connection.getAccountInfo(
  765. dataAccountKeypair3.publicKey
  766. );
  767. assert.isNull(dataAccount);
  768. });
  769. it("Can close multiple optional accounts", async () => {
  770. await program.methods
  771. .close()
  772. .accountsPartial({
  773. payer,
  774. optionalPda: dataPda4[0],
  775. dataAccount: dataAccountKeypair4.publicKey,
  776. systemProgram,
  777. })
  778. .signers([dataAccountKeypair4])
  779. .rpc();
  780. const dataAccount = await program.provider.connection.getAccountInfo(
  781. dataAccountKeypair4.publicKey
  782. );
  783. assert.isNull(dataAccount);
  784. });
  785. });
  786. });