6

There are two programs: master and puppet. On the client side the puppet is called in order to initialize an account on the puppet using an uuid v4 as seed. Then, the client calls the master to proxy a "set instruction" to the puppet, which will update some attribute on this previously created account.

The odd behavior is the following:

  • using this seed (split in two because it exceeds the max length) it works fine
const pdaId = "37f9b205-998c-4583-8d58-ebb7db846755";
  • using this seed, does not
const pdaId = "25df58b2-e5a7-46c7-9803-fdfd2a3895d4";

Master program

#[program]
pub mod pupper_master {
    use super::*;

    pub fn pull_strings(
        ctx: Context<PullStrings>,
        pda_id: String,
        base_bump: u8,
        data: u16,
    ) -> Result<()> {
        msg!("making a cpi call");

        let seeds = &[
            pda_id.as_bytes()[..18].as_ref(),
            pda_id.as_bytes()[18..].as_ref(),
            &[base_bump],
        ];
        let signer = &[&seeds[..]];
        let cpi_accounts = SetData {
            puppet: ctx.accounts.puppet_account.to_account_info(),
        };
        let cpi_program = ctx.accounts.puppet_program.to_account_info();
        let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer);
        puppet_cpi::cpi::set_data(
            cpi_ctx,
            pda_id.clone(),
            base_bump,
            data
        )?;

        Ok(())
    }
}

#[derive(Accounts)]
#[instruction(pda_id: String, base_bump: u8)]
pub struct PullStrings<'info> {
    #[account(mut, seeds = [pda_id.as_bytes()[..18].as_ref(), pda_id.as_bytes()[18..].as_ref()],bump = base_bump, seeds::program = puppet_program.key())]
    pub puppet_account: Account<'info, Data>,
    pub puppet_program: Program<'info, PuppetCpi>,
}

Puppet program

#[program]
pub mod puppet_cpi {
    use super::*;

    pub fn initialize(ctx: Context<Initialize>, _pda_id: String, authority: Pubkey) -> Result<()> {
        ctx.accounts.puppet.authority = authority;
        Ok(())
    }

    pub fn set_data(ctx: Context<SetData>, _pda_id: String, _base_bump: u8, data: u16) -> Result<()> {

        let puppet = &mut ctx.accounts.puppet;
        puppet.data = data;

        Ok(())
    }

}

#[derive(Accounts)]
#[instruction(pda_id: String)]
pub struct Initialize<'info> {
    #[account(init, payer = user, seeds = [pda_id.as_bytes()[..18].as_ref(), pda_id.as_bytes()[18..].as_ref()],bump, space = 9000)]
    pub puppet: Account<'info, Data>,
    #[account(mut)]
    pub user: Signer<'info>,
    pub system_program: Program<'info, System>
}
#[derive(Accounts)]
#[instruction(pda_id: String, base_bump: u8)]
pub struct SetData<'info> {
    #[account(mut, seeds = [pda_id.as_bytes()[..18].as_ref(), pda_id.as_bytes()[18..].as_ref()],bump = base_bump)]
    pub puppet: Account<'info, Data>,
}

#[account]
pub struct Data{
    pub data: u16,
    pub authority: Pubkey
}

And the test on the client side does:

  it("funds the user", async () => {
    await provider.connection.confirmTransaction(
      await provider.connection.requestAirdrop(admin.publicKey, 10000000000),
      "confirmed"
    );

    const adminUserBalance = await provider.connection.getBalance(
      admin.publicKey
    );

    assert.strictEqual(10000000000, adminUserBalance);
  });

  it("Initialize the pupper", async () => {
    const [puppetPDA, puppetBump] =
      await anchor.web3.PublicKey.findProgramAddress(
        [
          Buffer.from(pdaId.substring(0, 18)),
          Buffer.from(pdaId.substring(18, 36)),
        ],
        puppet.programId
      );

      const tx = await puppet.methods.initialize(pdaId, admin.publicKey).accounts({
        puppet: puppetPDA,
        user: admin.publicKey,
        systemProgram: anchor.web3.SystemProgram.programId
      }).signers([admin]).rpc();
    
  });

  it("make a cpi call", async() => {

    const [puppetPDA, puppetBump] =
      await anchor.web3.PublicKey.findProgramAddress(
        [
          Buffer.from(pdaId.substring(0, 18)),
          Buffer.from(pdaId.substring(18, 36)),
        ],
        puppet.programId
      );

    const tx = await master.methods.pullStrings(pdaId, puppetBump, new anchor.BN(10)).accounts({
      puppetAccount: puppetPDA,
      puppetProgram: puppet.programId
    }).rpc();

    console.log(tx)


  })

