2

I'd like to add a Vec<String> argument to an instruction handler that saves the Vec<String> to a PDA in an Anchor program:

// What we will put inside the Favorites PDA
#[account]
#[derive(InitSpace)]
pub struct Favorites {
    ...

    pub hobbies: Vec<String>
}

When I run anchor test

error: Expected max_len attribute.
  --> programs/favorites/src/lib.rs:19:18
   |
19 |     pub hobbies: Vec<String>
   |                  ^^^

Fair enough. If I change it to:

#[max_len(200)]
pub hobbies: Vec<String>

I receive:

error: The number of lengths are invalid.
  --> programs/favorites/src/lib.rs:20:22
   |
20 |     pub hobbies: Vec<String>
   |                      ^^^^^^

What does The number of lengths are invalid. mean?

What is the correct way to add a Vec<String> to an Anchor PDA?

2 Answers 2

3

What does The number of lengths are invalid. mean?

This error means you need to provide the maximum size of the Vector and the maximum size of the strings inside. Replace #[max_len(200)] with something like #[max_len(4, 50)] for 4 items of 50 characters each.

From the Anchor Source:

For types that have a variable size like String and Vec, it is necessary to indicate the size by the max_len attribute. For nested types, it is necessary to specify a size for each variable type (see example).

Here is a basic example on Solana Playground that stores Vec<String> on a new account.

use anchor_lang::prelude::*;

declare_id!("8RC6C92G1NrMogQy2VZ1GtXzMkeBQGYsAfF7D53jPtBR");

#[program]
mod hello_anchor {
    use super::*;
    pub fn initialize(ctx: Context<Initialize>, data: Vec<String>) -> Result<()> {
        msg!("data: {:?}!", data);
        ctx.accounts.new_account.data = data;
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(mut)]
    pub user: Signer<'info>,

    #[account(
        init, 
        payer = user, 
        space = ANCHOR_DISCRIMINATOR_SIZE + DataAccount::INIT_SPACE, 
    )]
    pub new_account: Account<'info, DataAccount>,
    pub system_program: Program<'info, System>,
}

#[constant]
pub const ANCHOR_DISCRIMINATOR_SIZE: usize = 8;

#[account]
#[derive(InitSpace)]
pub struct DataAccount {
    #[max_len(5, 5)]
    pub data: Vec<String>,
}
0

The length of variables has to be known at compile time, so if you want a string with a max length of 200, your best bet is to use a slice like some_str: [u8; 256], and convert it to a str manually (adjust your length depending on the encoding you want to support, UTF-8 is 1-4 bytes per character so 256 is a minimum of 64 chars)

On the front end send this with something like Buffer.from("blah blah", "utf-8"), then pad the rest of the slice with 0s.

5
  • "if you want a string with a max length of 200". I don't. The variable in this case is a Vec<String>, ie not a string with a with a max length of 200 bytes, but an array of strings with a max length of 200 bytes.
    – mikemaccana
    Apr 2 at 20:05
  • Same deal, you want a slice of slices, pick the max length of the String and the max number of Strings to be passed. In practice you are generally limited by the size of the tx anyways (about 1200 bytes including accounts).
    – Whiteseal
    Apr 2 at 21:06
  • So the whole length of the Vec<Sring> is max length of string x max number of strings? If you do have an answer to the question (storing a Vec<string> not a string per your current answer) can you add code to show this to your answer? If you do answer the question I'll then mod this up and mark it as the accepted answer
    – mikemaccana
    Apr 3 at 14:20
  • I would do a slice of slices, i.e for 20 strings of 256 bytes max each:, store: some_strs: [[u8; 256]; 20]. You can decode this into strings at runtime with something like let str_bytes = vec![some_strs[0]]; (note you'll probably have to trim zero padding) let some_str= str::from_utf8(&str_bytes).unwrap();
    – Whiteseal
    Apr 4 at 17:56
  • 1
    Vec<String> just works provided I set #[max_len()] with two numbers (the vector size and the string size), and I don't have to do any padding to send the strings - my TS tests looks like .setFavorites(favoriteNumber, favoriteColor, favoriteHobbies) where favoriteHobbies is a TS Array<string>. So I've accepted the other answer as it seems cleaner.
    – mikemaccana
    Apr 5 at 20:54

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.