I want to program a smart contract that stores data of a random generated "planet". I generated the data for the planet in another program and i use a uuid to recreate the planet.
Now i want to use this uuid and a public key to create the address. I tried to use PDA's for that and it kind of works. The first address i derive with the pubkey and the uuid works just fine, but if i want to derive another address with the same pubkey and different uuid I get a CPI Error.
Had anybody common experiences or is the way i use PDA's wrong and if so how would you store the data?
My code:
pub fn create_planet(ctx: Context<CreatePlanet>, owner: Pubkey, authority: Pubkey, bump: u8, uuid: u64) -> Result<()>{
//pub fn create_planet(ctx: Context<CreatePlanet>, owner: Pubkey, authority: Pubkey, bump: u8) -> Result<()>{
msg!("test");
let planet: &mut Account<Planet> = &mut ctx.accounts.planet;
let payer: &Signer = &ctx.accounts.payer;
/*if data.chars().count() > 212 {
return Err(ErrorCode::dataTooLong.into());
}*/
if planet.owner == anchor_lang::prelude::Pubkey::default() {
planet.owner = owner;
}
planet.authority = authority;
planet.bump = bump;
planet.uuid = uuid;
Ok(())
}
Struct:
#[derive(Accounts)]
#[instruction(bump: u8, uuid: u64)]
//#[instruction(bump: u8)]
pub struct CreatePlanet<'info>{
#[account(init, seeds = [b"planet".as_ref(),
payer.key.as_ref(), uuid.to_le_bytes().as_ref()], bump,
payer=payer, space=Planet::LEN)]
//#[account(init, seeds = [payer.key.as_ref(),
uuid.to_le_bytes().as_ref()], bump, payer=payer,
space=Planet::LEN)]
pub planet: Account<'info, Planet>,
#[account(mut)]
pub payer: Signer<'info>,
pub system_program: Program <'info, System>
}
RPC Call:
const uuid = new anchor.BN(parseInt((Date.now() / 1000).toString()));
const uuidBuffer = uuid.toBuffer('le', 8);
const [sandboxPda, sandboxBump] =
await PublicKey.findProgramAddress([Buffer.from('planet'), wallet.publicKey.toBuffer(), uuidBuffer], program.programId);
//await PublicKey.findProgramAddress([wallet.publicKey.toBuffer(), uuid], program.programId);
console.log("PDA: ", sandboxPda.toBase58());
console.log("Bump: ", sandboxBump);
await program.rpc.createPlanet(wallet.publicKey, ourWallet.publicKey, sandboxBump, uuid, {
accounts: {
planet: sandboxPda, //planet.publicKey
payer: wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId
},
signers: [wallet]
});
It causes to the following error:
Error: failed to send transaction: Transaction simulation failed: Error processing Instruction 0: Cross-program invocation with unauthorized signer or writable account
at Connection.sendEncodedTransaction (node_modules/#solana/web3.js/src/connection.ts:4068:13)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at Connection.sendRawTransaction (node_modules/#solana/web3.js/src/connection.ts:4030:20)
at sendAndConfirmRawTransaction (node_modules/#project-serum/anchor/src/provider.ts:284:21)
at AnchorProvider.sendAndConfirm (node_modules/#project-serum/anchor/src/provider.ts:144:14)
at Object.rpc [as createPlanet] (node_modules/#project-serum/anchor/src/program/namespace/rpc.ts:29:16)
Can you upload the code where you call the CPI?
Related
I have figured out how to create a PDA in the rust contract, but each time I try to serialize the data into the new PDA account, it fails with the following message:
WalletSendTransactionError: failed to send transaction: Transaction simulation failed: Error processing Instruction 0: Program failed to complete
As you can see its not very helpful, the following are my different files, I believe I have added everything you need to be able to just copy and paste to try yourselves if desired.
Main.js - function connected to a simple onClick
const createAdminPda = async () => {
const ownerKey = new PublicKey(publicKey.toString());
let [pda_key_pair, bump] = await PublicKey.findProgramAddress([Buffer.from("AdminPda3", "utf8")], animal_program_id)
const instructionTOOurProgram = new TransactionInstruction({
keys: [
{
pubkey: SystemProgram.programId,
isWritable: true,
isSigner: false,
},
{
pubkey: ownerKey,
isWritable: true,
isSigner: true,
},
{
pubkey: pda_key_pair,
isWritable: true,
isSigner: false,
}
],
programId: animal_program_id,
data: new Uint8Array([0]),
});
console.log("instructionTOOurProgram: ", instructionTOOurProgram)
const trans = await setPayerAndBlockhashTransaction(
[instructionTOOurProgram], ownerKey
);
console.log("trans: ", trans)
// Submit transaction
const transactionId = await sendTransaction(
trans,
connection
);
console.log("end transactionId", transactionId);
const result = await connection.confirmTransaction(transactionId);
console.log("end confirmTransaction", result);
}
lib.rs
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
use solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint,
entrypoint::ProgramResult,
instruction::{AccountMeta, Instruction},
msg,
program::invoke,
program::invoke_signed,
program_error::ProgramError,
program_pack::{IsInitialized, Pack, Sealed},
pubkey::Pubkey,
rent::Rent,
system_instruction, system_program,
sysvar::Sysvar,
};
const ADMIN_ACCOUNT_SEED: &[u8; 9] = b"AdminPda3";
entrypoint!(process_instruction);
#[derive(BorshDeserialize, BorshSerialize)]
pub enum MainInstruction {
CreateAdminPda2,
}
pub fn create_admin_pda(
program_id: Pubkey,
payer_account: Pubkey,
pda_account: Pubkey,
) -> Instruction {
Instruction {
program_id,
accounts: vec![
AccountMeta::new_readonly(system_program::id(), false),
AccountMeta::new(payer_account, true),
AccountMeta::new(pda_account, true),
],
data: MainInstruction::CreateAdminPda2 {}
.try_to_vec()
.expect("IO error"),
}
}
#[derive(Debug, BorshDeserialize, BorshSerialize)]
pub struct AdminPda2 {
pub st_type: String,
pub st_typeb: String,
}
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
data: &[u8],
) -> ProgramResult {
let processor = Processor::new(program_id, accounts, data);
processor.process()?;
Ok(())
}
struct Processor<'a, 'b> {
program_id: &'a Pubkey,
accounts: &'a [AccountInfo<'b>],
data: &'a [u8],
}
impl<'a, 'b> Processor<'a, 'b> {
fn new(program_id: &'a Pubkey, accounts: &'a [AccountInfo<'b>], data: &'a [u8]) -> Self {
Self {
program_id,
accounts,
data,
}
}
fn process(&self) -> ProgramResult {
msg!("ProgramResult process function");
let transaction = MainInstruction::try_from_slice(self.data)?;
msg!("ProgramResult data input {:?}", self.data);
match transaction {
MainInstruction::CreateAdminPda2 {} => {
self.create_admin_pda()?
}
}
Ok(())
}
fn create_admin_pda(&self) -> ProgramResult {
let account_info_iter = &mut self.accounts.iter();
// get Sys Program account
let system_program_account = next_account_info(account_info_iter)?;
msg!("Given system_program_account Key {:?}: ", *system_program_account.key);
// get payer account
let payer = next_account_info(account_info_iter)?;
msg!("Given payer Key {:?}: ", *payer.key);
// get admin pda
let admin_account_pda = next_account_info(account_info_iter)?;
msg!("Given admin_account_pda Key {:?}: ", *admin_account_pda.key);
let (admin_account_key, bump) = Pubkey::find_program_address(&[ADMIN_ACCOUNT_SEED], self.program_id);
msg!("Corrent PDA Key {:?}: ", admin_account_key);
msg!("Given PDA Key {:?}: ", *admin_account_pda.key);
// Do a check that the PDA does not already exist
//// Check that the function was fed the correct Public Key
if admin_account_key == *admin_account_pda.key {
msg!("The keys match");
// Check that the PDA is empty before creating it
if admin_account_pda.data_is_empty() {
msg!("The account is not created yet, create it");
// Create new account
// payer
// pda key
// system program
invoke_signed(
&system_instruction::create_account(
payer.key,
admin_account_pda.key,
Rent::get()?.minimum_balance(AdminPda2::SIZE),
AdminPda2::SIZE as u64,
self.program_id,
),
&[
payer.clone(),
admin_account_pda.clone(),
system_program_account.clone(),
],
&[&[ADMIN_ACCOUNT_SEED, &[bump]]],
)?;
} else {
msg!("The account is already created");
}
// Create a Animal with the given owner and time
let admin_data = AdminPda2 {
st_type: "AdminPda2".to_owned(),
st_typeb: "AdminPda2".to_owned()
};
msg!("admin_data {:?}", admin_data);
// Serialize the Animal to it's raw bytes
let mut admin_vec = admin_data.try_to_vec()?;
msg!("admin_vec {:?}", admin_vec);
// Write the serialized data to the new pda [commented out the function finishes ok, uncomment the next line it fails]
admin_vec.swap_with_slice(*admin_account_pda.try_borrow_mut_data()?);
msg!("Finished");
} else {
msg!("The keys did not match, the account will not be created");
}
Ok(())
}
}
The following is one of the executions where I successfully created the account but had the final line with 'admin_vec.swap_with_slice' commented out, so only the account was created and the data serialized but not yet written to the account.
https://explorer.solana.com/tx/4uhamGkpSo2Sf8kTn5Xz5u5bLXWL4TtpJf2DKAJ77HoKAsUrsVN9AwDuK3R9eJkEbN4YvcaQXMLHMvFqFVscWzEZ?cluster=devnet
I'm new to Solana and Rust. I've done some modifications with solana js hello world example and the error with borsh serialize and deserialize occurs.
Here is my rust program:
use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint,
entrypoint::ProgramResult,
msg,
program_error::ProgramError,
pubkey::Pubkey,
};
/// Define the type of state stored in accounts
#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct GreetingAccount {
/// number of greetings
pub id: u32,
pub name: String
}
// Declare and export the program's entrypoint
entrypoint!(process_instruction);
// Program entrypoint's implementation
pub fn process_instruction(
program_id: &Pubkey, // Public key of the account the hello world program was loaded into
accounts: &[AccountInfo], // The account to say hello to
_instruction_data: &[u8], // Ignored, all helloworld instructions are hellos
) -> ProgramResult {
msg!("Hello World Rust program entrypoint");
// Iterating accounts is safer than indexing
let accounts_iter = &mut accounts.iter();
// Get the account to say hello to
let account = next_account_info(accounts_iter)?;
// The account must be owned by the program in order to modify its data
if account.owner != program_id {
msg!("Greeted account does not have the correct program id");
return Err(ProgramError::IncorrectProgramId);
}
// Increment and store the number of times the account has been greeted
let mut greeting_account = GreetingAccount::deserialize(&mut &account.data.borrow()[..])?;
let msg = GreetingAccount::deserialize(&mut &_instruction_data[..])?;
greeting_account.id = msg.id;
greeting_account.name = msg.name;
greeting_account.serialize(&mut &mut account.data.borrow_mut()[..])?;
msg!("Greeted {} time(s)!", greeting_account.id);
Ok(())
}
The typescript client code is here for sending transaction:
export async function sayHello(): Promise<void> {
console.log('Saying hello to', greetedPubkey.toBase58());
const instruction = new TransactionInstruction({
keys: [{pubkey: greetedPubkey, isSigner: false, isWritable: true}],
programId,
data: Buffer.from(borsh.serialize(
GreetingSchema,
new GreetingAccount({id: 126, name: 'anas'}),
)),
});
await sendAndConfirmTransaction(
connection,
new Transaction().add(instruction),
[payer],
);
}
The error logs from the console:
logs: [
'Program 7X4jotvCZgDyEPHtAGCZeYXeJb4A8mjZFUhx9two37Vp invoke [1]',
'Program log: Hello World Rust program entrypoint',
'Program 7X4jotvCZgDyEPHtAGCZeYXeJb4A8mjZFUhx9two37Vp consumed 1412 of 1400000 compute units',
'Program 7X4jotvCZgDyEPHtAGCZeYXeJb4A8mjZFUhx9two37Vp failed: Failed to serialize or deserialize account data: Unknown'
]
The error is due to this line of code:
greeting_account.serialize(&mut &mut account.data.borrow_mut()[..])?;
Can someone help me identifying the reason of this failure?
I am trying to dynamically allocate a PDA with system_program's create_account instruction, but I feel like I am missing some key piece of information. I keep getting an error saying:
Error: failed to send transaction: Transaction simulation failed: Error processing Instruction 0: Cross-program invocation with unauthorized signer or writable account
I managed to deduce that the create_account call itself is what fails; I am not sure if I passed in the wrong seeds, or if my signers on the frontend/context are configured wrong (previously I was going with a Signer<'info> directly, but then following the Anchor Book article on PDAs, I turned that into an unchecked account, for better or worse.
pub fn init_collection(
_ctx: Context<InitCollection>,
) -> Result<()> {
msg!("checking if account is initialized");
if !_ctx.accounts.col.data_is_empty() {
msg!("account is already initialized");
return Ok(());
}
let acc = _ctx.accounts.borrow_mut();
init_pda(
&_ctx.accounts.col.to_account_info(),
&_ctx.accounts.rent,
14,
&crate::id(),
&_ctx.accounts.payer.to_account_info(),
&_ctx.accounts.system_program,
&[
b"col".as_ref(),
&[255],
])?;
Ok(())
}
#[derive(Accounts)]
pub struct InitCollection<'info> {
/// CHECK: Is checked in the program.
#[account(mut, seeds = [b"col".as_ref()], bump)]
col: UncheckedAccount<'info>,
/// CHECK: Is checked in the CPI.
payer: UncheckedAccount<'info>,
system_program: Program<'info, System>,
rent: Sysvar<'info, Rent>,
}
For creating the account itself, I basically lifted the functionality from the anchor project:
pub fn init_pda<'a>(
new_account: &AccountInfo<'a>,
rent_key: &Sysvar<'a, Rent>,
space: usize,
owner: &Pubkey,
payer: &AccountInfo<'a>,
system_program: &AccountInfo<'a>,
seeds_with_nonce: &[&[u8]],
) -> Result<()>
{
let rent = &Rent::from_account_info(&rent_key.to_account_info())?;
// If the account being initialized already has lamports, then
// return them all back to the payer so that the account has
// zero lamports when the system program's create instruction
// is eventually called.
let __current_lamports = new_account.lamports();
if __current_lamports == 0 {
// Create the token account with right amount of lamports and space, and the correct owner.
let lamports = rent.minimum_balance(space);
let cpi_accounts = anchor_lang::system_program::CreateAccount {
from: payer.to_account_info(),
to: new_account.to_account_info()
};
let cpi_context = anchor_lang::context::CpiContext::new(system_program.to_owned(), cpi_accounts);
anchor_lang::system_program::create_account(cpi_context.with_signer(&[seeds_with_nonce]), lamports, space as u64, owner)?; // <== This seems to be where it's failing
} else {
// Fund the account for rent exemption.
let required_lamports = rent
.minimum_balance(space)
.max(1)
.saturating_sub(__current_lamports);
if required_lamports > 0 {
let cpi_accounts = anchor_lang::system_program::Transfer {
from: payer.to_owned(),
to: new_account.to_owned(),
};
let cpi_context = anchor_lang::context::CpiContext::new(system_program.to_owned(), cpi_accounts);
anchor_lang::system_program::transfer(cpi_context, required_lamports)?;
}
// Allocate space.
let cpi_accounts = anchor_lang::system_program::Allocate {
account_to_allocate: new_account.to_owned(),
};
let cpi_context = anchor_lang::context::CpiContext::new(system_program.to_account_info(), cpi_accounts);
anchor_lang::system_program::allocate(cpi_context.with_signer(&[seeds_with_nonce]), space as u64)?;
// Assign to the spl token program.
let cpi_accounts = anchor_lang::system_program::Assign {
account_to_assign: new_account.to_owned()
};
let cpi_context = anchor_lang::context::CpiContext::new(system_program.to_account_info(), cpi_accounts);
anchor_lang::system_program::assign(cpi_context.with_signer(&[seeds_with_nonce]), owner)?;
}
Ok(())
}
Can anyone point me to the right direction? I can't seem to be able to figure out where exactly what I am missing.
Thank you!
I am trying to run the following code in Anchor Solana, with program in rust as follows:
use anchor_lang::prelude::*;
declare_id!("RnbXAWg5mCvmSafjd1CnYaz32qLgZHdeHK6xzHDi1yU");
#[program]
pub mod sol_proj_1 {
use super::*;
pub fn initialize(ctx: Context<Initialize>, data: u64) -> ProgramResult {
let my_account = &mut ctx.accounts.my_account;
my_account.data = data;
println!("hello there!");
Ok(())
}
pub fn update(ctx: Context<Update>, data: u64) -> ProgramResult {
let my_account = &mut ctx.accounts.my_account;
my_account.data = data;
Ok(())
}
}
// #[derive(Accounts)]
// pub struct Initialize {}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = user, space = 8 + 8)]
pub my_account: Account<'info, MyAccount>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Update<'info> {
#[account(mut)]
pub my_account: Account<'info, MyAccount>,
}
#[account]
pub struct MyAccount {
pub data: u64,
}
The test program is as follow:
import * as anchor from '#project-serum/anchor';
import { Program } from '#project-serum/anchor';
import { SolProj1 } from '../target/types/sol_proj_1';
const assert = require("assert");
describe('sol_proj_1', () => {
// Configure the client to use the local cluster.
const provider = anchor.Provider.local();
anchor.setProvider(provider);
// The Account to create.
const myAccount = anchor.web3.Keypair.generate();
const program = anchor.workspace.SolProj1 as Program<SolProj1>;
it('Is initialized!', async () => {
// Add your test here.
const tx = await program.rpc.initialize(new anchor.BN(1234), {
accounts: {
myAccount: myAccount.publicKey,
user: provider.wallet.publicKey,
systemProgram: program.programId,
},
signers: [myAccount],
});
/
console.log("Your transaction signature", tx);
});
});
With error when I run the following command
Anchor test
1) sol_proj_1
Is initialized!:
Error: failed to send transaction: invalid transaction: Transaction failed to sanitize accounts offsets correctly
at Connection.sendEncodedTransaction (node_modules/#solana/web3.js/src/connection.ts:3740:13)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at Connection.sendRawTransaction (node_modules/#solana/web3.js/src/connection.ts:3700:20)
at sendAndConfirmRawTransaction (node_modules/#solana/web3.js/src/util/send-and-confirm-raw-transaction.ts:27:21)
at Provider.send (node_modules/#project-serum/anchor/src/provider.ts:118:18)
at Object.rpc [as initialize] (node_modules/#project-serum/anchor/src/program/namespace/rpc.ts:25:23)
I tried the following
change the program ownership as I found that this can cause the issue but that did not work.
I also added entries in Anchor.toml, the tests are running on localhost
I found that empty wallet may also cause this issue but I have airdrop 100 sols in it
The Rust code deploys correctly but the test is failing the 'sanitizationfailure' is listed as follow "Transaction failed to sanitize accounts offsets correctly implies that account locks are not taken for this TX, and should not be unlocked." I couldn't find any info how to take out the locks
source: https://docs.rs/solana-sdk/1.9.2/solana_sdk/transaction/enum.TransactionError.html
Any help is appreciated!
The only thing I can see obvious is maybe the way you're passing in the system program from the frontend. You're passing in your program's id, when you should be passing the in system program id. So instead, try:
const tx = await program.rpc.initialize(new anchor.BN(1234), {
accounts: {
myAccount: myAccount.publicKey,
user: provider.wallet.publicKey,
systemProgram: SystemProgram.programId,
},
signers: [myAccount],
});
I had this error before and have solved.
In my case, I find that the Program_Id in Anchor.toml is different from lib.rs, they should be the same.
Program_Id is generated by running "anchor deploy" in terminal.
Anchor.toml
[programs.localnet]
solana_app = "<Program_ID>"
lib.rs
declare_id!("<Program_ID>");
Running "anchor deploy" in terminal.
And copy the program id in Anchor.toml and lib.rs
I am writing a simple contract of transferring tokens from 2 accounts in a common check vault.
I took the cashiers-check as base example and using this.
// Create check
#[access_control(CreateCheck::accounts(&ctx, nonce))]
pub fn create_check(ctx: Context<CreateCheck>, amount: u64, nonce: u8) -> Result<()> {
// Transfer funds to the check.
let cpi_accounts = Transfer {
from: ctx.accounts.from.to_account_info().clone(),
to: ctx.accounts.vault.to_account_info().clone(),
authority: ctx.accounts.owner.clone(),
};
let cpi_program = ctx.accounts.token_program.clone();
let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
token::transfer(cpi_ctx, amount)?;
// Print the check.
let check = &mut ctx.accounts.check;
check.amount = amount;
check.from = *ctx.accounts.from.to_account_info().key;
check.vault = *ctx.accounts.vault.to_account_info().key;
check.nonce = nonce;
Ok(())
}
this is to collect from second account
pub fn second_send(ctx: Context<SecondSend>) -> Result<()> {
let seeds = &[
ctx.accounts.check.to_account_info().key.as_ref(),
&[ctx.accounts.check.nonce],
];
let signer = &[&seeds[..]];
let cpi_accounts = Transfer {
from: ctx.accounts.from.to_account_info().clone(),
to: ctx.accounts.vault.to_account_info().clone(),
authority: ctx.accounts.check_signer.clone(),
};
let cpi_program = ctx.accounts.token_program.clone();
let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer);
token::transfer(cpi_ctx, ctx.accounts.check.amount)?;
// Burn the check for one time use.
ctx.accounts.check.burned = true;
Ok(())
}
struct:
#[derive(Accounts)]
pub struct SecondSend<'info> {
#[account(mut, has_one = vault)]
check: ProgramAccount<'info, Check>,
#[account(mut)]
vault: AccountInfo<'info>,
check_signer: AccountInfo<'info>,
#[account(mut, has_one = owner)]
from: CpiAccount<'info, TokenAccount>,
#[account(signer)]
owner: AccountInfo<'info>,
token_program: AccountInfo<'info>,
}
this is the test i wrote:
it("Sends token from satan", async () => {
await program.rpc.secondSend({
accounts: {
check: check.publicKey,
vault: vault.publicKey,
checkSigner: checkSigner,
from: satan,
owner: program.provider.wallet.publicKey,
tokenProgram: TOKEN_PROGRAM_ID,
},
});
});
whatever i do im getting this error :
Error: failed to send transaction: Transaction simulation failed: Error processing Instruction 0: custom program error: 0x3
im doing something wrong?
I think you missed out on looking at the Program Logs above the error you mentioned here. Try scrolling up, there should be certain logs.
That should provide more context, but if you're still stuck, you can update your question here :D