1

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:

  1. 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?
  2. 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.
  3. 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.

1 Answer 1

1

I think you want something like BufferLayout.blob(32, 'seeds') instead.

The error is occurring because an Option<T>::None is being unwrap()ed on line 112. There are no line numbers on the processor code that you've provided, but unwrap() is only called on Pubkey::create_program_account()'s result, so odds are your seeds don't resolved to a valid PDA. Looking at your client code, there's no call to (whatever is the JS equivalent of) Pubkey::find_program_address(), which would be required to reliably choose seeds. I'd suggest giving the cookbook article about PDAs and all of its references a read to better understand what might be going wrong here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.