Manually creating a PDA in the instruction function - rust

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!

Related

How Can You Create a Solana PDA and then add Data to it?

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

How to get the current time in Solana program without using any external SystemProgram account

I am writing this Solana program without using Anchor. I want the program to know the current timestamp without using any external SystemProgram.
This is the current version of my program:
use solana_program::{
account_info::AccountInfo,
entrypoint,
entrypoint::ProgramResult,
msg,
pubkey::Pubkey,
sysvar,
};
entrypoint!(process_instruction);
fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8]
) -> ProgramResult {
msg!("Hello Solana! (From Rust)");
let nowtime = sysvar::clock::Clock.unix_timestamp;
Ok(())
}
However, I'm getting this error:
expected value, found struct `sysvar::clock::Clock
How am I supposed to use this sysvar variable?
You should be able to get the time within a program with the following:
use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::{
account_info::{next_account_info, AccountInfo},
clock::Clock,
entrypoint,
entrypoint::ProgramResult,
msg,
pubkey::Pubkey,
sysvar::Sysvar,
};
entrypoint!(process_instruction);
#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct HelloState {
is_initialized: bool,
}
// Accounts required
/// 1. [signer, writable] Payer
/// 2. [writable] Hello state account
pub fn process_instruction(
_program_id: &Pubkey,
accounts: &[AccountInfo],
_instruction_data: &[u8],
) -> ProgramResult {
let accounts_iter = &mut accounts.iter();
// Payer account
let _payer_account = next_account_info(accounts_iter)?;
// Hello state account
let hello_state_account = next_account_info(accounts_iter)?;
// Getting clock directly
let clock = Clock::get()?;
let mut hello_state = HelloState::try_from_slice(&hello_state_account.data.borrow())?;
hello_state.is_initialized = true;
hello_state.serialize(&mut &mut hello_state_account.data.borrow_mut()[..])?;
msg!("Account initialized :)");
// Getting timestamp
let current_timestamp = clock.unix_timestamp;
msg!("Current Timestamp: {}", current_timestamp);
Ok(())
}
More specifically
let clock = Clock::get()?;
let current_timestamp = clock.unix_timestamp;
Note: The time on-chain is an estimation. The only reliable clock is slot number on chain. You can read more implementations for getting time here.
You can get the solana time by following function.
use anchor_lang::{prelude::*, solana_program::clock};
use std::convert::TryInto;
pub fn now_ts() -> Result<u64> {
Ok(clock::Clock::get()?.unix_timestamp.try_into().unwrap())
}

Problems in storing data in Solana Account

I need help in storing data into an account.
Here's what I've done so far.
Below is my 'process_instruction' entry point,
pub fn process_instruction(...) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
// get account where we have to store state
let states_account_info = next_account_info(account_info_iter)?;
let xyz: u64 = 1234567;
let counter_struct = Counter {
data: xyz
};
counter_struct.serialize(&mut &mut states_account_info.data.borrow_mut()[..])?;
Ok(())
}
Here's my Counter Struct
#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct Counter {
/// mappings of the keys
pub data: u64,
}
And this is how, I create the account inside the test,
let state_account_pubkey = Pubkey::new_unique();
let mut program_test = ProgramTest::new(
"solana_states_save_program",
program_id,
processor!(process_instruction),
);
program_test.add_account(
state_account_pubkey,
Account {
lamports: 5,
owner: program_id,
..Account::default()
},
);
But after executing the test, I get the following error, inside process_instruction() method (on counter_struct.serialize(...) statement),
thread 'main' panicked at 'called Result::unwrap() on an Err value:
TransactionError(InstructionError(0, BorshIoError("Unknown")))'
Kindly help.
We also needed to add account data space when creating an Account in tests, which I was missing.
program_test.add_account(
state_account_pubkey,
Account {
lamports: 5,
data: vec![0_u8; mem::size_of::<u32>()],
owner: program_id,
..Account::default()
},
);
Hope, it helps someone.

How to transfer from 2 accounts to the same vault in Serum Anchor?

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

How to solve Transaction simulation failed: Error processing Instruction during a solana blockchain transaction?

I have been trying to write some data with struct type to solana blockchain but have been stuck at an error which says ->
Transaction simulation failed: Error processing Instruction 0: Program failed to complete
Program 2qCpEJASM553foMRmd4MHRLxEFywKwvaUXRtypXJp4zv invoke [1]
Program consumption: 199505 units remaining
Program log: Instruction_data message object LoveRecord { groom: "a", created_on: "0" }
Program log: libstd rust_begin_panic
Program log: panicked at 'called `Option::unwrap()` on a `None` value', src/lib.rs:67:98
Program 2qCpEJASM553foMRmd4MHRLxEFywKwvaUXRtypXJp4zv consumed 200000 of 200000 compute units
Program failed to complete: BPF program panicked
Program 2qCpEJASM553foMRmd4MHRLxEFywKwvaUXRtypXJp4zv failed: Program failed to complete
Code for solana program entrypoint in Rust language is ->
use borsh::{ BorshDeserialize, BorshSerialize };
use solana_program::{
account_info::{ next_account_info, AccountInfo },
entrypoint,
entrypoint::ProgramResult,
msg,
program_error::ProgramError,
pubkey::Pubkey,
};
use std::io::ErrorKind::InvalidData;
#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct LoveRecord {
pub groom: String,
pub created_on: String
}
const DUMMY_TX_ID: &str = "a";
const DUMMY_CREATED_ON: &str = "0"; // milliseconds, 16 digits
pub fn get_init_love_record() -> LoveRecord {
LoveRecord{ groom: String::from(DUMMY_TX_ID), created_on: String::from(DUMMY_CREATED_ON) }
}
pub fn get_init_love_records() -> Vec<LoveRecord> {
let mut records = Vec::new();
for _ in 0..10 {
records.push(get_init_love_record());
}
return records;
}
entrypoint!(process_instruction);
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8]
) -> ProgramResult {
let accounts_iter = &mut accounts.iter();
let account = next_account_info(accounts_iter)?;
if account.owner != program_id {
msg!("Greeted account does not have the correct program id");
return Err(ProgramError::IncorrectProgramId);
}
let instruction_data_message = LoveRecord::try_from_slice(instruction_data).map_err(|err| {
msg!("Attempt to deserialize instruction data has failed. {:?}", err);
ProgramError::InvalidInstructionData
})?;
solana_program::msg!("pass");
msg!("pass");
msg!("Instruction_data message object {:?}", instruction_data_message);
let mut existing_data_messages = match <Vec<LoveRecord>>::try_from_slice(&account.data.borrow_mut()) {
Ok(data) => data,
Err(err) => {
if err.kind() == InvalidData {
msg!("InvalidData so initializing account data");
get_init_love_records()
} else {
msg!("Unknown error decoding account data {:?}", err)
}
}
};
let index = existing_data_messages.iter().position(|p| p.groom == String::from(DUMMY_TX_ID)).unwrap(); // find first dummy data entry
msg!("Found index {}", index);
existing_data_messages[index] = instruction_data_message; // set dummy data to new entry
let updated_data = existing_data_messages.try_to_vec().expect("Failed to encode data."); // set records object back to vector data
msg!("Final existing_data_messages[index] {:?}", existing_data_messages[index]);
// data algorithm for storing data into account and then archiving into Arweave
// 1. Each LoveRecord object will be prepopulated for txt field having 1 characters (length of a arweave tx).
// Each LoveRecordContainer will be prepopulated with 10 LoveRecord objects with dummy data.
// 2. Client will submit an arweave tx for each message; get back the tx id; and submit it to our program.
// 3. This tx id will be saved to the Solana program and be used for querying back to arweave to get actual data.
let data = &mut &mut account.data.borrow_mut();
msg!("Attempting save data.");
data[..updated_data.len()].copy_from_slice(&updated_data);
let saved_data = <Vec<LoveRecord>>::try_from_slice(data)?;
msg!("LoveRecord has been saved to account data. {:?}", saved_data[index]);
msg!("End program.");
Ok(())
}
Code to send transaction using Borsch Serialize is ->
static async sayHello(data) {
let config = DappLib.getConfig();
console.log(data);
let DUMMY_TX_ID = "a";
let DUMMY_CREATED_ON = "0";
class LoveRecord {
constructor(fields = undefined) {
this.groom = DUMMY_TX_ID;
this.created_on = DUMMY_CREATED_ON; // max milliseconds in dat
if (fields) {
this.groom = fields.groom;
this.created_on = fields.created_on;
}
}
}
const LoveRecordSchema = new Map([[
LoveRecord,
{
kind: "struct",
fields: [
["groom", "string"],
["created_on", "string"],
],
},
]]);
const loveobj = new LoveRecord();
loveobj.groom = "a";
loveobj.created_on = "0";
const res = borsh.serialize(LoveRecordSchema,loveobj);
let result = await Blockchain.put({ config }, 'greeting', res);
console.log("Pass")
return {
type: DappLib.DAPP_RESULT_OBJECT,
label: 'Transaction Result',
result
}
}
Code to send and sign transaction has been taken care of.
Any help would be really appreciated.
This is quite a specific question around your code, but the error message is probably the best place to look:
Program log: panicked at 'called `Option::unwrap()` on a `None` value', src/lib.rs:67:98
This seems to correspond with the line:
let index = existing_data_messages.iter().position(|p| p.groom == String::from(DUMMY_TX_ID)).unwrap();
You're calling unwrap() on a None value, which makes sense, considering nothing has been written to this account before. You'll need to handle the case where DUMMY_TX_ID is not found in your vector.
You can find more information about how position() works at https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.position

Resources