2

In my project, I'm using native Solana to create a contract. It is a basic program for users to enter their product info. I successfully built and deployed the contract but when I try to send a transaction from a client to enter product data I get an error:
Err value: Custom { kind: InvalidInput, error: "Unexpected length of input" }

index.ts (client)

const productInstructionLayout = borsh.struct([
    borsh.u8('variant'),
    borsh.str('name'),
    borsh.u32('price'),
    borsh.u32('quantity'),
])

async function sendTestProductInfo(signer: web3.Keypair, programId: web3.PublicKey, connection: web3.Connection) {
    let buffer = Buffer.alloc(1000)
    const productName = `Shirt${Math.random()*100}`
    productInstructionLayout.encode(
        {
            variant: 0,
            name: productName,
            price: 200,
            quantity: 2000,
        },
        buffer
    )

    buffer = buffer.slice(0, productInstructionLayout.getSpan(buffer))
    console.log(buffer.length)
    const [pda] = web3.PublicKey.findProgramAddressSync(
        [signer.publicKey.toBuffer(), Buffer.from(productName)],
        programId
    )

    console.log("PDA is:", pda.toBase58())

    const transaction = new web3.Transaction()
    
    const instruction = new web3.TransactionInstruction({
        programId: programId,
        data: buffer,
        keys: [
            {
                pubkey: signer.publicKey,
                isSigner: true,
                isWritable: false
            },
            {
                pubkey: pda,
                isSigner: false,
                isWritable: true
            },
            {
                pubkey: web3.SystemProgram.programId,
                isSigner: false,
                isWritable: false
            }
        ]
    })

    transaction.add(instruction)
    const tx = await web3.sendAndConfirmTransaction(connection, transaction, [signer])
}

instruction.rs

use borsh::BorshDeserialize;
use solana_program::program_error::ProgramError;
pub enum ProductInstruction {
    AddProductData {
        name: String,
        price: u32,
        quantity: u32,
    },
    UpdateProductData {
        name: String,
        price: u32,
        quantity: u32,
    },
    DeleteProductData {
        name: String,
    },
}

#[derive(BorshDeserialize)]
struct ProductPayload {
    name: String,
    price: u32,
    quantity: u32,
}

impl ProductInstruction {
    pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
        let (&variant, rest) = input
            .split_first()
            .ok_or(ProgramError::InvalidInstructionData)?;
        let payload = ProductPayload::try_from_slice(rest).unwrap();
        Ok(match variant {
            0 => Self::AddProductData {
                name: payload.name,
                price: payload.price,
                quantity: payload.quantity,
            },
            1 => Self::UpdateProductData {
                name: payload.name,
                price: payload.price,
                quantity: payload.quantity,
            },
            2 => Self::DeleteProductData { name: payload.name },
            _ => return Err(ProgramError::InvalidInstructionData),
        })
    }
}

processor.rs

pub fn process_instruction(
    program_id: &Pubkey,
    accounts: &[AccountInfo],
    instruction_data: &[u8],
) -> ProgramResult {
    let instruction = ProductInstruction::unpack(instruction_data)?;
    match instruction {
        ProductInstruction::AddProductData {
            name,
            price,
            quantity,
        } => add_product_data(program_id, accounts, name, price, quantity),
        ProductInstruction::UpdateProductData {
            name,
            price,
            quantity,
        } => update_product_data(program_id, accounts, name, price, quantity),
        ProductInstruction::DeleteProductData { name } => {
            delete_product_data(program_id, accounts, name)
        }
    }
}
pub fn add_product_data(
    program_id: &Pubkey,
    accounts: &[AccountInfo],
    name: String,
    price: u32,
    quantity: u32,
) -> ProgramResult {
    let account_info_iter = &mut accounts.iter();

    let initializer = next_account_info(account_info_iter)?;
    let pda_account = next_account_info(account_info_iter)?;
    let system_program = next_account_info(account_info_iter)?;

    if !initializer.is_signer {
        msg!("Missing required signature");
        return Err(ProgramError::MissingRequiredSignature)
    }
    
    let (pda, bump_seed) = Pubkey::find_program_address(
        &[initializer.key.as_ref(), name.as_bytes().as_ref()],
        program_id,
    );

    if pda != *pda_account.key {
        msg!("Invalid seeds for PDA");
        return Err(ProgramError::InvalidArgument)
    }
    
    let account_len: usize = 1000;
    let total_len: usize = 1 + (4 + name.len()) + 32 + 32;
    if total_len > 1000 {
        msg!("Data length is larger than 1000 bytes");
        return Err(ReviewError::InvalidDataLength.into())
    }
    let rent = Rent::get()?;
    let rent_lamports = rent.minimum_balance(account_len);

    invoke_signed(
        &system_instruction::create_account(
            initializer.key,
            pda_account.key,
            rent_lamports,
            account_len.try_into().unwrap(),
            program_id,
        ),
        &[
            initializer.clone(),
            pda_account.clone(),
            system_program.clone(),
        ],
        &[&[
            initializer.key.as_ref(),
            name.as_bytes().as_ref(),
            &[bump_seed],
        ]],
    )?;

    let mut product_data =
        try_from_slice_unchecked::<ProductAccountState>(&pda_account.data.borrow()).unwrap();
    product_data.name = name;
    product_data.price = price;
    product_data.quantity = quantity;
    product_data.is_initialized = true;

    product_data.serialize(&mut &mut pda_account.data.borrow_mut()[..])?;
    Ok(())
}

It seems that the error has to do with instruction_data. But I am not sure where it comes from since I matched the layout on my client with the contract payload. Can someone help?

1 Answer 1

2

Looks like you're forgetting to reserve the first element of the buffer for the instruction representation. buffer[0] should be set to 0 to represent AddProductData, while the rest of your arguments are serialized into the buffer starting from [1..].

1
  • You're highly welcome
    – Ademola
    Commented Feb 17, 2023 at 8:49

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.