Skip to content

Latest commit

 

History

History
219 lines (186 loc) · 5.78 KB

4_add_init_client_instruction.md

File metadata and controls

219 lines (186 loc) · 5.78 KB

Add init_client Instruction

$ git checkout tags/4

In programs/vrf-client/src/lib.rs, add the seed we'll use to derive our client PDA pubkey, the VrfClientState struct, and some errors we'll be using.

const STATE_SEED: &[u8] = b"CLIENTSEED";

#[repr(packed)]
#[account(zero_copy)]
#[derive(Default)]
pub struct VrfClientState {
    pub bump: u8,
    pub max_result: u64,
    pub result_buffer: [u8; 32],
    pub result: u128,
    pub timestamp: i64,
    pub vrf: Pubkey,
}

add some errors we'll use

#[error_code]
#[derive(Eq, PartialEq)]
pub enum VrfClientErrorCode {
    #[msg("Switchboard VRF Account's authority should be set to the client's state pubkey")]
    InvalidVrfAuthorityError,
    #[msg("The max result must not exceed u64")]
    MaxResultExceedsMaximum,
}

then add an AnchorEvent we'll trigger when a new VRF Client is created

#[event]
pub struct VrfClientCreated {
    pub vrf_client: Pubkey,
    pub max_result: u64,
    pub timestamp: i64,
}

In programs/vrf-client/src/actions/init_client.rs, add the following code for the init_client instruction.

use crate::*;

#[derive(Accounts)]
#[instruction(params: InitClientParams)]
pub struct InitClient<'info> {
    #[account(
        init,
        seeds = [
            STATE_SEED,
            vrf.key().as_ref()
        ],
        payer = payer,
        space = 8 + std::mem::size_of::<VrfClientState>(),
        bump,
    )]
    pub state: AccountLoader<'info, VrfClientState>,
    #[account(
        constraint = vrf.load()?.authority == state.key() @ VrfClientErrorCode::InvalidVrfAuthorityError
    )]
    pub vrf: AccountLoader<'info, VrfAccountData>,
    #[account(mut)]
    pub payer: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
pub struct InitClientParams {
    pub max_result: u64,
}

impl InitClient<'_>  {
    pub fn validate(&self, _ctx: &Context<Self>, params: &InitClientParams) -> Result<()> {
        msg!("init_client validate");
        if params.max_result > 1337 {
            return Err(error!(VrfClientErrorCode::MaxResultExceedsMaximum));
        }

        Ok(())
    }

    pub fn actuate(ctx: &Context<Self>, params: &InitClientParams) -> Result<()> {
        msg!("init_client actuate");

        let mut state = ctx.accounts.state.load_init()?;
        *state = VrfClientState::default();
        state.bump = ctx.bumps.get("state").unwrap().clone();
        state.vrf = ctx.accounts.vrf.key();

        if params.max_result == 0 {
            state.max_result = 1337;
        } else {
            state.max_result = params.max_result;
        }

        emit!(VrfClientCreated{
            vrf_client: ctx.accounts.state.key(),
            max_result: params.max_result,
            timestamp: clock::Clock::get().unwrap().unix_timestamp
        });

        Ok(())
    }
}

Update the init_client test. In the before hook, lets await our oracle before starting any tests.

  before(async () => {
    switchboard = await SwitchboardTestContext.loadFromEnv(
      program.provider as anchor.AnchorProvider,
      undefined,
      5_000_000 // .005 wSOL
    );
+     await switchboard.oracleHeartbeat();
    const queueData = await switchboard.queue.loadData();
    console.log(`oracleQueue: ${switchboard.queue.publicKey}`);
    console.log(
      `unpermissionedVrfEnabled: ${queueData.unpermissionedVrfEnabled}`
    );
    console.log(`# of oracles heartbeating: ${queueData.queue.length}`);
    console.log(
      "\x1b[32m%s\x1b[0m",
      `\u2714 Switchboard localnet environment loaded successfully\n`
    );
  });

Then update the init_client test.

it("init_client", async () => {
  const { unpermissionedVrfEnabled, authority, dataBuffer } =
    await switchboard.queue.loadData();

  const vrfKeypair = anchor.web3.Keypair.generate();

  // find PDA used for our client state pubkey
  [vrfClientKey, vrfClientBump] = anchor.utils.publicKey.findProgramAddressSync(
    [Buffer.from("CLIENTSEED"), vrfKeypair.publicKey.toBytes()],
    program.programId
  );

  const vrfAccount = await sbv2.VrfAccount.create(switchboard.program, {
    keypair: vrfKeypair,
    authority: vrfClientKey,
    queue: switchboard.queue,
    // Useless, will update when consume_randomness instruction is created
    callback: {
      programId: program.programId,
      accounts: [],
      ixData: Buffer.from(""),
    },
  });
  console.log(`Created VRF Account: ${vrfAccount.publicKey}`);
  const permissionAccount = await sbv2.PermissionAccount.create(
    switchboard.program,
    {
      authority,
      granter: switchboard.queue.publicKey,
      grantee: vrfAccount.publicKey,
    }
  );
  console.log(`Created Permission Account: ${permissionAccount.publicKey}`);

  // If queue requires permissions to use VRF, check the correct authority was provided
  if (!unpermissionedVrfEnabled) {
    if (!payer.publicKey.equals(authority)) {
      throw new Error(
        `queue requires PERMIT_VRF_REQUESTS and wrong queue authority provided`
      );
    }

    await permissionAccount.set({
      authority: payer,
      permission: sbv2.SwitchboardPermission.PERMIT_VRF_REQUESTS,
      enable: true,
    });
    console.log(`Set VRF Permissions`);
  }

  const tx = await program.methods
    .initClient({
      maxResult: new anchor.BN(1337),
    })
    .accounts({
      state: vrfClientKey,
      vrf: vrfAccount.publicKey,
      payer: payer.publicKey,
      systemProgram: anchor.web3.SystemProgram.programId,
    })
    .rpc();
  console.log("init_client transaction signature", tx);
});

Now run the test

sbv2 solana anchor test --keypair ~/.config/solana/id.json

Optionally, add -s to suppress the Switchboard oracle logs