solana/web3.js/test/bpf-loader.test.ts

268 lines
7.9 KiB
TypeScript

import fs from 'mz/fs';
import {expect, use} from 'chai';
import chaiAsPromised from 'chai-as-promised';
import {fail} from 'assert';
import {
Connection,
BpfLoader,
Transaction,
sendAndConfirmTransaction,
Keypair,
} from '../src';
import {url} from './url';
import {BPF_LOADER_PROGRAM_ID} from '../src/bpf-loader';
import {helpers} from './mocks/rpc-http';
use(chaiAsPromised);
if (process.env.TEST_LIVE) {
describe('BPF Loader', () => {
describe('load BPF program', () => {
const connection = new Connection(url, 'confirmed');
let program = Keypair.generate();
let payerAccount = Keypair.generate();
let programData: Buffer;
before(async function () {
this.timeout(60_000);
programData = await fs.readFile(
'test/fixtures/noop-program/solana_sbf_rust_noop.so',
);
const {feeCalculator} = await connection.getRecentBlockhash();
const fees =
feeCalculator.lamportsPerSignature *
BpfLoader.getMinNumSignatures(programData.length);
const payerBalance = await connection.getMinimumBalanceForRentExemption(
0,
);
const executableBalance =
await connection.getMinimumBalanceForRentExemption(
programData.length,
);
await helpers.airdrop({
connection,
address: payerAccount.publicKey,
amount: payerBalance + fees + executableBalance,
});
// Create program account with low balance
await helpers.airdrop({
connection,
address: program.publicKey,
amount: executableBalance - 1,
});
// First load will fail part way due to lack of funds
const insufficientPayerAccount = Keypair.generate();
await helpers.airdrop({
connection,
address: insufficientPayerAccount.publicKey,
amount: 2 * feeCalculator.lamportsPerSignature * 8,
});
const failedLoad = BpfLoader.load(
connection,
insufficientPayerAccount,
program,
programData,
BPF_LOADER_PROGRAM_ID,
);
await expect(failedLoad).to.be.rejected;
// Second load will succeed
await BpfLoader.load(
connection,
payerAccount,
program,
programData,
BPF_LOADER_PROGRAM_ID,
);
});
it('get confirmed transaction', async () => {
const transaction = new Transaction().add({
keys: [
{pubkey: payerAccount.publicKey, isSigner: true, isWritable: true},
],
programId: program.publicKey,
});
const signature = await sendAndConfirmTransaction(
connection,
transaction,
[payerAccount],
{
commitment: 'finalized', // `getParsedConfirmedTransaction` requires max commitment
preflightCommitment: connection.commitment || 'finalized',
},
);
const parsedTx = await connection.getParsedConfirmedTransaction(
signature,
);
if (parsedTx === null) {
expect(parsedTx).not.to.be.null;
return;
}
const {signatures, message} = parsedTx.transaction;
expect(signatures[0]).to.eq(signature);
const ix = message.instructions[0];
if ('parsed' in ix) {
expect('parsed' in ix).to.eq(false);
} else {
expect(ix.programId).to.eql(program.publicKey);
expect(ix.data).to.eq('');
}
}).timeout(30000);
it('simulate transaction', async () => {
const simulatedTransaction = new Transaction().add({
keys: [
{pubkey: payerAccount.publicKey, isSigner: true, isWritable: true},
],
programId: program.publicKey,
});
const {err, logs} = (
await connection.simulateTransaction(simulatedTransaction, [
payerAccount,
])
).value;
expect(err).to.be.null;
if (logs === null) {
expect(logs).not.to.be.null;
return;
}
expect(logs.length).to.be.at.least(2);
expect(logs[0]).to.eq(
`Program ${program.publicKey.toBase58()} invoke [1]`,
);
expect(logs[logs.length - 1]).to.eq(
`Program ${program.publicKey.toBase58()} success`,
);
});
it('simulate transaction with returnData', async () => {
const simulatedTransaction = new Transaction().add({
keys: [
{pubkey: payerAccount.publicKey, isSigner: true, isWritable: true},
],
programId: program.publicKey,
});
const {err, returnData} = (
await connection.simulateTransaction(simulatedTransaction, [
payerAccount,
])
).value;
const expectedReturnData = new Uint8Array([1, 2, 3]);
if (returnData) {
var decodedData = Buffer.from(returnData.data[0], returnData.data[1]);
expect(err).to.be.null;
expect(returnData.programId).to.eql(program.publicKey.toString());
expect(decodedData).to.eql(expectedReturnData);
} else {
fail('return data must be defined!');
}
});
it('deprecated - simulate transaction without signature verification', async () => {
const simulatedTransaction = new Transaction().add({
keys: [
{pubkey: payerAccount.publicKey, isSigner: true, isWritable: true},
],
programId: program.publicKey,
});
simulatedTransaction.setSigners(payerAccount.publicKey);
const {err, logs} = (
await connection.simulateTransaction(simulatedTransaction)
).value;
expect(err).to.be.null;
if (logs === null) {
expect(logs).not.to.be.null;
return;
}
expect(logs.length).to.be.at.least(2);
expect(logs[0]).to.eq(
`Program ${program.publicKey.toBase58()} invoke [1]`,
);
expect(logs[logs.length - 1]).to.eq(
`Program ${program.publicKey.toBase58()} success`,
);
});
it('simulate transaction without signature verification', async () => {
const simulatedTransaction = new Transaction();
simulatedTransaction.feePayer = payerAccount.publicKey;
simulatedTransaction.add({
keys: [
{pubkey: payerAccount.publicKey, isSigner: true, isWritable: true},
],
programId: program.publicKey,
});
const {err, logs} = (
await connection.simulateTransaction(simulatedTransaction)
).value;
expect(err).to.be.null;
if (logs === null) {
expect(logs).not.to.be.null;
return;
}
expect(logs.length).to.be.at.least(2);
expect(logs[0]).to.eq(
`Program ${program.publicKey.toBase58()} invoke [1]`,
);
expect(logs[logs.length - 1]).to.eq(
`Program ${program.publicKey.toBase58()} success`,
);
});
it('simulate transaction with bad programId', async () => {
const simulatedTransaction = new Transaction().add({
keys: [
{pubkey: payerAccount.publicKey, isSigner: true, isWritable: true},
],
programId: Keypair.generate().publicKey,
});
simulatedTransaction.setSigners(payerAccount.publicKey);
const {err, logs} = (
await connection.simulateTransaction(simulatedTransaction)
).value;
expect(err).to.eq('ProgramAccountNotFound');
if (logs === null) {
expect(logs).not.to.be.null;
return;
}
expect(logs).to.have.length(0);
});
it('reload program', async () => {
expect(
await BpfLoader.load(
connection,
payerAccount,
program,
programData,
BPF_LOADER_PROGRAM_ID,
),
).to.eq(false);
});
});
});
}