I am working with a solana program which was written a couple years ago and it is non-anchor. I am specifically having trouble with calling the program functions from the test-side, the data field with Buffer Layout is causing me problems. I will show one function here from the program and how I am trying to interact with it, along with the error:
Rust src/instruction.rs:
/* Inits a new lock schedule.
* A lock schedule consists of a LockScheduleHeader and array of LockSchedule s.
* The header consists of destination address, token mint address and initialized flag.
* LockTokenInstruction::Init instruction creates a program account from the seeds array which has data size to fit the number of schedule data.
*
* - Accounts
* 0. `[]` The system program account
* 1. `[]` Program state account
* 2. `[]` The sysvar Rent account
* 3. `[signer]` The fee payer account
* 4. `[]` The locking account
*/
Init {
seeds: [u8; 32],
number_of_schedules: u32,
},
This would be instruction index 0. That function calls this in src/processor.rs:
pub fn process_init(
program_id: &Pubkey,
accounts: &[AccountInfo],
seeds: [u8; 32],
schedules: u32
) -> ProgramResult {
let accounts_iter = &mut accounts.iter();
let system_program_account = next_account_info(accounts_iter)?;
let program_state_account = next_account_info(accounts_iter)?;
let rent_sysvar_account = next_account_info(accounts_iter)?;
let payer = next_account_info(accounts_iter)?;
let locking_account = next_account_info(accounts_iter)?;
let rent = Rent::from_account_info(rent_sysvar_account)?;
let program_state_account_key = Pubkey::create_program_address(&[String::from(OWNER_TOKEN_MINT_ADDRESS).as_bytes()], program_id)?;
if program_state_account_key != *program_state_account.key {
msg!("Provided program state account is invalid");
return Err(ProgramError::InvalidArgument);
}
let is_state_initialized = program_state_account.try_borrow_data()?[LockGlobalState::LEN - 1] == 1;
if !is_state_initialized {
msg!("The state of program is uninitialized");
return Err(ProgramError::InvalidArgument);
}
let program_global_state = LockGlobalState::unpack(&program_state_account.data.borrow())?;
if program_global_state.is_paused {
msg!("The program is paused");
return Err(ProgramError::InvalidArgument);
}
let locking_account_key = Pubkey::create_program_address(&[&seeds], &program_id).unwrap();
if locking_account_key != *locking_account.key {
msg!("Provided locking account is invalid");
return Err(ProgramError::InvalidArgument);
}
let state_size = (schedules as usize) * LockSchedule::LEN + LockScheduleHeader::LEN;
let init_locking_account = create_account(
&payer.key,
&locking_account_key,
rent.minimum_balance(state_size),
state_size as u64,
&program_id,
);
invoke_signed(
&init_locking_account,
&[
system_program_account.clone(),
payer.clone(),
locking_account.clone(),
],
&[&[&seeds]],
)?;
Ok(())
}
So it has two arguments, seeds: [u8; 32] and size: u32. Here is the javascript I am using to test this:
it('can initialize', async () => {
let payer = Keypair.generate();
let airdropSignature = await connection.requestAirdrop(
payer.publicKey,
LAMPORTS_PER_SOL,
);
let airdropTx = await connection.confirmTransaction(airdropSignature);
console.log("airdrop: " + airdropTx);
/* Inits a new lock schedule.
* A lock schedule consists of a LockScheduleHeader and array of LockSchedule s.
* The header consists of destination address, token mint address and initialized flag.
* LockTokenInstruction::Init instruction creates a program account from the seeds array which has data size to fit the number of schedule data.
*
* - Accounts
* 0. `[]` The system program account
* 1. `[]` Program state account
* 2. `[]` The sysvar Rent account
* 3. `[signer]` The fee payer account
* 4. `[]` The locking account
*/
let programState = Keypair.generate();
let lockingAccnt = Keypair.generate();
let seeds = new BN(32).toArray("le", 32);
const dataLayout = BufferLayout.struct([
BufferLayout.u8('instruction'),
BufferLayout.u32('seeds'), // I don't think this is right; should be [u8; 32] but can't find that
BufferLayout.u32('number_of_schedules')
]);
const data = Buffer.alloc(dataLayout.span);
dataLayout.encode(
{
instruction: 0, // 0 for create instruction,
seeds: seeds,
number_of_schedules: new BN(3),
},
data,
);
// Allocating space and rent for source token account
const createPState = SystemProgram.createAccount({
fromPubkey: payer.publicKey,
newAccountPubkey: programState.publicKey,
lamports: LAMPORTS_PER_SOL,
programId: lockProgram,
space: 64,
});
const createLAcc = SystemProgram.createAccount({
fromPubkey: payer.publicKey,
newAccountPubkey: lockingAccnt.publicKey,
lamports: LAMPORTS_PER_SOL,
programId: lockProgram,
space: 64,
});
const initTx = new TransactionInstruction({
programId: lockProgram,
keys: [
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
{ pubkey: programState.publicKey, isSigner: false, isWritable: true },
{ pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false},
{ pubkey: payer.publicKey, isSigner: true, isWritable: true },
{ pubkey: lockingAccnt.publicKey, isSigner: false, isWritable: true },
],
data: data
})
console.log(initTx);
const tx = new Transaction()
.add(initTx, createLAcc, createPState);
let confTx = await connection.sendTransaction(tx, [payer, lockingAccnt, programState], {skipPreflight: false, preflightCommitment: 'confirmed'})
.then(()=>{console.log("done")}).catch((e)=>{console.log("error",e)});;
console.log(confTx);
});
I am running these tests with Anchor. The error message I get is a panic from data, which I am assuming is from the Buffer Layout not being formatted correctly:
airdrop: [object Object]
TransactionInstruction {
keys: [
{ pubkey: [PublicKey], isSigner: false, isWritable: false },
{ pubkey: [PublicKey], isSigner: false, isWritable: true },
{ pubkey: [PublicKey], isSigner: false, isWritable: false },
{ pubkey: [PublicKey], isSigner: true, isWritable: true },
{ pubkey: [PublicKey], isSigner: false, isWritable: true }
],
programId: PublicKey {
_bn: <BN: 7894a05db0bdd9c31ee41a2478180850e493525f76e7b06fa3f67c80961c6e63>
},
data: <Buffer 00 00 00 00 00 00 00 00 00 03 00 00 00>
}
error SendTransactionError: failed to send transaction: Transaction simulation failed: Error processing Instruction 0: Program failed to complete
at Connection.sendEncodedTransaction (/mnt/c/Users/user/rust/solana/program/node_modules/@solana/web3.js/src/connection.ts:4546:13)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at Connection.sendRawTransaction (/mnt/c/Users/user/rust/solana/program/node_modules/@solana/web3.js/src/connection.ts:4505:20)
at Connection.sendTransaction (/mnt/c/Users/user/rust/solana/program/node_modules/@solana/web3.js/src/connection.ts:4493:12) {
logs: [
'Program 97hNSfjmUz1y9rurSmu5pKKMpP65n8owuq3RoqxCCD54 invoke [1]',
'Program log: Entrypoint',
'Program log: Beginning processing',
"Program log: panicked at 'called `Option::unwrap()` on a `None` value', src/instruction.rs:112:22",
'Program 97hNSfjmUz1y9rurSmu5pKKMpP65n8owuq3RoqxCCD54 consumed 2979 of 600000 compute units',
'Program failed to complete: BPF program panicked',
'Program 97hNSfjmUz1y9rurSmu5pKKMpP65n8owuq3RoqxCCD54 failed: Program failed to complete'
]
}
That error occurs on the 'none' value which is coming from data. I have three main questions about this:
- How can I format data correctly? How can I use Buffer Layout for seeds when it is unavailble to use uint arrays for the data field?
- Would I be better off rewriting this as an anchor program? I can handle that easily since it provides an idl and the program. workspace.
- Is there an easier way to do these transactions for non-anchor programs?
I really appreciate any help I get here. I know this is a lot to take in.