1

I have the following Rust code that structured a Solana account and test cases from another file using typescript:

const DISCRIMINATOR_LENGTH: usize = 8;
const PUBLIC_KEY_LENGTH: usize = 32;
const TIMESTAMP_LENGTH: usize = 8;
const STRING_LENGTH_PREFIX: usize = 4; // Stores the size of the string.
const MAX_FIRST_NAME_LENGTH: usize = 30 * 4; // 30 chars max.
const MAX_LAST_NAME_LENGTH: usize = 30 * 4; // 30 chars max.
const MAX_EMAIL_LENGTH: usize = 20 * 4; // 20 chars max.
const MAX_PHONE_LENGTH: usize = 10 * 4;//10 char max

#[account]
pub struct Contact {
    pub author: Pubkey,
    pub timestamp: i64,
    pub first_name: String,
    pub last_name: String,
    pub email: String,
    pub phone: String,
}

impl Contact {
    const LEN: usize = DISCRIMINATOR_LENGTH
        + PUBLIC_KEY_LENGTH // Author.
        + TIMESTAMP_LENGTH // Timestamp.
        + STRING_LENGTH_PREFIX + MAX_FIRST_NAME_LENGTH // first Name.
        + STRING_LENGTH_PREFIX + MAX_LAST_NAME_LENGTH // last Name.
        + STRING_LENGTH_PREFIX + MAX_EMAIL_LENGTH // Email.
        + STRING_LENGTH_PREFIX + MAX_PHONE_LENGTH; //phone
}

 it('can filter tweets by first name', async () => {
    //const author = program.provider.wallet.publicKey;
    const name = bs58.encode(Buffer.from('Peter'));
    const contactAccounts = await program.account.contact.all([
      {
        memcmp: {
          offset: 52,
          bytes: name,
          }
        
      }
    ]);
    assert.equal(contactAccounts.length, 1);
    assert.ok(contactAccounts.every(contactAccount => {
      return contactAccount.account.firstName==='Peter'
    }))
  });

Finding the first name at the 52'th location is working but when I try to find the last name location @ 176 bytes (52+120+4) the test case can't find any account.

What would be the exact location of the last name, or email? I have a test case that created Peter and Jordan as first and last names respectively.

1 Answer 1

2

Referencing one of the answers in this question: How the scope for memcmp works when the struct has multiple strings?

Seems like you would need to add padding for any unused space:

You would have to make title a fixed length in order for the offset to work in this way. e.g. title: [u8; 10] Then just pad the end of the title string with empty space if it doesn't fill up the full fixed length.

You can try something like this (referenced Candy Machine Program: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/candy-machine/program/src/processor/initialize.rs#L62):

use anchor_lang::prelude::*;

const DISCRIMINATOR_LENGTH: usize = 8;
const STRING_LENGTH_PREFIX: usize = 4;
const MAX_FIRST_NAME_LENGTH: usize = 30 * 4;
const MAX_LAST_NAME_LENGTH: usize = 30 * 4;

declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");

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

    pub fn initialize(
        ctx: Context<Initialize>,
        first_name: String,
        last_name: String,
    ) -> Result<()> {
        let data = &mut ctx.accounts.data;

        let mut array_of_zeroes = vec![0u8; MAX_FIRST_NAME_LENGTH - first_name.len()];
        let new_first_name = first_name + std::str::from_utf8(&array_of_zeroes).unwrap();
        data.first_name = new_first_name;

        array_of_zeroes = vec![0u8; MAX_LAST_NAME_LENGTH - last_name.len()];
        let new_last_name = last_name + std::str::from_utf8(&array_of_zeroes).unwrap();
        data.last_name = new_last_name;

        Ok(())
    }
}

#[derive(Accounts)]
#[instruction(first_name: String, last_name: String)]
pub struct Initialize<'info> {
    #[account(
        init,
        seeds = [first_name.as_bytes(), last_name.as_bytes()],
        bump,
        payer = payer,
        space = FullName::LEN
    )]
    pub data: Account<'info, FullName>,
    #[account(mut)]
    pub payer: Signer<'info>,
    pub system_program: Program<'info, System>,
    pub rent: Sysvar<'info, Rent>,
}

#[account]
pub struct FullName {
    pub first_name: String,
    pub last_name: String,
}

impl FullName {
    const LEN: usize = DISCRIMINATOR_LENGTH
        + STRING_LENGTH_PREFIX
        + MAX_FIRST_NAME_LENGTH
        + STRING_LENGTH_PREFIX
        + MAX_LAST_NAME_LENGTH;
}

Which seems to work with memcmp

describe("anchor-memcmp", () => {
  // Configure the client to use the local cluster.
  anchor.setProvider(anchor.AnchorProvider.env())

  const program = anchor.workspace.AnchorMemcmp as Program<AnchorMemcmp>

  it("Is initialized!", async () => {
    const tx = await program.methods.initialize("First", "Last").rpc()
  })

  it("First String", async () => {
    const name = bs58.encode(Buffer.from("F"))
    const accounts = await program.account.fullName.all([
      {
        memcmp: {
          offset: 8 + 4,
          bytes: name,
        },
      },
    ])
    console.log(accounts)
  })

  it("Second String", async () => {
    const name = bs58.encode(Buffer.from("L"))
    const accounts = await program.account.fullName.all([
      {
        memcmp: {
          offset: 8 + 4 + 120 + 4,
          bytes: name,
        },
      },
    ])
    console.log(accounts)
  })
})

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.