And finally the error

  puppet_cpi
    ✔ funds the user (304ms)
    ✔ Initialize the pupper (471ms)
    1) make a cpi call


  2 passing (872ms)
  1 failing

  1) puppet_cpi
       make a cpi call:
     Error: failed to send transaction: Transaction simulation failed: Error processing Instruction 0: Program failed to complete
      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:286:21)
      at Provider.send (node_modules/@project-serum/anchor/src/provider.ts:120:14)
      at MethodsBuilder.rpc [as _rpcFn] (node_modules/@project-serum/anchor/src/program/namespace/rpc.ts:24:16)

The repo/branch is https://github.com/dhruvja/test-cpi-with-pda/tree/testing_uid_with_cpi

12
  • fwiw the error referenced generally means the contract panicked. not sure if that helps. sorry i don't have much anchor knowledge so can't really provide any other advice regarding which of those methods might panic on unexpected inputs
    – trent.sol
    Commented Jul 13, 2022 at 18:49
  • @trent.sol thanks! It's difficult to understand why will this be panicking. It's a very simple operation, and it does not panic on another type of input (same length and type!)
    – blasrodri
    Commented Jul 13, 2022 at 20:41
  • Try updating your anchor_lang version in each Cargo.toml to > 0.24.2 I just pulled down your repo and got the same errors as you but once I changed anchor_lang to 0.25 it went away
    – Joe C
    Commented Jul 13, 2022 at 21:27
  • They pulled anything older than 0.24.2 off of crates.io
    – Joe C
    Commented Jul 13, 2022 at 21:27
  • 1
    I added a .catch(err) for promise in TS test and got more logs for why this might be failing, Error log is here, Main thing is Program failed to complete: Could not create program address with signer seeds: Provided seeds do not result in a valid address
    – Ayush
    Commented Jul 15, 2022 at 10:09

2 Answers 2

3

The program is panicking because it Could not create program address with signer seeds: Provided seeds do not result in a valid address . This means that the PDA lies on the curve. This is happening because on the client side, you are deriving the seeds and bump using the puppet program id and then sending the bump to the program. But on the program, you are deriving the seeds using the master program id. Hence it is failing for PDAs which are lying on the curve.

The solution can be to derive the PDA using the master program id and then sending that bump instead of the puppet bump. Something like this below

it("make a cpi call", async() => {

    const [puppetPDA, puppetBump] =
      await anchor.web3.PublicKey.findProgramAddress(
        [
          Buffer.from(pdaId.substring(0, 18)),
          Buffer.from(pdaId.substring(18, 36)),
        ],
        puppet.programId
      );

    const [masterPDA, masterBump] =
      await anchor.web3.PublicKey.findProgramAddress(
        [
          Buffer.from(pdaId.substring(0, 18)),
          Buffer.from(pdaId.substring(18, 36)),
        ],
        master.programId
      );
   


    const tx = await master.methods.pullStrings(pdaId, puppetBump, masterBump, new anchor.BN(10)).accounts({
      puppetAccount: puppetPDA,
      puppetProgram: puppet.programId
    }).rpc();

    console.log(tx)


  }) 

The above code would work since you are passing the master bump along with puppet bump. But this time using the master bump to derive the signer wouldn't cause an error since the PDA would never lie on the curve.

3

The issue here was that the master program wasn't owning the PDA. Hence, the CPI with seeds (and program ID) would in some cases yield a PDA that wasn't outside of the curve. And hence the runtime panic.

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.