6

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)?

4 Answers 4

9

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
3
  • how can all signatures required to authorized the instruction are available prior to invocation? if it is a new invocation to another program, how can the signature for that already exist?
    – dade
    Sep 14, 2022 at 19:10
  • I didn't clearly understand the difference. It seems invoke() is a general purpose function and can do what invoke_signed() can do, because calling program's signature can be just added to all signatures ... and then do the rest of stuff using invoke()? If not, please explain what one can do that other cannot do? and why exactly? Feb 5, 2023 at 22:26
  • any signer requirements by an invoke()ed instruction must already be in force at the invoking or higher level. either directly via a digital signature on the transaction or a PDA promoted logically via invoke_signed() instruction up the CPI stack. invoke_signed() can (logically) promote a PDA from non-signer to signer for the duration of the invocation
    – trent.sol
    Feb 6, 2023 at 22:30
7

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!

6

Both of these are used to CPI into another program on Solana. However, invoke_signed is used when there is a required signer for that instruction and that signer is a PDA. invoke is used when there is no new signer required.

invoke_signed also requires you pass in the seeds for the PDA.

0

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]]],
)?;

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.