Can someone explain what the difference is between the two and when to use each? when to use invoke vs invoke_signed inside a program (smart contract)?
The tl;dr version is that both are used to allow one program to invoke instructions of another, but
invoke()is used when all signatures required to authorized the instruction are available prior to invocation
invoke_signed()is used when the calling program's "signature" is required to authorize the called instruction
Kinda complex but I'll try to take a stab at trying to answer this with an example.
They both allow you to do Cross Program Invocation, commonly abbreviated as CPI.
So before we get to that part, for completeness, we'll introduce the fact that we have account info from all the accounts that were passed into the instruction already out of the account iterator in the following variables.
let accounts_iter = &mut accounts.iter(); let claimer_info = next_account_info(accounts_iter)?; let system_program_info = next_account_info(accounts_iter)?; let token_program_info = next_account_info(accounts_iter)?; let rent_program_info = next_account_info(accounts_iter)?; let ata_program_info = next_account_info(accounts_iter)?; let pda_info = next_account_info(accounts_iter)?; let claimer_reward_holder_info = next_account_info(accounts_iter)?; let pda_reward_holder_info = next_account_info(accounts_iter)?; let reward_mint_info = next_account_info(accounts_iter)?;
Now, let's say your program owned a Program Derived Address, a PDA, that was used to store an SPL reward token and users could call a function to claim reward tokens they earned.
You would first find a PDA address that's off the ed25519 curve using Pubkey::find_program_address() like so:
let (pda_address, pda_bump) = Pubkey::find_program_address(&[&PDA_WORD.as_bytes()], &program_id);
There's a lot that can be said about security here that will quickly go out of scope but in short its usually good to verify accounts that are passed in.
Before you could transfer the tokens you would need to invoke a CPI call to the SPL Associated Token Program to create an associated token account, ATA, for the claimer to receive the reward token. In this case you can use regular invoke because the account will be for the claimer and they will not interact with your PDA. You would use a regular invoke invocation that doesn't require seeds because it has nothing to do with the PDA. Like so:
`invoke( &spl_associated_token_account::instruction::create_associated_token_account( claimer.key, claimer.key, reward_mint_info.key, ), &[ claimer.clone(), claimer_reward_holder_info.clone(), claimer.clone(), reward_mint_info.clone(), system_program.clone(), token_program_info.clone(), rent_program_info.clone(), assoc_account_info.clone(), ], )?;`
Then to transfer the reward tokens to the newly created ATA you would invoke a invoke_signed invocation that would require the seeds used to generate the PDA as the "signature." In quotes, because PDAs are not on the ed25519 curve and as such don't have actual private keys. The invoked call would look like this:
`invoke_signed( &spl_token::instruction::transfer( token_program_info.key, pda_reward_holder_info.key, claimer_reward_holder_info.key, pda_info.key, &, rewardAmount, )?, &[ pda_reward_holder_info.clone(), claimer_reward_holder_info.clone(), pda_info.clone(), token_program_info.clone() ], &[&[&PDA_SEED_WORD.as_bytes(), &[pda_bump]]], )?;`
Hope this helps!
Let's say you want to send some sol from your account to someone. You just need
invoke because user wallet has signed the instruction so when the system program is called, the transfer on the system program is invoked. Then it will find a signature for user wallet, and it will allow that transfer.
if the same transfer should be done by a PDA, then you need to do
invoke_signed because the user has not signed because he cannot. there is no private key for that. the program needs to sign and the program needs to provide the signer's seeds. from here
Unlike a regular signature where a private key is used to sign, invoke_signed uses the optional seeds, bump seed, and program ID to derive a PDA and sign an instruction. This is done by comparing the derived PDA against all accounts passed into the instruction. If any of the accounts match the PDA, then the signer field for that account is set to true.
A program can securely sign transactions this way because invoke_signed generates the PDA used for signing with the program ID of the program invoking the instruction. Therefore, it is not possible for one program to generate a matching PDA to sign for an account with a PDA derived using another program ID.
invoke_signed( // instruction &system_instruction::create_account( note_creator.key, note_pda_account.key, rent_lamports, account_len.try_into().unwrap(), program_id, ), // account_infos &[note_creator.clone(), note_pda_account.clone(), system_program.clone()], // signers_seeds &[&[note_creator.key.as_ref(), note_id.as_bytes().as_ref(), &[bump_seed]]], )?;