-
Notifications
You must be signed in to change notification settings - Fork 3
Add description account comparison lib.rs #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: tilo/add-description/program-examples/account-comparison
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,4 +1,6 @@ | ||||||
| // Import Anchor framework for Solana program development | ||||||
| use anchor_lang::prelude::*; | ||||||
| // Import Light SDK components for ZK Compression functionality | ||||||
| use light_sdk::{ | ||||||
| account::LightAccount, | ||||||
| address::v1::derive_address, | ||||||
|
|
@@ -8,14 +10,18 @@ use light_sdk::{ | |||||
| LightDiscriminator, LightHasher, | ||||||
| }; | ||||||
|
|
||||||
| // Define custom error codes for the program | ||||||
| #[error_code] | ||||||
| pub enum CustomError { | ||||||
| #[msg("No authority to perform this action")] | ||||||
| Unauthorized, | ||||||
| } | ||||||
|
|
||||||
| // Declare the program ID - this uniquely identifies the program on Solana | ||||||
| declare_id!("FYX4GmKJYzSiycc7XZKf12NGXNE9siSx1cJubYJniHcv"); | ||||||
|
|
||||||
| // Derive a CPI signer for Light system program interactions | ||||||
| // This is used to sign transactions when calling the Light system program | ||||||
| const CPI_SIGNER: CpiSigner = | ||||||
| derive_light_cpi_signer!("FYX4GmKJYzSiycc7XZKf12NGXNE9siSx1cJubYJniHcv"); | ||||||
|
|
||||||
|
|
@@ -25,34 +31,48 @@ pub mod account_comparison { | |||||
|
|
||||||
| use super::*; | ||||||
|
|
||||||
| // Create a regular Solana account (stores data on-chain) | ||||||
| // This function demonstrates traditional account creation with rent costs | ||||||
| pub fn create_account(ctx: Context<CreateAccount>, name: String) -> Result<()> { | ||||||
| // Get mutable reference to the account being created | ||||||
| let account = &mut ctx.accounts.account; | ||||||
| // Initialize account data with default values | ||||||
| account.data = [1; 128]; | ||||||
| account.name = name; | ||||||
| // Set the account owner to the user who created it | ||||||
| account.user = *ctx.accounts.user.key; | ||||||
|
|
||||||
| Ok(()) | ||||||
| } | ||||||
|
|
||||||
| // Update data in a regular Solana account | ||||||
| // This function modifies existing account data on-chain | ||||||
| pub fn update_data(ctx: Context<UpdateData>, data: [u8; 128]) -> Result<()> { | ||||||
| // Get mutable reference to the account and update its data | ||||||
| let account = &mut ctx.accounts.account; | ||||||
| account.data = data; | ||||||
| Ok(()) | ||||||
| } | ||||||
|
|
||||||
| // Create a compressed account using ZK Compression | ||||||
| // This stores account data off-chain with cryptographic proofs for verification | ||||||
| pub fn create_compressed_account<'info>( | ||||||
| ctx: Context<'_, '_, '_, 'info, CreateCompressedAccount<'info>>, | ||||||
| name: String, | ||||||
| proof: ValidityProof, | ||||||
| address_tree_info: PackedAddressTreeInfo, | ||||||
| output_tree_index: u8, | ||||||
| proof: ValidityProof, // Zero-knowledge proof for state transitions | ||||||
| address_tree_info: PackedAddressTreeInfo, // Information about the address tree | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. contains indices that point to the address Merkle tree in remaining accounts, and other metadata to create the address. root index is used to get the root for proof verification from the address Merkle tree account but that is not directly exposed here so can be omitted. |
||||||
| output_tree_index: u8, // Index in the output state tree | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| ) -> Result<()> { | ||||||
| // Set up CPI accounts for calling the Light system program | ||||||
| // This includes the user account and remaining accounts for tree operations | ||||||
|
Comment on lines
+66
to
+67
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The user account is the fee payer, the remaining accounts need to contain program Ids, config accounts, and tree accounts. |
||||||
| let light_cpi_accounts = CpiAccounts::new( | ||||||
| ctx.accounts.user.as_ref(), | ||||||
| ctx.remaining_accounts, | ||||||
| CPI_SIGNER, | ||||||
| ); | ||||||
|
|
||||||
| // Derive a deterministic address for the compressed account | ||||||
| // Uses a seed based on the user's public key to ensure uniqueness | ||||||
|
Comment on lines
+74
to
+75
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is not descriptive enough, we should assume that the reader knows what a pda is, we should highlight that it is similar but different with some level of detail. |
||||||
| let (address, address_seed) = derive_address( | ||||||
| &[b"account", ctx.accounts.user.key().as_ref()], | ||||||
| &address_tree_info | ||||||
|
|
@@ -61,47 +81,57 @@ pub mod account_comparison { | |||||
| &crate::ID, | ||||||
| ); | ||||||
|
|
||||||
| // LightAccount::new_init will create an account with empty output state (no input state). | ||||||
| // Modifying the account will modify the output state that when converted to_account_info() | ||||||
| // is hashed with poseidon hashes, serialized with borsh | ||||||
| // and created with invoke_light_system_program by invoking the light-system-program. | ||||||
| // The hashing scheme is the account structure derived with LightHasher. | ||||||
| // Create a new compressed account with empty input state | ||||||
| // LightAccount::new_init initializes an account for creation | ||||||
| // The account data will be hashed using Poseidon hashes and stored off-chain | ||||||
|
Comment on lines
+84
to
+86
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. input state is too much detail |
||||||
| let mut compressed_account = LightAccount::<'_, CompressedAccountData>::new_init( | ||||||
| &crate::ID, | ||||||
| Some(address), | ||||||
| output_tree_index, | ||||||
| &crate::ID, // Program ID that owns this account | ||||||
| Some(address), // Derived address for the account | ||||||
| output_tree_index, // Position in the state tree | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
The actual position in the tree, the leaf index, is set in the light system program. |
||||||
| ); | ||||||
|
|
||||||
| // Set the compressed account data | ||||||
| compressed_account.user = ctx.accounts.user.key(); | ||||||
| compressed_account.name = name; | ||||||
| compressed_account.data = [1u8; 128]; | ||||||
|
|
||||||
| // Prepare address parameters for the new account creation | ||||||
| let new_address_params = address_tree_info.into_new_address_params_packed(address_seed); | ||||||
|
|
||||||
| // Create CPI inputs with the validity proof and account information | ||||||
| // This includes the compressed account converted to account info format | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this doesn't say anything useful
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the comment should describe what this piece of code actually does such as creating the data hash for the compressed account (double check where that actually happens). |
||||||
| let cpi = CpiInputs::new_with_address( | ||||||
| proof, | ||||||
| vec![compressed_account | ||||||
| .to_account_info() | ||||||
| .map_err(ProgramError::from)?], | ||||||
| vec![new_address_params], | ||||||
| ); | ||||||
|
|
||||||
| // Invoke the Light system program to create the compressed account | ||||||
| // This updates the state trees and stores the commitment on-chain | ||||||
| cpi.invoke_light_system_program(light_cpi_accounts) | ||||||
| .map_err(ProgramError::from)?; | ||||||
|
|
||||||
| Ok(()) | ||||||
| } | ||||||
|
|
||||||
| // Update an existing compressed account | ||||||
| // This function demonstrates how to modify compressed account data | ||||||
| pub fn update_compressed_account<'info>( | ||||||
| ctx: Context<'_, '_, '_, 'info, UpdateCompressedAccount<'info>>, | ||||||
| new_data: [u8; 128], | ||||||
| existing_data: [u8; 128], | ||||||
| name: String, | ||||||
| proof: ValidityProof, | ||||||
| account_meta: CompressedAccountMeta, | ||||||
| new_data: [u8; 128], // New data to store in the account | ||||||
| existing_data: [u8; 128], // Current data (for verification) | ||||||
| name: String, // Account name | ||||||
| proof: ValidityProof, // Proof that the update is valid | ||||||
| account_meta: CompressedAccountMeta, // Metadata about the compressed account | ||||||
| ) -> Result<()> { | ||||||
| // Create a mutable reference to the existing compressed account | ||||||
| // new_mut loads an existing account for modification | ||||||
| let mut compressed_account = LightAccount::<'_, CompressedAccountData>::new_mut( | ||||||
| &crate::ID, | ||||||
| &account_meta, | ||||||
| // Provide the current account data for verification | ||||||
| CompressedAccountData { | ||||||
| user: ctx.accounts.user.key(), | ||||||
| data: existing_data, | ||||||
|
|
@@ -110,25 +140,31 @@ pub mod account_comparison { | |||||
| ) | ||||||
| .map_err(ProgramError::from)?; | ||||||
|
|
||||||
| // Verify that the user has authority to update this account | ||||||
| if compressed_account.user != ctx.accounts.user.key() { | ||||||
| return err!(CustomError::Unauthorized); | ||||||
| } | ||||||
|
|
||||||
| // Update the account data | ||||||
| compressed_account.data = new_data; | ||||||
|
|
||||||
| // Set up CPI accounts for the Light system program call | ||||||
| let light_cpi_accounts = CpiAccounts::new( | ||||||
| ctx.accounts.user.as_ref(), | ||||||
| ctx.remaining_accounts, | ||||||
| CPI_SIGNER, | ||||||
| ); | ||||||
|
|
||||||
| // Create CPI inputs with the validity proof and updated account | ||||||
| let cpi_inputs = CpiInputs::new( | ||||||
| proof, | ||||||
| vec![compressed_account | ||||||
| .to_account_info() | ||||||
| .map_err(ProgramError::from)?], | ||||||
| ); | ||||||
|
|
||||||
| // Invoke the Light system program to update the compressed account | ||||||
| // This creates a new state commitment and nullifies the old one | ||||||
| cpi_inputs | ||||||
| .invoke_light_system_program(light_cpi_accounts) | ||||||
| .map_err(ProgramError::from)?; | ||||||
|
|
@@ -137,54 +173,68 @@ pub mod account_comparison { | |||||
| } | ||||||
| } | ||||||
|
|
||||||
| // Account validation struct for creating regular Solana accounts | ||||||
| #[derive(Accounts)] | ||||||
| pub struct CreateAccount<'info> { | ||||||
| #[account(mut)] | ||||||
| pub user: Signer<'info>, | ||||||
| pub user: Signer<'info>, // User who pays for and owns the account | ||||||
| // Initialize a new account with: | ||||||
| // - payer: user pays the rent | ||||||
| // - space: 8 bytes (discriminator) + 32 (pubkey) + 128 (data) + 64 (string) | ||||||
| // - seeds: deterministic address based on user's pubkey | ||||||
| #[account(init, payer = user, space = 8 + 32 + 128 + 64, seeds = [b"account", user.key().as_ref()], bump)] | ||||||
| pub account: Account<'info, AccountData>, | ||||||
| pub system_program: Program<'info, System>, | ||||||
| pub system_program: Program<'info, System>, // System program for account creation | ||||||
| } | ||||||
|
|
||||||
| /// [0..8, 8..40,40..168,168..232] | ||||||
| // Data structure for regular Solana accounts | ||||||
| // Memory layout: [0..8] discriminator, [8..40] user pubkey, [40..168] name, [168..232] data | ||||||
| #[account] | ||||||
| #[derive(Debug)] | ||||||
| pub struct AccountData { | ||||||
| pub user: Pubkey, | ||||||
| pub name: String, | ||||||
| pub data: [u8; 128], | ||||||
| pub user: Pubkey, // Owner of the account (32 bytes) | ||||||
| pub name: String, // Account name (variable length, up to 64 bytes) | ||||||
| pub data: [u8; 128], // Account data (128 bytes) | ||||||
| } | ||||||
|
|
||||||
| // Account validation struct for updating regular accounts | ||||||
| #[derive(Accounts)] | ||||||
| pub struct UpdateData<'info> { | ||||||
| #[account(mut, has_one = user)] | ||||||
| #[account(mut, has_one = user)] // Account must be owned by the user | ||||||
| pub account: Account<'info, AccountData>, | ||||||
| #[account(mut)] | ||||||
| pub user: Signer<'info>, | ||||||
| pub user: Signer<'info>, // User must sign the transaction | ||||||
| } | ||||||
|
|
||||||
| // Account validation struct for creating compressed accounts | ||||||
| // Note: Much simpler than regular accounts - no space allocation needed | ||||||
| #[derive(Accounts)] | ||||||
| pub struct CreateCompressedAccount<'info> { | ||||||
| #[account(mut)] | ||||||
| pub user: Signer<'info>, | ||||||
| pub user: Signer<'info>, // Only need the user to sign | ||||||
| } | ||||||
|
|
||||||
| // Account validation struct for updating compressed accounts | ||||||
| #[derive(Accounts)] | ||||||
| pub struct UpdateCompressedAccount<'info> { | ||||||
| #[account(mut)] | ||||||
| pub user: Signer<'info>, | ||||||
| pub user: Signer<'info>, // Only need the user to sign | ||||||
| } | ||||||
|
|
||||||
| // Data structure for compressed accounts | ||||||
| // This struct is serialized and hashed using Poseidon hashes | ||||||
| // The #[hash] attribute marks fields that are included in the account hash | ||||||
| #[derive(Clone, Debug, AnchorDeserialize, AnchorSerialize, LightDiscriminator, LightHasher)] | ||||||
| pub struct CompressedAccountData { | ||||||
| #[hash] | ||||||
| pub user: Pubkey, | ||||||
| pub user: Pubkey, // Owner of the account (included in hash) | ||||||
| #[hash] | ||||||
| pub name: String, | ||||||
| pub name: String, // Account name (included in hash) | ||||||
| #[hash] | ||||||
| pub data: [u8; 128], | ||||||
| pub data: [u8; 128], // Account data (included in hash) | ||||||
| } | ||||||
|
|
||||||
| // Default implementation for compressed account data | ||||||
| impl Default for CompressedAccountData { | ||||||
| fn default() -> Self { | ||||||
| Self { | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.