0

I'm storing a vector of u16 user IDs. I decided not to store public keys in vector, which I can use as ids or get rid of ids completely instead, however public keys are 32 bytes and will have limited space being stored as vector attribute in an account.

I'm creating a lottery program that stores all user IDs who enter, and then assigns some random winner IDs in a separate instruction. This is the Lottery account:

#[account]
pub struct Lottery {
    pub authority: Pubkey,
    pub all_user_ids: Option<Vec<u16>>,
    pub winner_ids: Option<Vec<u16>>,
}

For my user account, I'm doing creating it this:

#[derive(Accounts)]
pub struct CreateUser<'info> {
    #[account(
        init,
        payer = authority,
        space = 8 + std::mem::size_of::<UserAccount>(),
        seeds = ["user".as_bytes(), authority.key().as_ref()],
        bump,
    )]
    pub user_account: Account<'info, UserAccount>,
    #[account(mut,)]
    pub authority: Signer<'info>,
    pub system_program: Program<'info, System>,
}

pub fn handler(ctx: Context<CreateUser>, user_id: u16) -> Result<()> {
    let user_account = &mut ctx.accounts.user_account;
    user_account.id = user_id;

    msg!("Created User: {}", user_account.id);
    Ok(())
}

The issue is, I'm dependent on client to pass a user ID here, and there is no way to ensure that the user Id is unique. I want to keep it as small as u16. My own client can use different methods to ensure it so that it only sends incremented and unique Ids when creating new accounts for users, but other malicious attacker may call the program instruction from their separate client and add duplicate ids. What can I do here?

I don't want other methods like using zero copy to increase account sound to directly store public keys or anything else. Since I want to keep things simple here, and storing user Ids as u16 vector is easiest method here, just need one solution to a problem.

1 Answer 1

1

There are two ways I can think about tackling this issue:

1) Does a user's user ID actually matter for anything else or is it just to keep track of users? If my user ID does not matter outside the contract, simply create a counter struct with a u16. Each time a user enters the lottery, use the current counter value, then increment it.

  • So, the first person to enter would have ID 0 and the second would have ID 1. The counter ensures all IDs are unique.

  • You can actually then get rid of the Vec of user IDs, since you now have a counter telling you how many users there.

//Add this to account struct and make it a universal account
#[account]
pub struct Counter {
    pub count: u16
}

pub fn handler(ctx: Context<CreateUser>) -> Result<()> {
    let user_account = &mut ctx.accounts.user_account;
    user_account.id = ctx.accounts.counter.count;
    ctx.accounts.counter.count += 1;

    msg!("Created User: {}", user_account.id);
    Ok(())
}

2) Alternatively, if the ID needs to be provided client side, you can switch the PDA seeds for your user account. Inside the account, the user account would hold a pubkey instead of an ID. This would effectively make sure each ID is unique since you are initializing the user account in this instruction. If someone else inputs the same ID, the transaction would revert.

  • This requires minimal code changes, just change the UserAccount struct, the seeds, and change handler to set the pubkey instead of ID.
2
  • User IDs are being stored so that I can select x IDs randomly for lottery. So I can't get rid of the u16 Array. I essentially need to store a "list" of users somehow in the Lottery struct to select random ones from it in the future. And I decided to add u16, only to save space, since 32 bytes of public key will limit the amount of users I can store in Vector. U16 has no other use literally. But I think having one separate account that acts as a counter may work, although I'm not sure if creating literally one PDA to store a global counter sounds ideal
    – Gmer
    Commented Jun 2, 2023 at 4:55
  • Why can't you get rid of the u16 array? With a counter, if 10 people join the Lottery, the counter will hold state value of 10. Now, you can select a random number 0-9 for a winner. I understand using u16 to save space instead of holding a Vec of public keys, but you're still wasting space by storing a Vec. And my counter was an example, you can put the count inside the lottery struct instead of all_user_ids.
    – Varun Siva
    Commented Jun 3, 2023 at 5:15

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.