From 14f15547628bb0983adf6ce13032fdd30cab706f Mon Sep 17 00:00:00 2001 From: Michal Rostecki Date: Fri, 16 Aug 2024 18:57:31 +0200 Subject: [PATCH] feat: Add helpers for each compressed account action Remove the necessity of constructing `InstructionDataInvokeCpi` manually by adding utility functions for each type of singular account action: - **Create** (no inputs, only output) - `new_compressed_account` - creates an output compressed account - `create_cpi_inputs_for_new_account` - **Update** (input with old data, output with new data) - `input_compressed_account` - `output_compressed_accoint` - `create_cpi_inputs_for_account_update` - **Delete** (input with data to delete, no output) - `input_compressed_account` - `create_cpi_inputs_for_account_deletion` While this abstraction doesn't fully hide the UTXO model full yet, it aims to make it clear which kind of inputs and outputs are needed for each action. --- .../programs/name-service/src/lib.rs | 196 +++++------------- .../programs/name-service/tests/test.rs | 26 ++- sdk/src/compressed_account.rs | 143 ++++++++++++- sdk/src/utils.rs | 61 +++++- 4 files changed, 266 insertions(+), 160 deletions(-) diff --git a/examples/name-service/programs/name-service/src/lib.rs b/examples/name-service/programs/name-service/src/lib.rs index 3bb27ffeb3..ef63805020 100644 --- a/examples/name-service/programs/name-service/src/lib.rs +++ b/examples/name-service/programs/name-service/src/lib.rs @@ -2,30 +2,27 @@ use std::net::{Ipv4Addr, Ipv6Addr}; use anchor_lang::{prelude::*, solana_program::hash}; use borsh::{BorshDeserialize, BorshSerialize}; -use light_hasher::{bytes::AsByteVec, DataHasher, Discriminator, Poseidon}; +use light_hasher::bytes::AsByteVec; use light_sdk::{ light_accounts, merkle_context::{PackedAddressMerkleContext, PackedMerkleContext, PackedMerkleOutputContext}, - utils::create_cpi_inputs_for_new_address, + utils::{create_cpi_inputs_for_account_deletion, create_cpi_inputs_for_new_account}, verify::verify, LightDiscriminator, LightHasher, LightTraits, }; -use light_system_program::{ - invoke::processor::CompressedProof, - sdk::{ - address::derive_address, - compressed_account::{ - CompressedAccount, CompressedAccountData, PackedCompressedAccountWithMerkleContext, - }, - CompressedCpiContext, - }, - InstructionDataInvokeCpi, NewAddressParamsPacked, OutputCompressedAccountWithPackedContext, -}; +use light_system_program::{invoke::processor::CompressedProof, sdk::CompressedCpiContext}; declare_id!("7yucc7fL3JGbyMwg4neUaenNSdySS39hbAk89Ao3t1Hz"); #[program] pub mod name_service { + use light_sdk::{ + compressed_account::{ + input_compressed_account, new_compressed_account, output_compressed_account, + }, + utils::create_cpi_inputs_for_account_update, + }; + use super::*; #[allow(clippy::too_many_arguments)] @@ -46,20 +43,21 @@ pub mod name_service { name, rdata, }; - let (compressed_account, new_address_params) = create_compressed_account( - &ctx, + let (compressed_account, new_address_params) = new_compressed_account( &record, - address_seed, + &address_seed, + &crate::ID, &merkle_output_context, &address_merkle_context, address_merkle_tree_root_index, + ctx.remaining_accounts, )?; let signer_seed = b"cpi_signer".as_slice(); let bump = Pubkey::find_program_address(&[signer_seed], &ctx.accounts.self_program.key()).1; let signer_seeds = [signer_seed, &[bump]]; - let inputs = create_cpi_inputs_for_new_address( + let inputs = create_cpi_inputs_for_new_account( proof, new_address_params, compressed_account, @@ -78,12 +76,13 @@ pub mod name_service { proof: CompressedProof, merkle_context: PackedMerkleContext, merkle_tree_root_index: u16, - address: [u8; 32], + address_merkle_context: PackedAddressMerkleContext, name: String, old_rdata: RData, new_rdata: RData, cpi_context: Option, ) -> Result<()> { + let address_seed = hash::hash(name.as_bytes()).to_bytes(); let owner = ctx.accounts.signer.key(); // Re-create the old compressed account. It's needed as an input for @@ -93,11 +92,14 @@ pub mod name_service { name: name.clone(), rdata: old_rdata, }; - let old_compressed_account = compressed_input_account_with_address( - old_record, - address, + let old_compressed_account = input_compressed_account( + &old_record, + &address_seed, + &crate::ID, &merkle_context, merkle_tree_root_index, + &address_merkle_context, + ctx.remaining_accounts, )?; let new_record = NameRecord { @@ -105,24 +107,26 @@ pub mod name_service { name, rdata: new_rdata, }; - let new_compressed_account = - compressed_output_account_with_address(&new_record, address, &merkle_context)?; + let new_compressed_account = output_compressed_account( + &new_record, + &address_seed, + &crate::ID, + &merkle_context, + &address_merkle_context, + ctx.remaining_accounts, + )?; let signer_seed = b"cpi_signer".as_slice(); let bump = Pubkey::find_program_address(&[signer_seed], &ctx.accounts.self_program.key()).1; let signer_seeds = [signer_seed, &[bump]]; - let inputs = InstructionDataInvokeCpi { - proof: Some(proof), - new_address_params: vec![], - input_compressed_accounts_with_merkle_context: vec![old_compressed_account], - output_compressed_accounts: vec![new_compressed_account], - relay_fee: None, - compress_or_decompress_lamports: None, - is_compress: false, - signer_seeds: signer_seeds.iter().map(|seed| seed.to_vec()).collect(), + let inputs = create_cpi_inputs_for_account_update( + proof, + old_compressed_account, + new_compressed_account, + &signer_seeds, cpi_context, - }; + ); verify(ctx, &inputs, &[&signer_seeds])?; @@ -135,38 +139,38 @@ pub mod name_service { proof: CompressedProof, merkle_context: PackedMerkleContext, merkle_tree_root_index: u16, - address: [u8; 32], + address_merkle_context: PackedAddressMerkleContext, name: String, rdata: RData, cpi_context: Option, ) -> Result<()> { + let address_seed = hash::hash(name.as_bytes()).to_bytes(); + let record = NameRecord { owner: ctx.accounts.signer.key(), name, rdata, }; - let compressed_account = compressed_input_account_with_address( - record, - address, + let compressed_account = input_compressed_account( + &record, + &address_seed, + &crate::ID, &merkle_context, merkle_tree_root_index, + &address_merkle_context, + ctx.remaining_accounts, )?; let signer_seed = b"cpi_signer".as_slice(); let bump = Pubkey::find_program_address(&[signer_seed], &ctx.accounts.self_program.key()).1; let signer_seeds = [signer_seed, &[bump]]; - let inputs = InstructionDataInvokeCpi { - proof: Some(proof), - new_address_params: vec![], - input_compressed_accounts_with_merkle_context: vec![compressed_account], - output_compressed_accounts: vec![], - relay_fee: None, - compress_or_decompress_lamports: None, - is_compress: false, - signer_seeds: signer_seeds.iter().map(|seed| seed.to_vec()).collect(), + let inputs = create_cpi_inputs_for_account_deletion( + proof, + compressed_account, + &signer_seeds, cpi_context, - }; + ); verify(ctx, &inputs, &[&signer_seeds])?; @@ -224,101 +228,3 @@ pub struct NameService<'info> { #[authority] pub cpi_signer: AccountInfo<'info>, } - -fn create_compressed_account( - ctx: &Context<'_, '_, '_, '_, NameService<'_>>, - record: &NameRecord, - address_seed: [u8; 32], - merkle_output_context: &PackedMerkleOutputContext, - address_merkle_context: &PackedAddressMerkleContext, - address_merkle_tree_root_index: u16, -) -> Result<( - OutputCompressedAccountWithPackedContext, - NewAddressParamsPacked, -)> { - let data = record.try_to_vec()?; - let data_hash = record.hash::().map_err(ProgramError::from)?; - let compressed_account_data = CompressedAccountData { - discriminator: NameRecord::discriminator(), - data, - data_hash, - }; - let address = derive_address( - &ctx.remaining_accounts[address_merkle_context.address_merkle_tree_pubkey_index as usize] - .key(), - &address_seed, - ) - .map_err(|_| ProgramError::InvalidArgument)?; - let compressed_account = CompressedAccount { - owner: crate::ID, - lamports: 0, - address: Some(address), - data: Some(compressed_account_data), - }; - let compressed_account = OutputCompressedAccountWithPackedContext { - compressed_account, - merkle_tree_index: merkle_output_context.merkle_tree_pubkey_index, - }; - - let new_address_params = NewAddressParamsPacked { - seed: address_seed, - address_merkle_tree_account_index: address_merkle_context.address_merkle_tree_pubkey_index, - address_queue_account_index: address_merkle_context.address_queue_pubkey_index, - address_merkle_tree_root_index, - }; - - Ok((compressed_account, new_address_params)) -} - -fn compressed_input_account_with_address( - record: NameRecord, - address: [u8; 32], - merkle_context: &PackedMerkleContext, - merkle_tree_root_index: u16, -) -> Result { - let data = record.try_to_vec()?; - let data_hash = record.hash::().map_err(ProgramError::from)?; - let compressed_account_data = CompressedAccountData { - discriminator: NameRecord::discriminator(), - data, - data_hash, - }; - let compressed_account = CompressedAccount { - owner: crate::ID, - lamports: 0, - address: Some(address), - data: Some(compressed_account_data), - }; - - Ok(PackedCompressedAccountWithMerkleContext { - compressed_account, - merkle_context: *merkle_context, - root_index: merkle_tree_root_index, - read_only: false, - }) -} - -fn compressed_output_account_with_address( - record: &NameRecord, - address: [u8; 32], - merkle_context: &PackedMerkleContext, -) -> Result { - let data = record.try_to_vec()?; - let data_hash = record.hash::().map_err(ProgramError::from)?; - let compressed_account_data = CompressedAccountData { - discriminator: NameRecord::discriminator(), - data, - data_hash, - }; - let compressed_account = CompressedAccount { - owner: crate::ID, - lamports: 0, - address: Some(address), - data: Some(compressed_account_data), - }; - - Ok(OutputCompressedAccountWithPackedContext { - compressed_account, - merkle_tree_index: merkle_context.merkle_tree_pubkey_index, - }) -} diff --git a/examples/name-service/programs/name-service/tests/test.rs b/examples/name-service/programs/name-service/tests/test.rs index 4b041465b2..436efb3f80 100644 --- a/examples/name-service/programs/name-service/tests/test.rs +++ b/examples/name-service/programs/name-service/tests/test.rs @@ -81,11 +81,11 @@ async fn test_name_service() { update_record( &mut rpc, &mut test_indexer, + &env, &rdata_1, &rdata_2, &payer, compressed_account, - &address, &account_compression_authority, ®istered_program_pda, ) @@ -107,10 +107,10 @@ async fn test_name_service() { delete_record( &mut rpc, &mut test_indexer, + &env, &rdata_2, &payer, compressed_account, - &address, &account_compression_authority, ®istered_program_pda, ) @@ -189,11 +189,11 @@ async fn create_record( async fn update_record( rpc: &mut R, test_indexer: &mut TestIndexer, + env: &EnvAccounts, old_rdata: &RData, new_rdata: &RData, payer: &Keypair, compressed_account: &CompressedAccountWithMerkleContext, - address: &[u8; 32], account_compression_authority: &Pubkey, registered_program_pda: &Pubkey, ) { @@ -215,11 +215,18 @@ async fn update_record( let merkle_context = pack_merkle_context(compressed_account.merkle_context, &mut remaining_accounts); + let address_merkle_context = AddressMerkleContext { + address_merkle_tree_pubkey: env.address_merkle_tree_pubkey, + address_queue_pubkey: env.address_merkle_tree_queue_pubkey, + }; + let address_merkle_context = + pack_address_merkle_context(address_merkle_context, &mut remaining_accounts); + let instruction_data = name_service::instruction::UpdateRecord { proof: rpc_result.proof, merkle_context, merkle_tree_root_index: rpc_result.root_indices[0], - address: *address, + address_merkle_context, name: "example.io".to_string(), old_rdata: old_rdata.clone(), new_rdata: new_rdata.clone(), @@ -253,10 +260,10 @@ async fn update_record( async fn delete_record( rpc: &mut R, test_indexer: &mut TestIndexer, + env: &EnvAccounts, rdata: &RData, payer: &Keypair, compressed_account: &CompressedAccountWithMerkleContext, - address: &[u8; 32], account_compression_authority: &Pubkey, registered_program_pda: &Pubkey, ) { @@ -278,11 +285,18 @@ async fn delete_record( let merkle_context = pack_merkle_context(compressed_account.merkle_context, &mut remaining_accounts); + let address_merkle_context = AddressMerkleContext { + address_merkle_tree_pubkey: env.address_merkle_tree_pubkey, + address_queue_pubkey: env.address_merkle_tree_queue_pubkey, + }; + let address_merkle_context = + pack_address_merkle_context(address_merkle_context, &mut remaining_accounts); + let instruction_data = name_service::instruction::DeleteRecord { proof: rpc_result.proof, merkle_context, merkle_tree_root_index: rpc_result.root_indices[0], - address: *address, + address_merkle_context, name: "example.io".to_string(), rdata: rdata.clone(), cpi_context: None, diff --git a/sdk/src/compressed_account.rs b/sdk/src/compressed_account.rs index a518b75584..ebf99935b0 100644 --- a/sdk/src/compressed_account.rs +++ b/sdk/src/compressed_account.rs @@ -1,8 +1,145 @@ -use light_system_program::sdk::compressed_account::{ - CompressedAccountWithMerkleContext, PackedCompressedAccountWithMerkleContext, +use anchor_lang::prelude::{AccountInfo, Key, ProgramError, Pubkey, Result}; +use borsh::BorshSerialize; +use light_hasher::{DataHasher, Discriminator, Poseidon}; +use light_system_program::{ + sdk::{ + address::derive_address, + compressed_account::{ + CompressedAccount, CompressedAccountData, CompressedAccountWithMerkleContext, + PackedCompressedAccountWithMerkleContext, PackedMerkleContext, + }, + }, + NewAddressParamsPacked, OutputCompressedAccountWithPackedContext, }; -use crate::merkle_context::{pack_merkle_context, RemainingAccounts}; +use crate::merkle_context::{ + pack_merkle_context, PackedAddressMerkleContext, PackedMerkleOutputContext, RemainingAccounts, +}; + +pub fn serialize_and_hash_account( + account: &T, + address_seed: &[u8; 32], + program_id: &Pubkey, + address_merkle_context: &PackedAddressMerkleContext, + remaining_accounts: &[AccountInfo], +) -> Result +where + T: BorshSerialize + DataHasher + Discriminator, +{ + let data = account.try_to_vec()?; + let data_hash = account.hash::().map_err(ProgramError::from)?; + let compressed_account_data = CompressedAccountData { + discriminator: T::discriminator(), + data, + data_hash, + }; + + let address = derive_address( + &remaining_accounts[address_merkle_context.address_merkle_tree_pubkey_index as usize].key(), + address_seed, + ) + .map_err(|_| ProgramError::InvalidArgument)?; + + let compressed_account = CompressedAccount { + owner: *program_id, + lamports: 0, + address: Some(address), + data: Some(compressed_account_data), + }; + + Ok(compressed_account) +} + +pub fn new_compressed_account( + account: &T, + address_seed: &[u8; 32], + program_id: &Pubkey, + merkle_output_context: &PackedMerkleOutputContext, + address_merkle_context: &PackedAddressMerkleContext, + address_merkle_tree_root_index: u16, + remaining_accounts: &[AccountInfo], +) -> Result<( + OutputCompressedAccountWithPackedContext, + NewAddressParamsPacked, +)> +where + T: BorshSerialize + DataHasher + Discriminator, +{ + let compressed_account = serialize_and_hash_account( + account, + address_seed, + program_id, + address_merkle_context, + remaining_accounts, + )?; + + let compressed_account = OutputCompressedAccountWithPackedContext { + compressed_account, + merkle_tree_index: merkle_output_context.merkle_tree_pubkey_index, + }; + + let new_address_params = NewAddressParamsPacked { + seed: *address_seed, + address_merkle_tree_account_index: address_merkle_context.address_merkle_tree_pubkey_index, + address_queue_account_index: address_merkle_context.address_queue_pubkey_index, + address_merkle_tree_root_index, + }; + + Ok((compressed_account, new_address_params)) +} + +pub fn input_compressed_account( + account: &T, + address_seed: &[u8; 32], + program_id: &Pubkey, + merkle_context: &PackedMerkleContext, + merkle_tree_root_index: u16, + address_merkle_context: &PackedAddressMerkleContext, + remaining_accounts: &[AccountInfo], +) -> Result +where + T: BorshSerialize + DataHasher + Discriminator, +{ + let compressed_account = serialize_and_hash_account( + account, + address_seed, + program_id, + address_merkle_context, + remaining_accounts, + )?; + + Ok(PackedCompressedAccountWithMerkleContext { + compressed_account, + merkle_context: *merkle_context, + root_index: merkle_tree_root_index, + read_only: false, + }) +} + +pub fn output_compressed_account( + account: &T, + address_seed: &[u8; 32], + program_id: &Pubkey, + merkle_context: &PackedMerkleContext, + address_merkle_context: &PackedAddressMerkleContext, + remaining_accounts: &[AccountInfo], +) -> Result +where + T: BorshSerialize + DataHasher + Discriminator, +{ + let compressed_account = serialize_and_hash_account( + account, + address_seed, + program_id, + address_merkle_context, + remaining_accounts, + )?; + + Ok(OutputCompressedAccountWithPackedContext { + compressed_account, + merkle_tree_index: merkle_context.merkle_tree_pubkey_index, + }) +} pub fn pack_compressed_accounts( compressed_accounts: &[CompressedAccountWithMerkleContext], diff --git a/sdk/src/utils.rs b/sdk/src/utils.rs index 21be778e44..0112f08189 100644 --- a/sdk/src/utils.rs +++ b/sdk/src/utils.rs @@ -1,7 +1,8 @@ use anchor_lang::solana_program::pubkey::Pubkey; use light_system_program::{ - invoke::processor::CompressedProof, sdk::CompressedCpiContext, InstructionDataInvokeCpi, - NewAddressParamsPacked, OutputCompressedAccountWithPackedContext, + invoke::processor::CompressedProof, + sdk::{compressed_account::PackedCompressedAccountWithMerkleContext, CompressedCpiContext}, + InstructionDataInvokeCpi, NewAddressParamsPacked, OutputCompressedAccountWithPackedContext, }; pub fn get_registered_program_pda(program_id: &Pubkey) -> Pubkey { @@ -17,22 +18,70 @@ pub fn get_cpi_authority_pda(program_id: &Pubkey) -> Pubkey { } /// Helper function to create data for creating a single PDA. -pub fn create_cpi_inputs_for_new_address( +pub fn create_cpi_inputs_for_new_account( proof: CompressedProof, new_address_params: NewAddressParamsPacked, compressed_pda: OutputCompressedAccountWithPackedContext, - seeds: &[&[u8]], + signer_seeds: &[&[u8]], cpi_context: Option, ) -> InstructionDataInvokeCpi { InstructionDataInvokeCpi { proof: Some(proof), new_address_params: vec![new_address_params], relay_fee: None, - input_compressed_accounts_with_merkle_context: Vec::new(), + input_compressed_accounts_with_merkle_context: vec![], output_compressed_accounts: vec![compressed_pda], compress_or_decompress_lamports: None, is_compress: false, - signer_seeds: seeds.iter().map(|x| x.to_vec()).collect::>>(), + signer_seeds: signer_seeds + .iter() + .map(|x| x.to_vec()) + .collect::>>(), + cpi_context, + } +} + +pub fn create_cpi_inputs_for_account_update( + proof: CompressedProof, + old_compressed_pda: PackedCompressedAccountWithMerkleContext, + new_compressed_pda: OutputCompressedAccountWithPackedContext, + signer_seeds: &[&[u8]], + cpi_context: Option, +) -> InstructionDataInvokeCpi { + InstructionDataInvokeCpi { + proof: Some(proof), + new_address_params: vec![], + input_compressed_accounts_with_merkle_context: vec![old_compressed_pda], + output_compressed_accounts: vec![new_compressed_pda], + relay_fee: None, + compress_or_decompress_lamports: None, + is_compress: false, + signer_seeds: signer_seeds + .iter() + .map(|x| x.to_vec()) + .collect::>>(), + cpi_context, + } +} + +pub fn create_cpi_inputs_for_account_deletion( + proof: CompressedProof, + compressed_pda: PackedCompressedAccountWithMerkleContext, + signer_seeds: &[&[u8]], + cpi_context: Option, +) -> InstructionDataInvokeCpi { + InstructionDataInvokeCpi { + proof: Some(proof), + new_address_params: vec![], + input_compressed_accounts_with_merkle_context: vec![compressed_pda], + output_compressed_accounts: vec![], + relay_fee: None, + compress_or_decompress_lamports: None, + is_compress: false, + signer_seeds: signer_seeds + .iter() + .map(|x| x.to_vec()) + .collect::>>(), cpi_context, } }