846 lines
28 KiB
TypeScript
846 lines
28 KiB
TypeScript
import * as anchor from "@coral-xyz/anchor";
|
|
import {
|
|
Program,
|
|
web3,
|
|
BN,
|
|
AnchorError,
|
|
LangErrorCode,
|
|
LangErrorMessage,
|
|
translateError,
|
|
parseIdlErrors,
|
|
} from "@coral-xyz/anchor";
|
|
import { Optional } from "../target/types/optional";
|
|
import { AllowMissingOptionals } from "../target/types/allow_missing_optionals";
|
|
import { assert, expect } from "chai";
|
|
|
|
describe("Optional", () => {
|
|
// configure the client to use the local cluster
|
|
anchor.setProvider(anchor.AnchorProvider.env());
|
|
const anchorProvider = anchor.AnchorProvider.env();
|
|
const program = anchor.workspace.Optional as Program<Optional>;
|
|
|
|
const DATA_PDA_PREFIX = "data_pda";
|
|
|
|
const makeDataPdaSeeds = (dataAccount: web3.PublicKey) => {
|
|
return [Buffer.from(DATA_PDA_PREFIX), dataAccount.toBuffer()];
|
|
};
|
|
|
|
const findDataPda = (
|
|
dataAccount: web3.PublicKey
|
|
): [web3.PublicKey, number] => {
|
|
return web3.PublicKey.findProgramAddressSync(
|
|
makeDataPdaSeeds(dataAccount),
|
|
program.programId
|
|
);
|
|
};
|
|
|
|
// payer of the transactions
|
|
const payerWallet = (program.provider as anchor.AnchorProvider).wallet;
|
|
const payer = payerWallet.publicKey;
|
|
const systemProgram = web3.SystemProgram.programId;
|
|
|
|
const requiredKeypair1 = web3.Keypair.generate();
|
|
const requiredKeypair2 = web3.Keypair.generate();
|
|
|
|
let createRequiredIx1: web3.TransactionInstruction;
|
|
let createRequiredIx2: web3.TransactionInstruction;
|
|
|
|
const dataAccountKeypair1 = web3.Keypair.generate();
|
|
const dataAccountKeypair2 = web3.Keypair.generate();
|
|
|
|
const dataPda1 = findDataPda(dataAccountKeypair1.publicKey);
|
|
const dataPda2 = findDataPda(dataAccountKeypair2.publicKey);
|
|
|
|
const initializeValue1 = new BN(10);
|
|
const initializeValue2 = new BN(100);
|
|
const initializeKey = web3.PublicKey.default;
|
|
|
|
const createRequired = async (
|
|
requiredKeypair?: web3.Keypair
|
|
): Promise<[web3.Keypair, web3.TransactionInstruction]> => {
|
|
const keypair = requiredKeypair ?? new web3.Keypair();
|
|
const createIx = await program.account.dataAccount.createInstruction(
|
|
keypair
|
|
);
|
|
return [keypair, createIx];
|
|
};
|
|
|
|
before("Setup async stuff", async () => {
|
|
createRequiredIx1 = (await createRequired(requiredKeypair1))[1];
|
|
createRequiredIx2 = (await createRequired(requiredKeypair2))[1];
|
|
});
|
|
|
|
describe("Missing optionals feature tests", async () => {
|
|
it("Fails with missing optional accounts at the end by default", async () => {
|
|
const [requiredKeypair, createRequiredIx] = await createRequired();
|
|
const initializeIx = await program.methods
|
|
.initialize(initializeValue1, initializeKey)
|
|
.accounts({
|
|
payer: null,
|
|
optionalAccount: null,
|
|
systemProgram,
|
|
required: requiredKeypair.publicKey,
|
|
optionalPda: null,
|
|
})
|
|
.signers([requiredKeypair])
|
|
.instruction();
|
|
initializeIx.keys.pop();
|
|
const initializeTxn = new web3.Transaction()
|
|
.add(createRequiredIx)
|
|
.add(initializeIx);
|
|
try {
|
|
await anchorProvider
|
|
.sendAndConfirm(initializeTxn, [requiredKeypair])
|
|
.catch((e) => {
|
|
throw translateError(e, parseIdlErrors(program.idl));
|
|
});
|
|
assert.fail(
|
|
"Unexpected success in creating a transaction that should have failed with `AccountNotEnoughKeys` error"
|
|
);
|
|
} catch (e) {
|
|
// @ts-ignore
|
|
assert.isTrue(e instanceof AnchorError, e.toString());
|
|
const err: AnchorError = <AnchorError>e;
|
|
const errorCode = LangErrorCode.AccountNotEnoughKeys;
|
|
assert.strictEqual(
|
|
err.error.errorMessage,
|
|
LangErrorMessage.get(errorCode)
|
|
);
|
|
assert.strictEqual(err.error.errorCode.number, errorCode);
|
|
}
|
|
});
|
|
|
|
it("Succeeds with missing optional accounts at the end with the feature on", async () => {
|
|
const allowMissingOptionals = anchor.workspace
|
|
.AllowMissingOptionals as Program<AllowMissingOptionals>;
|
|
const doStuffIx = await allowMissingOptionals.methods
|
|
.doStuff()
|
|
.accounts({
|
|
payer,
|
|
systemProgram,
|
|
optional2: null,
|
|
})
|
|
.instruction();
|
|
doStuffIx.keys.pop();
|
|
doStuffIx.keys.pop();
|
|
const doStuffTxn = new web3.Transaction().add(doStuffIx);
|
|
await anchorProvider.sendAndConfirm(doStuffTxn);
|
|
});
|
|
});
|
|
|
|
describe("Initialize tests", async () => {
|
|
it("Initialize with required null fails anchor-ts validation", async () => {
|
|
const [requiredKeypair, createRequiredIx] = await createRequired();
|
|
try {
|
|
await program.methods
|
|
.initialize(initializeValue1, initializeKey)
|
|
.preInstructions([createRequiredIx])
|
|
.accounts({
|
|
payer,
|
|
systemProgram,
|
|
// @ts-ignore
|
|
required: null, //requiredKeypair.publicKey,
|
|
optionalPda: null,
|
|
optionalAccount: null,
|
|
})
|
|
.signers([requiredKeypair])
|
|
.rpc();
|
|
assert.fail(
|
|
"Unexpected success in creating a transaction that should have failed at the client level"
|
|
);
|
|
} catch (e) {
|
|
const errMsg = "Invalid arguments: required not provided";
|
|
// @ts-ignore
|
|
let error: string = e.toString();
|
|
assert(error.includes(errMsg), `Unexpected error: ${e}`);
|
|
}
|
|
});
|
|
|
|
it("Can initialize with no payer and no optionals", async () => {
|
|
const [requiredKeypair, createRequiredIx] = await createRequired();
|
|
await program.methods
|
|
.initialize(initializeValue1, initializeKey)
|
|
.preInstructions([createRequiredIx])
|
|
.accounts({
|
|
payer: null,
|
|
systemProgram,
|
|
required: requiredKeypair.publicKey,
|
|
optionalPda: null,
|
|
optionalAccount: null,
|
|
})
|
|
.signers([requiredKeypair])
|
|
.rpc();
|
|
|
|
let required = await program.account.dataAccount.fetch(
|
|
requiredKeypair.publicKey
|
|
);
|
|
expect(required.data.toNumber()).to.equal(0);
|
|
});
|
|
|
|
it("Can initialize with no optionals", async () => {
|
|
const [requiredKeypair, createRequiredIx] = await createRequired();
|
|
await program.methods
|
|
.initialize(initializeValue1, initializeKey)
|
|
.preInstructions([createRequiredIx])
|
|
.accounts({
|
|
payer: null,
|
|
systemProgram: null,
|
|
required: requiredKeypair.publicKey,
|
|
optionalPda: null,
|
|
optionalAccount: null,
|
|
})
|
|
.signers([requiredKeypair])
|
|
.rpc();
|
|
|
|
let required = await program.account.dataAccount.fetch(
|
|
requiredKeypair.publicKey
|
|
);
|
|
expect(required.data.toNumber()).to.equal(0);
|
|
});
|
|
|
|
it("Initialize with optionals and missing system program fails optional checks", async () => {
|
|
const [requiredKeypair, createRequiredIx] = await createRequired();
|
|
const dataAccount = new web3.Keypair();
|
|
try {
|
|
await program.methods
|
|
.initialize(initializeValue1, initializeKey)
|
|
.preInstructions([createRequiredIx])
|
|
.accounts({
|
|
payer,
|
|
systemProgram: null,
|
|
required: requiredKeypair.publicKey,
|
|
optionalPda: null,
|
|
optionalAccount: dataAccount.publicKey,
|
|
})
|
|
.signers([requiredKeypair, dataAccount])
|
|
.rpc();
|
|
assert.fail(
|
|
"Unexpected success in creating a transaction that should have failed with `ConstraintAccountIsNone` error"
|
|
);
|
|
} catch (e) {
|
|
// @ts-ignore
|
|
assert.isTrue(e instanceof AnchorError, e.toString());
|
|
const err: AnchorError = <AnchorError>e;
|
|
const errorCode = LangErrorCode.ConstraintAccountIsNone;
|
|
assert.strictEqual(
|
|
err.error.errorMessage,
|
|
LangErrorMessage.get(errorCode)
|
|
);
|
|
assert.strictEqual(err.error.errorCode.number, errorCode);
|
|
}
|
|
});
|
|
|
|
it("Unwrapping None account in constraint panics", async () => {
|
|
const [requiredKeypair, createRequiredIx] = await createRequired();
|
|
const dataAccount = new web3.Keypair();
|
|
const [dataPda] = findDataPda(dataAccount.publicKey);
|
|
try {
|
|
await program.methods
|
|
.initialize(initializeValue1, initializeKey)
|
|
.preInstructions([createRequiredIx])
|
|
.accounts({
|
|
payer,
|
|
systemProgram,
|
|
required: requiredKeypair.publicKey,
|
|
optionalPda: dataPda,
|
|
optionalAccount: null,
|
|
})
|
|
.signers([requiredKeypair])
|
|
.rpc();
|
|
assert.fail(
|
|
"Unexpected success in creating a transaction that should have failed with `ProgramFailedToComplete` error"
|
|
);
|
|
} catch (e) {
|
|
const errMsg = "Program failed to complete";
|
|
// @ts-ignore
|
|
let error: string = e.toString();
|
|
assert(error.includes(errMsg), `Unexpected error: ${e}`);
|
|
}
|
|
});
|
|
|
|
it("Can initialize with required and optional account", async () => {
|
|
await program.methods
|
|
.initialize(initializeValue1, initializeKey)
|
|
.preInstructions([createRequiredIx1])
|
|
.accounts({
|
|
payer,
|
|
systemProgram,
|
|
required: requiredKeypair1.publicKey,
|
|
optionalPda: null,
|
|
optionalAccount: dataAccountKeypair1.publicKey,
|
|
})
|
|
.signers([requiredKeypair1, dataAccountKeypair1])
|
|
.rpc();
|
|
|
|
const requiredDataAccount = await program.account.dataAccount.fetch(
|
|
requiredKeypair1.publicKey
|
|
);
|
|
expect(requiredDataAccount.data.toNumber()).to.equal(0);
|
|
|
|
const optionalDataAccount = await program.account.dataAccount.fetch(
|
|
dataAccountKeypair1.publicKey
|
|
);
|
|
expect(optionalDataAccount.data.toNumber()).to.equal(
|
|
initializeValue1.muln(2).toNumber()
|
|
);
|
|
});
|
|
|
|
it("Invalid seeds with all accounts provided fails", async () => {
|
|
try {
|
|
await program.methods
|
|
.initialize(initializeValue2, initializeKey)
|
|
.preInstructions([createRequiredIx2])
|
|
.accounts({
|
|
payer,
|
|
systemProgram,
|
|
required: requiredKeypair2.publicKey,
|
|
optionalPda: dataPda1[0],
|
|
optionalAccount: dataAccountKeypair2.publicKey,
|
|
})
|
|
.signers([requiredKeypair2, dataAccountKeypair2])
|
|
.rpc();
|
|
assert.fail(
|
|
"Unexpected success in creating a transaction that should have failed with `ConstraintSeeds` error"
|
|
);
|
|
} catch (e) {
|
|
// @ts-ignore
|
|
assert.isTrue(e instanceof AnchorError, e.toString());
|
|
const err: AnchorError = <AnchorError>e;
|
|
const errorCode = LangErrorCode.ConstraintSeeds;
|
|
assert.strictEqual(
|
|
err.error.errorMessage,
|
|
LangErrorMessage.get(errorCode)
|
|
);
|
|
assert.strictEqual(err.error.errorCode.number, errorCode);
|
|
}
|
|
});
|
|
|
|
it("Can initialize with all accounts provided", async () => {
|
|
await program.methods
|
|
.initialize(initializeValue2, initializeKey)
|
|
.preInstructions([createRequiredIx2])
|
|
.accounts({
|
|
payer,
|
|
systemProgram,
|
|
required: requiredKeypair2.publicKey,
|
|
optionalPda: dataPda2[0],
|
|
optionalAccount: dataAccountKeypair2.publicKey,
|
|
})
|
|
.signers([requiredKeypair2, dataAccountKeypair2])
|
|
.rpc();
|
|
|
|
const requiredDataAccount = await program.account.dataAccount.fetch(
|
|
requiredKeypair2.publicKey
|
|
);
|
|
expect(requiredDataAccount.data.toNumber()).to.equal(0);
|
|
|
|
const optionalDataAccount = await program.account.dataAccount.fetch(
|
|
dataAccountKeypair2.publicKey
|
|
);
|
|
expect(optionalDataAccount.data.toNumber()).to.equal(
|
|
initializeValue2.toNumber()
|
|
);
|
|
|
|
const optionalDataPda = await program.account.dataPda.fetch(dataPda2[0]);
|
|
expect(optionalDataPda.dataAccount.toString()).to.equal(
|
|
initializeKey.toString()
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("Update tests", async () => {
|
|
it("Can update with invalid explicit pda bump with no pda", async () => {
|
|
await program.methods
|
|
.update(initializeValue2, initializeKey, dataPda2[1] - 1)
|
|
.accounts({
|
|
payer,
|
|
optionalPda: null,
|
|
optionalAccount: null,
|
|
})
|
|
.rpc();
|
|
});
|
|
|
|
it("Errors with invalid explicit pda bump with pda included", async () => {
|
|
try {
|
|
await program.methods
|
|
.update(initializeValue2, initializeKey, dataPda2[1] - 1)
|
|
.accounts({
|
|
payer,
|
|
optionalPda: dataPda2[0],
|
|
optionalAccount: dataAccountKeypair2.publicKey,
|
|
})
|
|
.signers([dataAccountKeypair2])
|
|
.rpc();
|
|
assert.fail(
|
|
"Unexpected success in creating a transaction that should have failed with `ConstraintSeeds` error"
|
|
);
|
|
} catch (e) {
|
|
// @ts-ignore
|
|
assert.isTrue(e instanceof AnchorError, e.toString());
|
|
const err: AnchorError = <AnchorError>e;
|
|
const errorCode = LangErrorCode.ConstraintSeeds;
|
|
assert.strictEqual(
|
|
err.error.errorMessage,
|
|
LangErrorMessage.get(errorCode)
|
|
);
|
|
assert.strictEqual(err.error.errorCode.number, errorCode);
|
|
}
|
|
});
|
|
|
|
it("Fails with a missing signer", async () => {
|
|
try {
|
|
let txn = await program.methods
|
|
.update(initializeValue2, initializeKey, dataPda2[1])
|
|
.accounts({
|
|
payer,
|
|
optionalPda: dataPda2[0],
|
|
optionalAccount: dataAccountKeypair2.publicKey,
|
|
})
|
|
.transaction();
|
|
txn.instructions[0].keys.forEach((meta) => {
|
|
if (meta.pubkey.equals(dataAccountKeypair2.publicKey)) {
|
|
meta.isSigner = false;
|
|
}
|
|
});
|
|
await anchorProvider.sendAndConfirm(txn);
|
|
assert.fail(
|
|
"Unexpected success in creating a transaction that should have failed with `ConstraintSigner` error"
|
|
);
|
|
} catch (e) {
|
|
// @ts-ignore
|
|
assert.isTrue(e instanceof web3.SendTransactionError, e.toString());
|
|
const err: web3.SendTransactionError = <web3.SendTransactionError>e;
|
|
const anchorError = AnchorError.parse(err.logs!)!;
|
|
const errorCode = LangErrorCode.ConstraintSigner;
|
|
assert.strictEqual(
|
|
anchorError.error.errorMessage,
|
|
LangErrorMessage.get(errorCode)
|
|
);
|
|
assert.strictEqual(anchorError.error.errorCode.number, errorCode);
|
|
}
|
|
});
|
|
|
|
it("Can trigger raw constraint violations with references to optional accounts", async () => {
|
|
try {
|
|
await program.methods
|
|
.update(initializeValue2, initializeKey, dataPda2[1])
|
|
.accounts({
|
|
payer: null,
|
|
optionalPda: dataPda2[0],
|
|
optionalAccount: dataAccountKeypair2.publicKey,
|
|
})
|
|
.signers([dataAccountKeypair2])
|
|
.rpc();
|
|
assert.fail(
|
|
"Unexpected success in creating a transaction that should have failed with `ConstraintRaw` error"
|
|
);
|
|
} catch (e) {
|
|
// @ts-ignore
|
|
assert.isTrue(e instanceof AnchorError, e.toString());
|
|
const err: AnchorError = <AnchorError>e;
|
|
const errorCode = LangErrorCode.ConstraintRaw;
|
|
assert.strictEqual(
|
|
err.error.errorMessage,
|
|
LangErrorMessage.get(errorCode)
|
|
);
|
|
assert.strictEqual(err.error.errorCode.number, errorCode);
|
|
}
|
|
});
|
|
|
|
it("Can update an optional account", async () => {
|
|
await program.methods
|
|
.update(initializeValue2.muln(3), initializeKey, dataPda2[1])
|
|
.accounts({
|
|
payer,
|
|
optionalPda: null,
|
|
optionalAccount: dataAccountKeypair2.publicKey,
|
|
})
|
|
.signers([dataAccountKeypair2])
|
|
.rpc();
|
|
|
|
const dataAccount = await program.account.dataAccount.fetch(
|
|
dataAccountKeypair2.publicKey
|
|
);
|
|
expect(dataAccount.data.toNumber()).to.equal(
|
|
initializeValue2.muln(3).toNumber()
|
|
);
|
|
});
|
|
|
|
it("Can update both accounts", async () => {
|
|
const newKey = web3.PublicKey.unique();
|
|
await program.methods
|
|
.update(initializeValue2, newKey, dataPda2[1])
|
|
.accounts({
|
|
payer,
|
|
optionalPda: dataPda2[0],
|
|
optionalAccount: dataAccountKeypair2.publicKey,
|
|
})
|
|
.signers([dataAccountKeypair2])
|
|
.rpc();
|
|
|
|
const dataPda = await program.account.dataPda.fetch(dataPda2[0]);
|
|
expect(dataPda.dataAccount.toString()).to.equal(newKey.toString());
|
|
|
|
const dataAccount = await program.account.dataAccount.fetch(
|
|
dataAccountKeypair2.publicKey
|
|
);
|
|
expect(dataAccount.data.toNumber()).to.equal(initializeValue2.toNumber());
|
|
});
|
|
});
|
|
|
|
describe("Realloc tests", async () => {
|
|
it("Realloc with no payer fails", async () => {
|
|
try {
|
|
await program.methods
|
|
.realloc(new BN(100))
|
|
.accounts({
|
|
payer: null,
|
|
required: dataAccountKeypair1.publicKey,
|
|
optionalPda: null,
|
|
optionalAccount: dataAccountKeypair2.publicKey,
|
|
systemProgram,
|
|
})
|
|
.signers([dataAccountKeypair2])
|
|
.rpc();
|
|
assert.fail(
|
|
"Unexpected success in creating a transaction that should have failed with `ConstraintAccountIsNone` error"
|
|
);
|
|
} catch (e) {
|
|
// @ts-ignore
|
|
assert.isTrue(e instanceof AnchorError, e.toString());
|
|
const err: AnchorError = <AnchorError>e;
|
|
const errorCode = LangErrorCode.ConstraintAccountIsNone;
|
|
assert.strictEqual(
|
|
err.error.errorMessage,
|
|
LangErrorMessage.get(errorCode)
|
|
);
|
|
assert.strictEqual(err.error.errorCode.number, errorCode);
|
|
}
|
|
});
|
|
|
|
it("Realloc with no system program fails", async () => {
|
|
try {
|
|
await program.methods
|
|
.realloc(new BN(100))
|
|
.accounts({
|
|
payer,
|
|
required: dataAccountKeypair1.publicKey,
|
|
optionalPda: null,
|
|
optionalAccount: dataAccountKeypair2.publicKey,
|
|
systemProgram: null,
|
|
})
|
|
.signers([dataAccountKeypair2])
|
|
.rpc();
|
|
assert.fail(
|
|
"Unexpected success in creating a transaction that should have failed with `ConstraintAccountIsNone` error"
|
|
);
|
|
} catch (e) {
|
|
// @ts-ignore
|
|
assert.isTrue(e instanceof AnchorError, e.toString());
|
|
const err: AnchorError = <AnchorError>e;
|
|
const errorCode = LangErrorCode.ConstraintAccountIsNone;
|
|
assert.strictEqual(
|
|
err.error.errorMessage,
|
|
LangErrorMessage.get(errorCode)
|
|
);
|
|
assert.strictEqual(err.error.errorCode.number, errorCode);
|
|
}
|
|
});
|
|
|
|
it("Wrong type of account is caught for optional accounts", async () => {
|
|
try {
|
|
await program.methods
|
|
.realloc(new BN(100))
|
|
.accounts({
|
|
payer,
|
|
required: dataAccountKeypair1.publicKey,
|
|
optionalPda: dataAccountKeypair2.publicKey,
|
|
optionalAccount: null,
|
|
systemProgram,
|
|
})
|
|
.rpc();
|
|
assert.fail(
|
|
"Unexpected success in creating a transaction that should have failed with `AccountDiscriminatorMismatch` error"
|
|
);
|
|
} catch (e) {
|
|
// @ts-ignore
|
|
assert.isTrue(e instanceof AnchorError, e.toString());
|
|
const err: AnchorError = <AnchorError>e;
|
|
const errorCode = LangErrorCode.AccountDiscriminatorMismatch;
|
|
assert.strictEqual(
|
|
err.error.errorMessage,
|
|
LangErrorMessage.get(errorCode)
|
|
);
|
|
assert.strictEqual(err.error.errorCode.number, errorCode);
|
|
}
|
|
});
|
|
|
|
it("Can realloc with optional accounts", async () => {
|
|
const newLength = 100;
|
|
await program.methods
|
|
.realloc(new BN(newLength))
|
|
.accounts({
|
|
payer,
|
|
required: dataAccountKeypair1.publicKey,
|
|
optionalPda: null,
|
|
optionalAccount: dataAccountKeypair2.publicKey,
|
|
systemProgram,
|
|
})
|
|
.signers([dataAccountKeypair2])
|
|
.rpc();
|
|
const dataAccount = await program.provider.connection.getAccountInfo(
|
|
dataAccountKeypair2.publicKey
|
|
);
|
|
assert.exists(dataAccount);
|
|
expect(dataAccount!.data.length).to.equal(newLength);
|
|
});
|
|
|
|
it("Can realloc back to original size with optional accounts", async () => {
|
|
const newLength = program.account.dataAccount.size;
|
|
await program.methods
|
|
.realloc(new BN(newLength))
|
|
.accounts({
|
|
payer,
|
|
required: dataAccountKeypair1.publicKey,
|
|
optionalPda: null,
|
|
optionalAccount: dataAccountKeypair2.publicKey,
|
|
systemProgram,
|
|
})
|
|
.signers([dataAccountKeypair2])
|
|
.rpc();
|
|
const dataAccount = await program.provider.connection.getAccountInfo(
|
|
dataAccountKeypair2.publicKey
|
|
);
|
|
assert.exists(dataAccount);
|
|
expect(dataAccount!.data.length).to.equal(newLength);
|
|
});
|
|
|
|
it("Can realloc multiple optional accounts", async () => {
|
|
const newLength = 100;
|
|
await program.methods
|
|
.realloc(new BN(newLength))
|
|
.accounts({
|
|
payer,
|
|
required: dataAccountKeypair1.publicKey,
|
|
optionalPda: dataPda2[0],
|
|
optionalAccount: dataAccountKeypair2.publicKey,
|
|
systemProgram,
|
|
})
|
|
.signers([dataAccountKeypair2])
|
|
.rpc();
|
|
const dataAccount = await program.provider.connection.getAccountInfo(
|
|
dataAccountKeypair2.publicKey
|
|
);
|
|
assert.exists(dataAccount);
|
|
expect(dataAccount!.data.length).to.equal(newLength);
|
|
|
|
const dataPda = await program.provider.connection.getAccountInfo(
|
|
dataPda2[0]
|
|
);
|
|
assert.exists(dataPda);
|
|
expect(dataPda!.data.length).to.equal(newLength);
|
|
});
|
|
});
|
|
|
|
describe("Close tests", async () => {
|
|
const requiredKeypair3 = web3.Keypair.generate();
|
|
const requiredKeypair4 = web3.Keypair.generate();
|
|
|
|
let createRequiredIx3: web3.TransactionInstruction;
|
|
let createRequiredIx4: web3.TransactionInstruction;
|
|
|
|
const dataAccountKeypair3 = web3.Keypair.generate();
|
|
const dataAccountKeypair4 = web3.Keypair.generate();
|
|
|
|
const dataPda3 = findDataPda(dataAccountKeypair3.publicKey);
|
|
const dataPda4 = findDataPda(dataAccountKeypair4.publicKey);
|
|
|
|
const initializeValue3 = new BN(50);
|
|
const initializeValue4 = new BN(1000);
|
|
|
|
before("Setup additional accounts", async () => {
|
|
createRequiredIx3 = (await createRequired(requiredKeypair3))[1];
|
|
createRequiredIx4 = (await createRequired(requiredKeypair4))[1];
|
|
const assertInitSuccess = async (
|
|
requiredPubkey: web3.PublicKey,
|
|
dataPdaPubkey: web3.PublicKey,
|
|
dataAccountPubkey: web3.PublicKey,
|
|
initializeValue: BN
|
|
) => {
|
|
const requiredDataAccount = await program.account.dataAccount.fetch(
|
|
requiredPubkey
|
|
);
|
|
expect(requiredDataAccount.data.toNumber()).to.equal(0);
|
|
|
|
const optionalDataAccount = await program.account.dataAccount.fetch(
|
|
dataAccountPubkey
|
|
);
|
|
expect(optionalDataAccount.data.toNumber()).to.equal(
|
|
initializeValue.toNumber()
|
|
);
|
|
|
|
const optionalDataPda = await program.account.dataPda.fetch(
|
|
dataPdaPubkey
|
|
);
|
|
expect(optionalDataPda.dataAccount.toString()).to.equal(
|
|
initializeKey.toString()
|
|
);
|
|
};
|
|
|
|
await program.methods
|
|
.initialize(initializeValue3, initializeKey)
|
|
.preInstructions([createRequiredIx3])
|
|
.accounts({
|
|
payer,
|
|
systemProgram,
|
|
required: requiredKeypair3.publicKey,
|
|
optionalPda: dataPda3[0],
|
|
optionalAccount: dataAccountKeypair3.publicKey,
|
|
})
|
|
.signers([requiredKeypair3, dataAccountKeypair3])
|
|
.rpc();
|
|
await assertInitSuccess(
|
|
requiredKeypair3.publicKey,
|
|
dataPda3[0],
|
|
dataAccountKeypair3.publicKey,
|
|
initializeValue3
|
|
);
|
|
await program.methods
|
|
.initialize(initializeValue4, initializeKey)
|
|
.preInstructions([createRequiredIx4])
|
|
.accounts({
|
|
payer,
|
|
systemProgram,
|
|
required: requiredKeypair4.publicKey,
|
|
optionalPda: dataPda4[0],
|
|
optionalAccount: dataAccountKeypair4.publicKey,
|
|
})
|
|
.signers([requiredKeypair4, dataAccountKeypair4])
|
|
.rpc();
|
|
await assertInitSuccess(
|
|
requiredKeypair4.publicKey,
|
|
dataPda4[0],
|
|
dataAccountKeypair4.publicKey,
|
|
initializeValue4
|
|
);
|
|
|
|
await program.methods
|
|
.update(initializeValue3, dataAccountKeypair3.publicKey, dataPda3[1])
|
|
.accounts({
|
|
payer,
|
|
optionalPda: dataPda3[0],
|
|
optionalAccount: dataAccountKeypair3.publicKey,
|
|
})
|
|
.signers([dataAccountKeypair3])
|
|
.rpc();
|
|
const optionalPda3 = await program.account.dataPda.fetch(dataPda3[0]);
|
|
expect(optionalPda3.dataAccount.toString()).to.equal(
|
|
dataAccountKeypair3.publicKey.toString()
|
|
);
|
|
await program.methods
|
|
.update(initializeValue4, dataAccountKeypair4.publicKey, dataPda4[1])
|
|
.accounts({
|
|
payer,
|
|
optionalPda: dataPda4[0],
|
|
optionalAccount: dataAccountKeypair4.publicKey,
|
|
})
|
|
.signers([dataAccountKeypair4])
|
|
.rpc();
|
|
const optionalPda4 = await program.account.dataPda.fetch(dataPda4[0]);
|
|
expect(optionalPda4.dataAccount.toString()).to.equal(
|
|
dataAccountKeypair4.publicKey.toString()
|
|
);
|
|
});
|
|
|
|
it("Close with no close target fails", async () => {
|
|
try {
|
|
await program.methods
|
|
.close()
|
|
.accounts({
|
|
payer: null,
|
|
optionalPda: null,
|
|
dataAccount: dataAccountKeypair3.publicKey,
|
|
systemProgram,
|
|
})
|
|
.signers([dataAccountKeypair3])
|
|
.rpc();
|
|
assert.fail(
|
|
"Unexpected success in creating a transaction that should have failed with `ConstraintRaw` error"
|
|
);
|
|
} catch (e) {
|
|
// @ts-ignore
|
|
assert.isTrue(e instanceof AnchorError, e.toString());
|
|
const err: AnchorError = <AnchorError>e;
|
|
const errorCode = LangErrorCode.ConstraintAccountIsNone;
|
|
assert.strictEqual(
|
|
err.error.errorMessage,
|
|
LangErrorMessage.get(errorCode)
|
|
);
|
|
assert.strictEqual(err.error.errorCode.number, errorCode);
|
|
}
|
|
});
|
|
|
|
it("Has one constraints are caught with optional accounts", async () => {
|
|
try {
|
|
await program.methods
|
|
.close()
|
|
.accounts({
|
|
payer,
|
|
optionalPda: dataPda4[0],
|
|
dataAccount: dataAccountKeypair3.publicKey,
|
|
systemProgram,
|
|
})
|
|
.signers([dataAccountKeypair3])
|
|
.rpc();
|
|
assert.fail(
|
|
"Unexpected success in creating a transaction that should have failed with `ConstraintHasOne` error"
|
|
);
|
|
} catch (e) {
|
|
// @ts-ignore
|
|
assert.isTrue(e instanceof AnchorError, e.toString());
|
|
const err: AnchorError = <AnchorError>e;
|
|
const errorCode = LangErrorCode.ConstraintHasOne;
|
|
assert.strictEqual(
|
|
err.error.errorMessage,
|
|
LangErrorMessage.get(errorCode)
|
|
);
|
|
assert.strictEqual(err.error.errorCode.number, errorCode);
|
|
}
|
|
});
|
|
|
|
it("Can close an optional account", async () => {
|
|
await program.methods
|
|
.close()
|
|
.accounts({
|
|
payer,
|
|
optionalPda: null,
|
|
dataAccount: dataAccountKeypair3.publicKey,
|
|
systemProgram,
|
|
})
|
|
.signers([dataAccountKeypair3])
|
|
.rpc();
|
|
const dataAccount = await program.provider.connection.getAccountInfo(
|
|
dataAccountKeypair3.publicKey
|
|
);
|
|
assert.isNull(dataAccount);
|
|
});
|
|
|
|
it("Can close multiple optional accounts", async () => {
|
|
await program.methods
|
|
.close()
|
|
.accounts({
|
|
payer,
|
|
optionalPda: dataPda4[0],
|
|
dataAccount: dataAccountKeypair4.publicKey,
|
|
systemProgram,
|
|
})
|
|
.signers([dataAccountKeypair4])
|
|
.rpc();
|
|
const dataAccount = await program.provider.connection.getAccountInfo(
|
|
dataAccountKeypair4.publicKey
|
|
);
|
|
assert.isNull(dataAccount);
|
|
});
|
|
});
|
|
});
|