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?