From c5c882b42c07e0ad9591e52bd71d5d5283a0b7a3 Mon Sep 17 00:00:00 2001 From: Michal Rostecki Date: Mon, 18 Nov 2024 14:57:49 +0100 Subject: [PATCH] feat: Light SDK without Anchor Provide a possibility to use the Rust Light SDK for programs which are not using Anchor. Related changes: - Rename the former `sdk-test` program to `sdk-anchor-test`. - Add the new `sdk-test` program, which uses only solana-program. - Replace all re-imports from `anchor_lang` with direct imports from `solana_program`. - Introduce `anchor` feature flag in `light_sdk`. - Make the whole `light_sdk::verify` module independent from Anchor. --- Cargo.lock | 15 +- Cargo.toml | 4 +- .../name-service-without-macros/Cargo.toml | 2 +- .../name-service-without-macros/src/lib.rs | 73 ++-- .../name-service-without-macros/tests/test.rs | 107 ++--- .../programs/name-service/Cargo.toml | 2 +- .../programs/token-escrow/Cargo.toml | 2 +- macros/light-sdk-macros/src/discriminator.rs | 4 +- macros/light-sdk-macros/src/hasher.rs | 156 ++++---- sdk/Cargo.toml | 16 +- sdk/src/account.rs | 33 +- sdk/src/account_info.rs | 106 ++++- sdk/src/account_meta.rs | 20 +- sdk/src/address.rs | 20 +- sdk/src/compressed_account.rs | 38 +- sdk/src/constants.rs | 2 + sdk/src/error.rs | 80 +++- sdk/src/event.rs | 3 + sdk/src/instruction_accounts.rs | 374 ++++++++++++++++++ sdk/src/lib.rs | 5 + sdk/src/merkle_context.rs | 247 ++++-------- sdk/src/proof.rs | 3 + sdk/src/state.rs | 3 + sdk/src/system_accounts.rs | 143 +++++++ sdk/src/token.rs | 3 + sdk/src/transfer.rs | 19 +- sdk/src/utils.rs | 66 +--- sdk/src/verify.rs | 268 ++++--------- .../.gitignore | 0 .../.prettierignore | 0 .../Anchor.toml | 0 .../README.md | 0 .../package.json | 0 .../programs/sdk-anchor-test}/Cargo.toml | 8 +- .../programs/sdk-anchor-test}/Xargo.toml | 0 .../programs/sdk-anchor-test}/src/lib.rs | 0 .../programs/sdk-anchor-test}/tests/test.rs | 36 +- .../tsconfig.json | 0 test-programs/sdk-test/Cargo.toml | 19 + test-programs/sdk-test/src/lib.rs | 289 ++++++++++++++ test-programs/sdk-test/tests/test.rs | 1 + 41 files changed, 1439 insertions(+), 728 deletions(-) create mode 100644 sdk/src/instruction_accounts.rs create mode 100644 sdk/src/system_accounts.rs rename test-programs/{sdk-test-program => sdk-anchor-test}/.gitignore (100%) rename test-programs/{sdk-test-program => sdk-anchor-test}/.prettierignore (100%) rename test-programs/{sdk-test-program => sdk-anchor-test}/Anchor.toml (100%) rename test-programs/{sdk-test-program => sdk-anchor-test}/README.md (100%) rename test-programs/{sdk-test-program => sdk-anchor-test}/package.json (100%) rename test-programs/{sdk-test-program/programs/sdk-test => sdk-anchor-test/programs/sdk-anchor-test}/Cargo.toml (84%) rename test-programs/{sdk-test-program/programs/sdk-test => sdk-anchor-test/programs/sdk-anchor-test}/Xargo.toml (100%) rename test-programs/{sdk-test-program/programs/sdk-test => sdk-anchor-test/programs/sdk-anchor-test}/src/lib.rs (100%) rename test-programs/{sdk-test-program/programs/sdk-test => sdk-anchor-test/programs/sdk-anchor-test}/tests/test.rs (90%) rename test-programs/{sdk-test-program => sdk-anchor-test}/tsconfig.json (100%) create mode 100644 test-programs/sdk-test/Cargo.toml create mode 100644 test-programs/sdk-test/src/lib.rs create mode 100644 test-programs/sdk-test/tests/test.rs diff --git a/Cargo.lock b/Cargo.lock index 6e153cd1ce..22df45b4c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3734,7 +3734,6 @@ dependencies = [ "aligned-sized", "anchor-lang", "borsh 0.10.3", - "bytemuck", "groth16-solana", "lazy_static", "light-concurrent-merkle-tree", @@ -3759,6 +3758,7 @@ dependencies = [ "solana-program", "solana-program-test", "solana-sdk", + "thiserror", "tokio", ] @@ -5767,7 +5767,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0495e4577c672de8254beb68d01a9b62d0e8a13c099edecdbedccce3223cd29f" [[package]] -name = "sdk-test" +name = "sdk-anchor-test" version = "0.7.0" dependencies = [ "anchor-lang", @@ -5785,6 +5785,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "sdk-test" +version = "0.7.0" +dependencies = [ + "borsh 0.10.3", + "light-sdk", + "solana-program", + "solana-program-test", + "thiserror", +] + [[package]] name = "security-framework" version = "2.11.1" diff --git a/Cargo.toml b/Cargo.toml index 03b788d30b..5da1a120c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,8 @@ members = [ "test-programs/registry-test/", "test-programs/system-cpi-test/", "test-programs/system-test/", - "test-programs/sdk-test-program/programs/sdk-test/", + "test-programs/sdk-test/", + "test-programs/sdk-anchor-test/programs/sdk-anchor-test/", "forester-utils", "forester", "photon-api", @@ -79,6 +80,7 @@ thiserror = "1.0" light-client = { path = "client", version = "0.9.1" } light-concurrent-merkle-tree = { path = "merkle-tree/concurrent", version = "1.1.0" } light-hasher = { path = "merkle-tree/hasher", version = "1.1.0" } +light-heap = { path = "heap", version = "1.1.0" } light-indexed-merkle-tree = { path = "merkle-tree/indexed", version = "1.1.0" } light-macros = { path = "macros/light", version = "1.1.0" } light-merkle-tree-reference = { path = "merkle-tree/reference", version = "1.1.0" } diff --git a/examples/name-service/programs/name-service-without-macros/Cargo.toml b/examples/name-service/programs/name-service-without-macros/Cargo.toml index d859ebb95c..1c96c69642 100644 --- a/examples/name-service/programs/name-service-without-macros/Cargo.toml +++ b/examples/name-service/programs/name-service-without-macros/Cargo.toml @@ -25,7 +25,7 @@ anchor-lang = { workspace=true} borsh = { workspace = true } light-hasher = { workspace = true, features = ["solana"] } light-macros = { workspace = true } -light-sdk = { workspace = true } +light-sdk = { workspace = true, features = ["anchor"] } light-sdk-macros = { workspace = true } light-utils = { workspace = true } light-verifier = { workspace = true } diff --git a/examples/name-service/programs/name-service-without-macros/src/lib.rs b/examples/name-service/programs/name-service-without-macros/src/lib.rs index 559b346ec7..78a3ed36eb 100644 --- a/examples/name-service/programs/name-service-without-macros/src/lib.rs +++ b/examples/name-service/programs/name-service-without-macros/src/lib.rs @@ -4,8 +4,8 @@ use anchor_lang::prelude::*; use borsh::{BorshDeserialize, BorshSerialize}; use light_hasher::bytes::AsByteVec; use light_sdk::{ - account::LightAccount, instruction_data::LightInstructionData, light_system_accounts, - verify::verify_light_accounts, LightDiscriminator, LightHasher, LightTraits, + account::LightAccount, instruction_data::LightInstructionData, verify::verify_light_accounts, + LightDiscriminator, LightHasher, }; declare_id!("7yucc7fL3JGbyMwg4neUaenNSdySS39hbAk89Ao3t1Hz"); @@ -15,13 +15,13 @@ pub mod name_service { use light_hasher::Discriminator; use light_sdk::{ address::derive_address, error::LightSdkError, - program_merkle_context::unpack_address_merkle_context, + program_merkle_context::unpack_address_merkle_context, system_accounts::LightCpiAccounts, }; use super::*; - pub fn create_record<'info>( - ctx: Context<'_, '_, '_, 'info, CreateRecord<'info>>, + pub fn create_record<'a, 'b, 'c, 'info>( + ctx: Context<'a, 'b, 'c, 'info, CreateRecord<'info>>, inputs: Vec, name: String, rdata: RData, @@ -55,7 +55,19 @@ pub mod name_service { record.name = name; record.rdata = rdata; - verify_light_accounts(&ctx, inputs.proof, &[record], None, false, None)?; + let light_cpi_accounts = LightCpiAccounts::new( + ctx.accounts.signer.as_ref(), + ctx.accounts.cpi_signer.as_ref(), + ctx.remaining_accounts, + ); + verify_light_accounts( + &light_cpi_accounts, + inputs.proof, + &[record], + None, + false, + None, + )?; Ok(()) } @@ -84,7 +96,19 @@ pub mod name_service { record.rdata = new_rdata; - verify_light_accounts(&ctx, inputs.proof, &[record], None, false, None)?; + let light_cpi_accounts = LightCpiAccounts::new( + ctx.accounts.signer.as_ref(), + ctx.accounts.cpi_signer.as_ref(), + ctx.remaining_accounts, + ); + verify_light_accounts( + &light_cpi_accounts, + inputs.proof, + &[record], + None, + false, + None, + )?; Ok(()) } @@ -106,7 +130,19 @@ pub mod name_service { return err!(CustomError::Unauthorized); } - verify_light_accounts(&ctx, inputs.proof, &[record], None, false, None)?; + let light_cpi_accounts = LightCpiAccounts::new( + ctx.accounts.signer.as_ref(), + ctx.accounts.cpi_signer.as_ref(), + ctx.remaining_accounts, + ); + verify_light_accounts( + &light_cpi_accounts, + inputs.proof, + &[record], + None, + false, + None, + )?; Ok(()) } @@ -154,41 +190,26 @@ pub enum CustomError { Unauthorized, } -#[light_system_accounts] -#[derive(Accounts, LightTraits)] +#[derive(Accounts)] pub struct CreateRecord<'info> { #[account(mut)] - #[fee_payer] pub signer: Signer<'info>, - #[self_program] - pub self_program: Program<'info, crate::program::NameService>, /// CHECK: Checked in light-system-program. - #[authority] pub cpi_signer: AccountInfo<'info>, } -#[light_system_accounts] -#[derive(Accounts, LightTraits)] +#[derive(Accounts)] pub struct UpdateRecord<'info> { #[account(mut)] - #[fee_payer] pub signer: Signer<'info>, - #[self_program] - pub self_program: Program<'info, crate::program::NameService>, /// CHECK: Checked in light-system-program. - #[authority] pub cpi_signer: AccountInfo<'info>, } -#[light_system_accounts] -#[derive(Accounts, LightTraits)] +#[derive(Accounts)] pub struct DeleteRecord<'info> { #[account(mut)] - #[fee_payer] pub signer: Signer<'info>, - #[self_program] - pub self_program: Program<'info, crate::program::NameService>, /// CHECK: Checked in light-system-program. - #[authority] pub cpi_signer: AccountInfo<'info>, } diff --git a/examples/name-service/programs/name-service-without-macros/tests/test.rs b/examples/name-service/programs/name-service-without-macros/tests/test.rs index 6f3119c261..37f9133741 100644 --- a/examples/name-service/programs/name-service-without-macros/tests/test.rs +++ b/examples/name-service/programs/name-service-without-macros/tests/test.rs @@ -11,11 +11,12 @@ use light_sdk::account_meta::LightAccountMeta; use light_sdk::address::derive_address; use light_sdk::compressed_account::CompressedAccountWithMerkleContext; use light_sdk::error::LightSdkError; +use light_sdk::instruction_accounts::LightInstructionAccounts; use light_sdk::instruction_data::LightInstructionData; -use light_sdk::merkle_context::{AddressMerkleContext, RemainingAccounts}; +use light_sdk::merkle_context::AddressMerkleContext; use light_sdk::utils::get_cpi_authority_pda; use light_sdk::verify::find_cpi_signer; -use light_sdk::{PROGRAM_ID_ACCOUNT_COMPRESSION, PROGRAM_ID_LIGHT_SYSTEM, PROGRAM_ID_NOOP}; +use light_sdk::{PROGRAM_ID_ACCOUNT_COMPRESSION, PROGRAM_ID_LIGHT_SYSTEM}; use light_test_utils::test_env::{setup_test_programs_with_accounts_v2, EnvAccounts}; use light_test_utils::{RpcConnection, RpcError}; use name_service_without_macros::{CustomError, NameRecord, RData}; @@ -51,8 +52,6 @@ async fn test_name_service() { let name = "example.io"; - let mut remaining_accounts = RemainingAccounts::default(); - let address_merkle_context = AddressMerkleContext { address_merkle_tree_pubkey: env.address_merkle_tree_pubkey, address_queue_pubkey: env.address_merkle_tree_queue_pubkey, @@ -64,12 +63,20 @@ async fn test_name_service() { &name_service_without_macros::ID, ); - let account_compression_authority = get_cpi_authority_pda(&PROGRAM_ID_LIGHT_SYSTEM); let registered_program_pda = Pubkey::find_program_address( &[PROGRAM_ID_LIGHT_SYSTEM.to_bytes().as_slice()], &PROGRAM_ID_ACCOUNT_COMPRESSION, ) .0; + let account_compression_authority = get_cpi_authority_pda(&PROGRAM_ID_LIGHT_SYSTEM); + + let mut instruction_accounts = LightInstructionAccounts::new( + ®istered_program_pda, + &account_compression_authority, + &name_service_without_macros::ID, + None, + None, + ); // Create the example.io -> 10.0.1.25 record. let rdata_1 = RData::A(Ipv4Addr::new(10, 0, 1, 25)); @@ -79,12 +86,9 @@ async fn test_name_service() { &mut rpc, &mut test_indexer, &env, - &mut remaining_accounts, + &mut instruction_accounts, &payer, &address, - &account_compression_authority, - ®istered_program_pda, - &PROGRAM_ID_LIGHT_SYSTEM, ) .await .unwrap(); @@ -97,12 +101,9 @@ async fn test_name_service() { &mut rpc, &mut test_indexer, &env, - &mut remaining_accounts, + &mut instruction_accounts, &payer, &address, - &account_compression_authority, - ®istered_program_pda, - &Pubkey::new_unique(), ) .await; assert!(matches!( @@ -133,13 +134,10 @@ async fn test_name_service() { update_record( &mut rpc, &mut test_indexer, - &mut remaining_accounts, + &mut instruction_accounts, &rdata_2, &payer, compressed_account, - &account_compression_authority, - ®istered_program_pda, - &PROGRAM_ID_LIGHT_SYSTEM, ) .await .unwrap(); @@ -153,13 +151,10 @@ async fn test_name_service() { let result = update_record( &mut rpc, &mut test_indexer, - &mut remaining_accounts, + &mut instruction_accounts, &rdata_2, &invalid_signer, compressed_account, - &account_compression_authority, - ®istered_program_pda, - &PROGRAM_ID_LIGHT_SYSTEM, ) .await; assert!(matches!( @@ -174,13 +169,10 @@ async fn test_name_service() { let result = update_record( &mut rpc, &mut test_indexer, - &mut remaining_accounts, + &mut instruction_accounts, &rdata_2, &payer, compressed_account, - &account_compression_authority, - ®istered_program_pda, - &Pubkey::new_unique(), ) .await; assert!(matches!( @@ -215,12 +207,9 @@ async fn test_name_service() { let result = delete_record( &mut rpc, &mut test_indexer, - &mut remaining_accounts, + &mut instruction_accounts, &invalid_signer, compressed_account, - &account_compression_authority, - ®istered_program_pda, - &PROGRAM_ID_LIGHT_SYSTEM, ) .await; assert!(matches!( @@ -235,12 +224,9 @@ async fn test_name_service() { let result = delete_record( &mut rpc, &mut test_indexer, - &mut remaining_accounts, + &mut instruction_accounts, &payer, compressed_account, - &account_compression_authority, - ®istered_program_pda, - &Pubkey::new_unique(), ) .await; assert!(matches!( @@ -255,12 +241,9 @@ async fn test_name_service() { delete_record( &mut rpc, &mut test_indexer, - &mut remaining_accounts, + &mut instruction_accounts, &payer, compressed_account, - &account_compression_authority, - ®istered_program_pda, - &PROGRAM_ID_LIGHT_SYSTEM, ) .await .unwrap(); @@ -272,12 +255,9 @@ async fn create_record( rpc: &mut R, test_indexer: &mut TestIndexer, env: &EnvAccounts, - remaining_accounts: &mut RemainingAccounts, + instruction_accounts: &mut LightInstructionAccounts, payer: &Keypair, address: &[u8; 32], - account_compression_authority: &Pubkey, - registered_program_pda: &Pubkey, - light_system_program: &Pubkey, ) -> Result<(), RpcError> where R: RpcConnection + MerkleTreeExt, @@ -300,9 +280,10 @@ where &env.merkle_tree_pubkey, Some(&address_merkle_context), Some(rpc_result.address_root_indices[0]), - remaining_accounts, + instruction_accounts, ) .unwrap(); + println!("MERKLE_TREE: {:?}", env.merkle_tree_pubkey); let inputs = LightInstructionData { proof: Some(rpc_result), @@ -319,17 +300,11 @@ where let accounts = name_service_without_macros::accounts::CreateRecord { signer: payer.pubkey(), - light_system_program: *light_system_program, - account_compression_program: PROGRAM_ID_ACCOUNT_COMPRESSION, - account_compression_authority: *account_compression_authority, - registered_program_pda: *registered_program_pda, - noop_program: PROGRAM_ID_NOOP, - self_program: name_service_without_macros::ID, cpi_signer, - system_program: solana_sdk::system_program::id(), }; - let remaining_accounts = remaining_accounts.to_account_metas(); + let remaining_accounts = instruction_accounts.to_account_metas(); + println!("REMAINING ACCOUNTS: {remaining_accounts:?}"); let instruction = Instruction { program_id: name_service_without_macros::ID, @@ -347,13 +322,10 @@ where async fn update_record( rpc: &mut R, test_indexer: &mut TestIndexer, - remaining_accounts: &mut RemainingAccounts, + instruction_accounts: &mut LightInstructionAccounts, new_rdata: &RData, payer: &Keypair, compressed_account: &CompressedAccountWithMerkleContext, - account_compression_authority: &Pubkey, - registered_program_pda: &Pubkey, - light_system_program: &Pubkey, ) -> Result<(), RpcError> where R: RpcConnection + MerkleTreeExt, @@ -375,7 +347,7 @@ where compressed_account, rpc_result.root_indices[0], &merkle_tree_pubkey, - remaining_accounts, + instruction_accounts, ); let inputs = LightInstructionData { @@ -392,17 +364,10 @@ where let accounts = name_service_without_macros::accounts::UpdateRecord { signer: payer.pubkey(), - light_system_program: *light_system_program, - account_compression_program: PROGRAM_ID_ACCOUNT_COMPRESSION, - account_compression_authority: *account_compression_authority, - registered_program_pda: *registered_program_pda, - noop_program: PROGRAM_ID_NOOP, - self_program: name_service_without_macros::ID, cpi_signer, - system_program: solana_sdk::system_program::id(), }; - let remaining_accounts = remaining_accounts.to_account_metas(); + let remaining_accounts = instruction_accounts.to_account_metas(); let instruction = Instruction { program_id: name_service_without_macros::ID, @@ -420,12 +385,9 @@ where async fn delete_record( rpc: &mut R, test_indexer: &mut TestIndexer, - remaining_accounts: &mut RemainingAccounts, + light_instruction_accounts: &mut LightInstructionAccounts, payer: &Keypair, compressed_account: &CompressedAccountWithMerkleContext, - account_compression_authority: &Pubkey, - registered_program_pda: &Pubkey, - light_system_program: &Pubkey, ) -> Result<(), RpcError> where R: RpcConnection + MerkleTreeExt, @@ -446,7 +408,7 @@ where let compressed_account = LightAccountMeta::new_close( compressed_account, rpc_result.root_indices[0], - remaining_accounts, + light_instruction_accounts, ); let inputs = LightInstructionData { @@ -460,17 +422,10 @@ where let accounts = name_service_without_macros::accounts::DeleteRecord { signer: payer.pubkey(), - light_system_program: *light_system_program, - account_compression_program: PROGRAM_ID_ACCOUNT_COMPRESSION, - account_compression_authority: *account_compression_authority, - registered_program_pda: *registered_program_pda, - noop_program: PROGRAM_ID_NOOP, - self_program: name_service_without_macros::ID, cpi_signer, - system_program: solana_sdk::system_program::id(), }; - let remaining_accounts = remaining_accounts.to_account_metas(); + let remaining_accounts = light_instruction_accounts.to_account_metas(); let instruction = Instruction { program_id: name_service_without_macros::ID, diff --git a/examples/name-service/programs/name-service/Cargo.toml b/examples/name-service/programs/name-service/Cargo.toml index 2ea79f575a..b12806662a 100644 --- a/examples/name-service/programs/name-service/Cargo.toml +++ b/examples/name-service/programs/name-service/Cargo.toml @@ -25,7 +25,7 @@ anchor-lang = { workspace=true} borsh = { workspace = true } light-hasher = { workspace = true, features = ["solana"] } light-macros = { workspace = true } -light-sdk = { workspace = true } +light-sdk = { workspace = true, features = ["anchor"] } light-sdk-macros = { workspace = true } light-utils = { workspace = true } light-verifier = { workspace = true } diff --git a/examples/token-escrow/programs/token-escrow/Cargo.toml b/examples/token-escrow/programs/token-escrow/Cargo.toml index 90ae03e928..256c592eb0 100644 --- a/examples/token-escrow/programs/token-escrow/Cargo.toml +++ b/examples/token-escrow/programs/token-escrow/Cargo.toml @@ -26,7 +26,7 @@ light-system-program = { workspace = true } account-compression = { workspace = true } light-hasher = { path = "../../../../merkle-tree/hasher", version = "1.1.0" } light-verifier = { path = "../../../../circuit-lib/verifier", version = "1.1.0" } -light-sdk = { workspace = true, features = ["legacy"] } +light-sdk = { workspace = true, features = ["anchor", "legacy"] } [target.'cfg(not(target_os = "solana"))'.dependencies] solana-sdk = { workspace = true } diff --git a/macros/light-sdk-macros/src/discriminator.rs b/macros/light-sdk-macros/src/discriminator.rs index dd24715746..93ccb99849 100644 --- a/macros/light-sdk-macros/src/discriminator.rs +++ b/macros/light-sdk-macros/src/discriminator.rs @@ -13,7 +13,7 @@ pub(crate) fn discriminator(input: ItemStruct) -> Result { let discriminator: proc_macro2::TokenStream = format!("{discriminator:?}").parse().unwrap(); Ok(quote! { - impl #impl_gen light_hasher::Discriminator for #account_name #type_gen #where_clause { + impl #impl_gen light_sdk::hasher::Discriminator for #account_name #type_gen #where_clause { const DISCRIMINATOR: [u8; 8] = #discriminator; } }) @@ -39,7 +39,7 @@ mod tests { let output = discriminator(input).unwrap(); let output = output.to_string(); - assert!(output.contains("impl light_hasher :: Discriminator for MyAccount")); + assert!(output.contains("impl light_sdk::hasher :: Discriminator for MyAccount")); assert!(output.contains("[181 , 255 , 112 , 42 , 17 , 188 , 66 , 199]")); } } diff --git a/macros/light-sdk-macros/src/hasher.rs b/macros/light-sdk-macros/src/hasher.rs index 8baf5a9d7f..a290dbe973 100644 --- a/macros/light-sdk-macros/src/hasher.rs +++ b/macros/light-sdk-macros/src/hasher.rs @@ -47,7 +47,7 @@ pub(crate) fn hasher(input: ItemStruct) -> Result { quote! { match &self.#field_name { Some(value) => { - let nested_hash = ::light_hasher::DataHasher::hash::<::light_hasher::Poseidon>(value) + let nested_hash = ::light_sdk::hasher::DataHasher::hash::<::light_sdk::hasher::Poseidon>(value) .expect("Failed to hash nested field"); result.push(nested_hash.to_vec()); } @@ -58,7 +58,7 @@ pub(crate) fn hasher(input: ItemStruct) -> Result { } } else { quote! { - let nested_hash = ::light_hasher::DataHasher::hash::<::light_hasher::Poseidon>(&self.#field_name) + let nested_hash = ::light_sdk::hasher::DataHasher::hash::<::light_sdk::hasher::Poseidon>(&self.#field_name) .expect("Failed to hash nested field"); result.push(nested_hash.to_vec()); } @@ -109,9 +109,9 @@ pub(crate) fn hasher(input: ItemStruct) -> Result { .collect::>>()?; Ok(quote! { - impl #impl_gen ::light_hasher::bytes::AsByteVec for #struct_name #type_gen #where_clause { + impl #impl_gen ::light_sdk::hasher::bytes::AsByteVec for #struct_name #type_gen #where_clause { fn as_byte_vec(&self) -> Vec> { - use ::light_hasher::bytes::AsByteVec; + use ::light_sdk::hasher::bytes::AsByteVec; let mut result: Vec> = Vec::new(); #( @@ -121,14 +121,14 @@ pub(crate) fn hasher(input: ItemStruct) -> Result { } } - impl #impl_gen ::light_hasher::DataHasher for #struct_name #type_gen #where_clause { - fn hash(&self) -> ::std::result::Result<[u8; 32], ::light_hasher::HasherError> + impl #impl_gen ::light_sdk::hasher::DataHasher for #struct_name #type_gen #where_clause { + fn hash(&self) -> ::std::result::Result<[u8; 32], ::light_sdk::hasher::HasherError> where - H: ::light_hasher::Hasher + H: ::light_sdk::hasher::Hasher { - use ::light_hasher::bytes::AsByteVec; - use ::light_hasher::DataHasher; - use ::light_hasher::Hasher; + use ::light_sdk::hasher::bytes::AsByteVec; + use ::light_sdk::hasher::DataHasher; + use ::light_sdk::hasher::Hasher; let bytes = self.as_byte_vec(); let nested_bytes: Vec<_> = bytes.iter().map(|v| v.as_slice()).collect(); @@ -158,9 +158,9 @@ mod tests { let output = hasher(input).unwrap(); let formatted_output = unparse(&syn::parse2(output).unwrap()); - let expected_output = r#"impl ::light_hasher::bytes::AsByteVec for MyAccount { + let expected_output = r#"impl ::light_sdk::hasher::bytes::AsByteVec for MyAccount { fn as_byte_vec(&self) -> Vec> { - use ::light_hasher::bytes::AsByteVec; + use ::light_sdk::hasher::bytes::AsByteVec; let mut result: Vec> = Vec::new(); result.extend(self.a.as_byte_vec()); result.extend(self.b.as_byte_vec()); @@ -169,14 +169,14 @@ mod tests { result } } -impl ::light_hasher::DataHasher for MyAccount { - fn hash(&self) -> ::std::result::Result<[u8; 32], ::light_hasher::HasherError> +impl ::light_sdk::hasher::DataHasher for MyAccount { + fn hash(&self) -> ::std::result::Result<[u8; 32], ::light_sdk::hasher::HasherError> where - H: ::light_hasher::Hasher, + H: ::light_sdk::hasher::Hasher, { - use ::light_hasher::bytes::AsByteVec; - use ::light_hasher::DataHasher; - use ::light_hasher::Hasher; + use ::light_sdk::hasher::bytes::AsByteVec; + use ::light_sdk::hasher::DataHasher; + use ::light_sdk::hasher::Hasher; let bytes = self.as_byte_vec(); let nested_bytes: Vec<_> = bytes.iter().map(|v| v.as_slice()).collect(); H::hashv(&nested_bytes) @@ -199,27 +199,27 @@ impl ::light_hasher::DataHasher for MyAccount { let syntax_tree: syn::File = syn::parse2(output).unwrap(); let formatted_output = unparse(&syntax_tree); - let expected_output = r#"impl ::light_hasher::bytes::AsByteVec for OuterStruct { + let expected_output = r#"impl ::light_sdk::hasher::bytes::AsByteVec for OuterStruct { fn as_byte_vec(&self) -> Vec> { - use ::light_hasher::bytes::AsByteVec; + use ::light_sdk::hasher::bytes::AsByteVec; let mut result: Vec> = Vec::new(); result.extend(self.a.as_byte_vec()); - let nested_hash = ::light_hasher::DataHasher::hash::< - ::light_hasher::Poseidon, + let nested_hash = ::light_sdk::hasher::DataHasher::hash::< + ::light_sdk::hasher::Poseidon, >(&self.b) .expect("Failed to hash nested field"); result.push(nested_hash.to_vec()); result } } -impl ::light_hasher::DataHasher for OuterStruct { - fn hash(&self) -> ::std::result::Result<[u8; 32], ::light_hasher::HasherError> +impl ::light_sdk::hasher::DataHasher for OuterStruct { + fn hash(&self) -> ::std::result::Result<[u8; 32], ::light_sdk::hasher::HasherError> where - H: ::light_hasher::Hasher, + H: ::light_sdk::hasher::Hasher, { - use ::light_hasher::bytes::AsByteVec; - use ::light_hasher::DataHasher; - use ::light_hasher::Hasher; + use ::light_sdk::hasher::bytes::AsByteVec; + use ::light_sdk::hasher::DataHasher; + use ::light_sdk::hasher::Hasher; let bytes = self.as_byte_vec(); let nested_bytes: Vec<_> = bytes.iter().map(|v| v.as_slice()).collect(); H::hashv(&nested_bytes) @@ -243,9 +243,9 @@ impl ::light_hasher::DataHasher for OuterStruct { let syntax_tree = syn::parse2(output).unwrap(); let formatted_output = unparse(&syntax_tree); - let expected_output = r#"impl ::light_hasher::bytes::AsByteVec for OuterStruct { + let expected_output = r#"impl ::light_sdk::hasher::bytes::AsByteVec for OuterStruct { fn as_byte_vec(&self) -> Vec> { - use ::light_hasher::bytes::AsByteVec; + use ::light_sdk::hasher::bytes::AsByteVec; let mut result: Vec> = Vec::new(); result.extend(self.a.as_byte_vec()); let bytes = { @@ -255,8 +255,8 @@ impl ::light_hasher::DataHasher for OuterStruct { .expect("Could not truncate to BN254 field size"); result.push(hash.to_vec()); }; - let nested_hash = ::light_hasher::DataHasher::hash::< - ::light_hasher::Poseidon, + let nested_hash = ::light_sdk::hasher::DataHasher::hash::< + ::light_sdk::hasher::Poseidon, >(&self.d) .expect("Failed to hash nested field"); result.push(nested_hash.to_vec()); @@ -279,9 +279,9 @@ impl ::light_hasher::DataHasher for OuterStruct { let syntax_tree = syn::parse2(output).unwrap(); let formatted_output = unparse(&syntax_tree); - let expected_output = r#"impl ::light_hasher::bytes::AsByteVec for OptionStruct { + let expected_output = r#"impl ::light_sdk::hasher::bytes::AsByteVec for OptionStruct { fn as_byte_vec(&self) -> Vec> { - use ::light_hasher::bytes::AsByteVec; + use ::light_sdk::hasher::bytes::AsByteVec; let mut result: Vec> = Vec::new(); match &self.a { Some(value) => { @@ -306,14 +306,14 @@ impl ::light_hasher::DataHasher for OuterStruct { result } } -impl ::light_hasher::DataHasher for OptionStruct { - fn hash(&self) -> ::std::result::Result<[u8; 32], ::light_hasher::HasherError> +impl ::light_sdk::hasher::DataHasher for OptionStruct { + fn hash(&self) -> ::std::result::Result<[u8; 32], ::light_sdk::hasher::HasherError> where - H: ::light_hasher::Hasher, + H: ::light_sdk::hasher::Hasher, { - use ::light_hasher::bytes::AsByteVec; - use ::light_hasher::DataHasher; - use ::light_hasher::Hasher; + use ::light_sdk::hasher::bytes::AsByteVec; + use ::light_sdk::hasher::DataHasher; + use ::light_sdk::hasher::Hasher; let bytes = self.as_byte_vec(); let nested_bytes: Vec<_> = bytes.iter().map(|v| v.as_slice()).collect(); H::hashv(&nested_bytes) @@ -334,9 +334,9 @@ impl ::light_hasher::DataHasher for OptionStruct { let output = hasher(input).unwrap(); let formatted_output = unparse(&syn::parse2(output).unwrap()); - let expected_truncate = r#"impl ::light_hasher::bytes::AsByteVec for TruncateOptionStruct { + let expected_truncate = r#"impl ::light_sdk::hasher::bytes::AsByteVec for TruncateOptionStruct { fn as_byte_vec(&self) -> Vec> { - use ::light_hasher::bytes::AsByteVec; + use ::light_sdk::hasher::bytes::AsByteVec; let mut result: Vec> = Vec::new(); match &self.a { Some(value) => { @@ -356,14 +356,14 @@ impl ::light_hasher::DataHasher for OptionStruct { result } } -impl ::light_hasher::DataHasher for TruncateOptionStruct { - fn hash(&self) -> ::std::result::Result<[u8; 32], ::light_hasher::HasherError> +impl ::light_sdk::hasher::DataHasher for TruncateOptionStruct { + fn hash(&self) -> ::std::result::Result<[u8; 32], ::light_sdk::hasher::HasherError> where - H: ::light_hasher::Hasher, + H: ::light_sdk::hasher::Hasher, { - use ::light_hasher::bytes::AsByteVec; - use ::light_hasher::DataHasher; - use ::light_hasher::Hasher; + use ::light_sdk::hasher::bytes::AsByteVec; + use ::light_sdk::hasher::DataHasher; + use ::light_sdk::hasher::Hasher; let bytes = self.as_byte_vec(); let nested_bytes: Vec<_> = bytes.iter().map(|v| v.as_slice()).collect(); H::hashv(&nested_bytes) @@ -382,14 +382,14 @@ impl ::light_hasher::DataHasher for TruncateOptionStruct { let output = hasher(input).unwrap(); let formatted_output = unparse(&syn::parse2(output).unwrap()); - let expected_nested = r#"impl ::light_hasher::bytes::AsByteVec for NestedOptionStruct { + let expected_nested = r#"impl ::light_sdk::hasher::bytes::AsByteVec for NestedOptionStruct { fn as_byte_vec(&self) -> Vec> { - use ::light_hasher::bytes::AsByteVec; + use ::light_sdk::hasher::bytes::AsByteVec; let mut result: Vec> = Vec::new(); match &self.a { Some(value) => { - let nested_hash = ::light_hasher::DataHasher::hash::< - ::light_hasher::Poseidon, + let nested_hash = ::light_sdk::hasher::DataHasher::hash::< + ::light_sdk::hasher::Poseidon, >(value) .expect("Failed to hash nested field"); result.push(nested_hash.to_vec()); @@ -401,14 +401,14 @@ impl ::light_hasher::DataHasher for TruncateOptionStruct { result } } -impl ::light_hasher::DataHasher for NestedOptionStruct { - fn hash(&self) -> ::std::result::Result<[u8; 32], ::light_hasher::HasherError> +impl ::light_sdk::hasher::DataHasher for NestedOptionStruct { + fn hash(&self) -> ::std::result::Result<[u8; 32], ::light_sdk::hasher::HasherError> where - H: ::light_hasher::Hasher, + H: ::light_sdk::hasher::Hasher, { - use ::light_hasher::bytes::AsByteVec; - use ::light_hasher::DataHasher; - use ::light_hasher::Hasher; + use ::light_sdk::hasher::bytes::AsByteVec; + use ::light_sdk::hasher::DataHasher; + use ::light_sdk::hasher::Hasher; let bytes = self.as_byte_vec(); let nested_bytes: Vec<_> = bytes.iter().map(|v| v.as_slice()).collect(); H::hashv(&nested_bytes) @@ -432,14 +432,14 @@ impl ::light_hasher::DataHasher for NestedOptionStruct { let output = hasher(input).unwrap(); let formatted_output = unparse(&syn::parse2(output).unwrap()); - let expected_output = r#"impl ::light_hasher::bytes::AsByteVec for MixedOptionsStruct { + let expected_output = r#"impl ::light_sdk::hasher::bytes::AsByteVec for MixedOptionsStruct { fn as_byte_vec(&self) -> Vec> { - use ::light_hasher::bytes::AsByteVec; + use ::light_sdk::hasher::bytes::AsByteVec; let mut result: Vec> = Vec::new(); match &self.a { Some(value) => { - let nested_hash = ::light_hasher::DataHasher::hash::< - ::light_hasher::Poseidon, + let nested_hash = ::light_sdk::hasher::DataHasher::hash::< + ::light_sdk::hasher::Poseidon, >(value) .expect("Failed to hash nested field"); result.push(nested_hash.to_vec()); @@ -476,14 +476,14 @@ impl ::light_hasher::DataHasher for NestedOptionStruct { result } } -impl ::light_hasher::DataHasher for MixedOptionsStruct { - fn hash(&self) -> ::std::result::Result<[u8; 32], ::light_hasher::HasherError> +impl ::light_sdk::hasher::DataHasher for MixedOptionsStruct { + fn hash(&self) -> ::std::result::Result<[u8; 32], ::light_sdk::hasher::HasherError> where - H: ::light_hasher::Hasher, + H: ::light_sdk::hasher::Hasher, { - use ::light_hasher::bytes::AsByteVec; - use ::light_hasher::DataHasher; - use ::light_hasher::Hasher; + use ::light_sdk::hasher::bytes::AsByteVec; + use ::light_sdk::hasher::DataHasher; + use ::light_sdk::hasher::Hasher; let bytes = self.as_byte_vec(); let nested_bytes: Vec<_> = bytes.iter().map(|v| v.as_slice()).collect(); H::hashv(&nested_bytes) @@ -509,13 +509,13 @@ impl ::light_hasher::DataHasher for MixedOptionsStruct { let syntax_tree = syn::parse2(output).unwrap(); let formatted_output = unparse(&syntax_tree); - let expected_output = r#"impl ::light_hasher::bytes::AsByteVec for OuterStruct { + let expected_output = r#"impl ::light_sdk::hasher::bytes::AsByteVec for OuterStruct { fn as_byte_vec(&self) -> Vec> { - use ::light_hasher::bytes::AsByteVec; + use ::light_sdk::hasher::bytes::AsByteVec; let mut result: Vec> = Vec::new(); result.extend(self.a.as_byte_vec()); - let nested_hash = ::light_hasher::DataHasher::hash::< - ::light_hasher::Poseidon, + let nested_hash = ::light_sdk::hasher::DataHasher::hash::< + ::light_sdk::hasher::Poseidon, >(&self.b) .expect("Failed to hash nested field"); result.push(nested_hash.to_vec()); @@ -537,14 +537,14 @@ impl ::light_hasher::DataHasher for MixedOptionsStruct { result } } -impl ::light_hasher::DataHasher for OuterStruct { - fn hash(&self) -> ::std::result::Result<[u8; 32], ::light_hasher::HasherError> +impl ::light_sdk::hasher::DataHasher for OuterStruct { + fn hash(&self) -> ::std::result::Result<[u8; 32], ::light_sdk::hasher::HasherError> where - H: ::light_hasher::Hasher, + H: ::light_sdk::hasher::Hasher, { - use ::light_hasher::bytes::AsByteVec; - use ::light_hasher::DataHasher; - use ::light_hasher::Hasher; + use ::light_sdk::hasher::bytes::AsByteVec; + use ::light_sdk::hasher::DataHasher; + use ::light_sdk::hasher::Hasher; let bytes = self.as_byte_vec(); let nested_bytes: Vec<_> = bytes.iter().map(|v| v.as_slice()).collect(); H::hashv(&nested_bytes) diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 05f54d1d0c..51149d9583 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -11,13 +11,14 @@ crate-type = ["cdylib", "lib"] name = "light_sdk" [features] +anchor = [] no-entrypoint = [] no-idl = [] no-log-ix-name = [] cpi = ["no-entrypoint"] custom-heap = ["light-heap"] mem-profiling = [] -default = ["custom-heap"] +default = [] test-sbf = [] bench-sbf = [] idl-build = ["anchor-lang/idl-build"] @@ -33,12 +34,15 @@ anchor-lang = { workspace = true } # Math and crypto num-bigint = { workspace = true } +# Error handling +thiserror = { workspace = true } + +# Light Protocol aligned-sized = { version = "1.1.0", path = "../macros/aligned-sized" } -light-macros = { version = "1.1.0", path = "../macros/light" } -light-sdk-macros = { version = "0.4.0", path = "../macros/light-sdk-macros" } -bytemuck = "1.17" -light-hasher = { version = "1.1.0", path = "../merkle-tree/hasher", features=["solana"] } -light-heap = { version = "1.1.0", path = "../heap", optional = true } +light-macros = { workspace = true } +light-sdk-macros = { workspace = true } +light-hasher = { workspace = true } +light-heap = { workspace = true, optional = true } light-indexed-merkle-tree = { workspace = true } account-compression = { workspace = true , optional = true } light-system-program = { workspace = true , optional = true } diff --git a/sdk/src/account.rs b/sdk/src/account.rs index 0a86a6adf4..a93f990772 100644 --- a/sdk/src/account.rs +++ b/sdk/src/account.rs @@ -1,9 +1,11 @@ use std::ops::{Deref, DerefMut}; -use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize, Result}; - +#[cfg(feature = "anchor")] +use anchor_lang::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize}; use light_hasher::{DataHasher, Discriminator, Poseidon}; -use solana_program::{program_error::ProgramError, pubkey::Pubkey}; +use solana_program::pubkey::Pubkey; use crate::{ account_info::LightAccountInfo, @@ -17,7 +19,7 @@ use crate::{ }; pub trait LightAccounts<'a>: Sized { - fn try_light_accounts(accounts: &'a [LightAccountInfo]) -> Result; + fn try_light_accounts(accounts: &'a [LightAccountInfo]) -> Result; } // TODO(vadorovsky): Implment `LightAccountLoader`. @@ -44,7 +46,7 @@ where new_address: [u8; 32], new_address_seed: [u8; 32], owner: &'info Pubkey, - ) -> Result { + ) -> Result { let account_state = T::default(); let account_info = LightAccountInfo::from_meta_init_without_output_data( meta, @@ -63,7 +65,7 @@ where meta: &'info LightAccountMeta, discriminator: [u8; 8], owner: &'info Pubkey, - ) -> Result { + ) -> Result { let mut account_info = LightAccountInfo::from_meta_without_output_data(meta, discriminator, owner)?; let account_state = T::try_from_slice( @@ -72,9 +74,7 @@ where .ok_or(LightSdkError::ExpectedData)? .as_slice(), )?; - let input_hash = account_state - .hash::() - .map_err(ProgramError::from)?; + let input_hash = account_state.hash::()?; // Set the input account hash. // @@ -91,7 +91,7 @@ where meta: &'info LightAccountMeta, discriminator: [u8; 8], owner: &'info Pubkey, - ) -> Result { + ) -> Result { let mut account_info = LightAccountInfo::from_meta_without_output_data(meta, discriminator, owner)?; let account_state = T::try_from_slice( @@ -100,9 +100,7 @@ where .ok_or(LightSdkError::ExpectedData)? .as_slice(), )?; - let input_hash = account_state - .hash::() - .map_err(ProgramError::from)?; + let input_hash = account_state.hash::()?; // Set the input account hash. // @@ -121,21 +119,18 @@ where pub fn input_compressed_account( &self, - ) -> Result> { + ) -> Result, LightSdkError> { self.account_info.input_compressed_account() } pub fn output_compressed_account( &self, - ) -> Result> { + ) -> Result, LightSdkError> { match self.account_info.output_merkle_tree_index { Some(merkle_tree_index) => { let data = { let discriminator = T::discriminator(); - let data_hash = self - .account_state - .hash::() - .map_err(ProgramError::from)?; + let data_hash = self.account_state.hash::()?; Some(CompressedAccountData { discriminator, data: self.account_state.try_to_vec()?, diff --git a/sdk/src/account_info.rs b/sdk/src/account_info.rs index 242d94164b..5575843e37 100644 --- a/sdk/src/account_info.rs +++ b/sdk/src/account_info.rs @@ -1,6 +1,5 @@ use std::{cell::RefCell, rc::Rc}; -use anchor_lang::prelude::Result; use solana_program::pubkey::Pubkey; use crate::{ @@ -12,6 +11,7 @@ use crate::{ }, error::LightSdkError, merkle_context::PackedMerkleContext, + system_accounts::SYSTEM_ACCOUNTS_LEN, }; /// Information about compressed account which is being initialized. @@ -64,7 +64,7 @@ impl<'a> LightAccountInfo<'a> { new_address_seed: [u8; 32], space: Option, owner: &'a Pubkey, - ) -> Result { + ) -> Result { let address_merkle_context = meta .address_merkle_context .as_ref() @@ -72,9 +72,26 @@ impl<'a> LightAccountInfo<'a> { let new_address_params = PackedNewAddressParams { seed: new_address_seed, - address_queue_account_index: address_merkle_context.address_queue_pubkey_index, + address_queue_account_index: address_merkle_context + .address_queue_pubkey_index + // Merkle tree accounts are at the end of "remaining accounts". + // "Remaining acocunts" passed to programs contain also system + // accounts. + // However, in light-system-program, system accounts are + // specified as regular accounts and don't end up as remaining + // anymore. + // Therefore, we need to update the Merkle tree accout indices. + .saturating_sub(SYSTEM_ACCOUNTS_LEN as u8), address_merkle_tree_account_index: address_merkle_context - .address_merkle_tree_pubkey_index, + .address_merkle_tree_pubkey_index + // Merkle tree accounts are at the end of "remaining accounts". + // "Remaining acocunts" passed to programs contain also system + // accounts. + // However, in light-system-program, system accounts are + // specified as regular accounts and don't end up as remaining + // anymore. + // Therefore, we need to update the Merkle tree accout indices. + .saturating_sub(SYSTEM_ACCOUNTS_LEN as u8), address_merkle_tree_root_index: meta .address_merkle_tree_root_index .ok_or(LightSdkError::ExpectedAddressRootIndex)?, @@ -97,7 +114,16 @@ impl<'a> LightAccountInfo<'a> { // Needs to be assigned by the program. data_hash: None, address: Some(new_address), - output_merkle_tree_index: meta.output_merkle_tree_index, + output_merkle_tree_index: meta + .output_merkle_tree_index + // Merkle tree accounts are at the end of "remaining accounts". + // "Remaining acocunts" passed to programs contain also system + // accounts. + // However, in light-system-program, system accounts are + // specified as regular accounts and don't end up as remaining + // anymore. + // Therefore, we need to update the Merkle tree accout indices. + .map(|index| index.saturating_sub(SYSTEM_ACCOUNTS_LEN as u8)), new_address_params: Some(new_address_params), }; Ok(account_info) @@ -107,7 +133,7 @@ impl<'a> LightAccountInfo<'a> { meta: &'a LightAccountMeta, discriminator: [u8; 8], owner: &'a Pubkey, - ) -> Result { + ) -> Result { let input = LightInputAccountInfo { lamports: meta.lamports, address: meta.address, @@ -154,7 +180,16 @@ impl<'a> LightAccountInfo<'a> { // Needs to be assigned by the program. data_hash: None, address: meta.address, - output_merkle_tree_index: meta.output_merkle_tree_index, + output_merkle_tree_index: meta + .output_merkle_tree_index + // Merkle tree accounts are at the end of "remaining accounts". + // "Remaining acocunts" passed to programs contain also system + // accounts. + // However, in light-system-program, system accounts are + // specified as regular accounts and don't end up as remaining + // anymore. + // Therefore, we need to update the Merkle tree accout indices. + .map(|index| index.saturating_sub(SYSTEM_ACCOUNTS_LEN as u8)), new_address_params: None, }; Ok(account_info) @@ -164,7 +199,7 @@ impl<'a> LightAccountInfo<'a> { meta: &'a LightAccountMeta, discriminator: [u8; 8], owner: &'a Pubkey, - ) -> Result { + ) -> Result { let input = LightInputAccountInfo { lamports: meta.lamports, address: meta.address, @@ -202,7 +237,7 @@ impl<'a> LightAccountInfo<'a> { new_address: [u8; 32], new_address_seed: [u8; 32], owner: &'a Pubkey, - ) -> Result { + ) -> Result { let address_merkle_context = meta .address_merkle_context .as_ref() @@ -210,9 +245,26 @@ impl<'a> LightAccountInfo<'a> { let new_address_params = PackedNewAddressParams { seed: new_address_seed, - address_queue_account_index: address_merkle_context.address_queue_pubkey_index, + address_queue_account_index: address_merkle_context + .address_queue_pubkey_index + // Merkle tree accounts are at the end of "remaining accounts". + // "Remaining acocunts" passed to programs contain also system + // accounts. + // However, in light-system-program, system accounts are + // specified as regular accounts and don't end up as remaining + // anymore. + // Therefore, we need to update the Merkle tree accout indices. + .saturating_sub(SYSTEM_ACCOUNTS_LEN as u8), address_merkle_tree_account_index: address_merkle_context - .address_merkle_tree_pubkey_index, + .address_merkle_tree_pubkey_index + // Merkle tree accounts are at the end of "remaining accounts". + // "Remaining acocunts" passed to programs contain also system + // accounts. + // However, in light-system-program, system accounts are + // specified as regular accounts and don't end up as remaining + // anymore. + // Therefore, we need to update the Merkle tree accout indices. + .saturating_sub(SYSTEM_ACCOUNTS_LEN as u8), address_merkle_tree_root_index: meta .address_merkle_tree_root_index .ok_or(LightSdkError::ExpectedAddressRootIndex)?, @@ -228,7 +280,16 @@ impl<'a> LightAccountInfo<'a> { data: None, data_hash: None, address: Some(new_address), - output_merkle_tree_index: meta.output_merkle_tree_index, + output_merkle_tree_index: meta + .output_merkle_tree_index + // Merkle tree accounts are at the end of "remaining accounts". + // "Remaining acocunts" passed to programs contain also system + // accounts. + // However, in light-system-program, system accounts are + // specified as regular accounts and don't end up as remaining + // anymore. + // Therefore, we need to update the Merkle tree accout indices. + .map(|index| index.saturating_sub(SYSTEM_ACCOUNTS_LEN as u8)), new_address_params: Some(new_address_params), }; Ok(account_info) @@ -243,7 +304,7 @@ impl<'a> LightAccountInfo<'a> { meta: &'a LightAccountMeta, discriminator: [u8; 8], owner: &'a Pubkey, - ) -> Result { + ) -> Result { let input = LightInputAccountInfo { lamports: meta.lamports, address: meta.address, @@ -268,7 +329,16 @@ impl<'a> LightAccountInfo<'a> { data: None, data_hash: None, address: meta.address, - output_merkle_tree_index: meta.output_merkle_tree_index, + output_merkle_tree_index: meta + .output_merkle_tree_index + // Merkle tree accounts are at the end of "remaining accounts". + // "Remaining acocunts" passed to programs contain also system + // accounts. + // However, in light-system-program, system accounts are + // specified as regular accounts and don't end up as remaining + // anymore. + // Therefore, we need to update the Merkle tree accout indices. + .map(|index| index.saturating_sub(SYSTEM_ACCOUNTS_LEN as u8)), new_address_params: None, }; Ok(account_info) @@ -284,12 +354,16 @@ impl<'a> LightAccountInfo<'a> { self.input.as_ref().and_then(|input| input.data) } + pub fn new_address_params(&self) -> Option { + unimplemented!() + } + /// Converts the given [LightAccountInfo] into a /// [PackedCompressedAccountWithMerkleContext] which can be sent to the /// light-system program. pub fn input_compressed_account( &self, - ) -> Result> { + ) -> Result, LightSdkError> { match self.input.as_ref() { Some(input) => { let data = match input.data { @@ -324,7 +398,7 @@ impl<'a> LightAccountInfo<'a> { pub fn output_compressed_account( &self, - ) -> Result> { + ) -> Result, LightSdkError> { match self.output_merkle_tree_index { Some(merkle_tree_index) => { let data = match self.data { diff --git a/sdk/src/account_meta.rs b/sdk/src/account_meta.rs index a658808088..80be5c3ba8 100644 --- a/sdk/src/account_meta.rs +++ b/sdk/src/account_meta.rs @@ -1,14 +1,18 @@ //! Types used +#[cfg(feature = "anchor")] use anchor_lang::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize}; use solana_program::pubkey::Pubkey; use crate::{ compressed_account::CompressedAccountWithMerkleContext, error::LightSdkError, + instruction_accounts::LightInstructionAccounts, merkle_context::{ pack_address_merkle_context, pack_merkle_context, AddressMerkleContext, - PackedAddressMerkleContext, PackedMerkleContext, RemainingAccounts, + PackedAddressMerkleContext, PackedMerkleContext, }, }; @@ -42,7 +46,7 @@ impl LightAccountMeta { output_merkle_tree: &Pubkey, address_merkle_context: Option<&AddressMerkleContext>, address_merkle_tree_root_index: Option, - remaining_accounts: &mut RemainingAccounts, + remaining_accounts: &mut LightInstructionAccounts, ) -> Result { let output_merkle_tree_index = remaining_accounts.insert_or_get(*output_merkle_tree); let address_merkle_context = @@ -65,14 +69,13 @@ impl LightAccountMeta { compressed_account: &CompressedAccountWithMerkleContext, merkle_tree_root_index: u16, output_merkle_tree: &Pubkey, - remaining_accounts: &mut RemainingAccounts, + accounts: &mut LightInstructionAccounts, ) -> Self { - let merkle_context = - pack_merkle_context(&compressed_account.merkle_context, remaining_accounts); + let merkle_context = pack_merkle_context(&compressed_account.merkle_context, accounts); // If no output Merkle tree was specified, use the one used for the // input account. - let output_merkle_tree_index = remaining_accounts.insert_or_get(*output_merkle_tree); + let output_merkle_tree_index = accounts.insert_or_get(*output_merkle_tree); Self { lamports: Some(compressed_account.compressed_account.lamports), @@ -94,10 +97,9 @@ impl LightAccountMeta { pub fn new_close( compressed_account: &CompressedAccountWithMerkleContext, merkle_tree_root_index: u16, - remaining_accounts: &mut RemainingAccounts, + accounts: &mut LightInstructionAccounts, ) -> Self { - let merkle_context = - pack_merkle_context(&compressed_account.merkle_context, remaining_accounts); + let merkle_context = pack_merkle_context(&compressed_account.merkle_context, accounts); Self { lamports: Some(compressed_account.compressed_account.lamports), address: compressed_account.compressed_account.address, diff --git a/sdk/src/address.rs b/sdk/src/address.rs index 49f8592ac3..fccff1e8e7 100644 --- a/sdk/src/address.rs +++ b/sdk/src/address.rs @@ -1,8 +1,11 @@ -use anchor_lang::{solana_program::pubkey::Pubkey, AnchorDeserialize, AnchorSerialize}; +#[cfg(feature = "anchor")] +use anchor_lang::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize}; use light_utils::{hash_to_bn254_field_size_be, hashv_to_bn254_field_size_be}; -use solana_program::account_info::AccountInfo; +use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; -use crate::merkle_context::{AddressMerkleContext, RemainingAccounts}; +use crate::{instruction_accounts::LightInstructionAccounts, merkle_context::AddressMerkleContext}; #[derive(Debug, PartialEq, Default, Clone, AnchorDeserialize, AnchorSerialize)] pub struct NewAddressParams { @@ -27,15 +30,14 @@ pub struct AddressWithMerkleContext { pub fn pack_new_addresses_params( addresses_params: &[NewAddressParams], - remaining_accounts: &mut RemainingAccounts, + accounts: &mut LightInstructionAccounts, ) -> Vec { addresses_params .iter() .map(|x| { - let address_queue_account_index = - remaining_accounts.insert_or_get(x.address_queue_pubkey); + let address_queue_account_index = accounts.insert_or_get(x.address_queue_pubkey); let address_merkle_tree_account_index = - remaining_accounts.insert_or_get(x.address_merkle_tree_pubkey); + accounts.insert_or_get(x.address_merkle_tree_pubkey); PackedNewAddressParams { seed: x.seed, address_queue_account_index, @@ -48,9 +50,9 @@ pub fn pack_new_addresses_params( pub fn pack_new_address_params( address_params: NewAddressParams, - remaining_accounts: &mut RemainingAccounts, + accounts: &mut LightInstructionAccounts, ) -> PackedNewAddressParams { - pack_new_addresses_params(&[address_params], remaining_accounts)[0] + pack_new_addresses_params(&[address_params], accounts)[0] } pub fn unpack_new_address_params( diff --git a/sdk/src/compressed_account.rs b/sdk/src/compressed_account.rs index a11797847e..72587be2cc 100644 --- a/sdk/src/compressed_account.rs +++ b/sdk/src/compressed_account.rs @@ -1,9 +1,15 @@ -use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize, ProgramError, Pubkey, Result}; +#[cfg(feature = "anchor")] +use anchor_lang::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize}; use light_hasher::{DataHasher, Discriminator, Hasher, Poseidon}; use light_utils::hash_to_bn254_field_size_be; +use solana_program::pubkey::Pubkey; -use crate::merkle_context::{ - pack_merkle_context, MerkleContext, PackedMerkleContext, RemainingAccounts, +use crate::{ + error::LightSdkError, + instruction_accounts::LightInstructionAccounts, + merkle_context::{pack_merkle_context, MerkleContext, PackedMerkleContext}, }; #[derive(Debug, PartialEq, Default, Clone, AnchorDeserialize, AnchorSerialize)] @@ -22,7 +28,7 @@ impl CompressedAccount { &owner_hashed: &[u8; 32], &merkle_tree_hashed: &[u8; 32], leaf_index: &u32, - ) -> Result<[u8; 32]> { + ) -> Result<[u8; 32], LightSdkError> { let capacity = 3 + std::cmp::min(self.lamports, 1) as usize + self.address.is_some() as usize @@ -55,7 +61,7 @@ impl CompressedAccount { vec.push(&discriminator_bytes); vec.push(&data.data_hash); } - let hash = H::hashv(&vec).map_err(ProgramError::from)?; + let hash = H::hashv(&vec)?; Ok(hash) } @@ -63,7 +69,7 @@ impl CompressedAccount { &self, &merkle_tree_pubkey: &Pubkey, leaf_index: &u32, - ) -> Result<[u8; 32]> { + ) -> Result<[u8; 32], LightSdkError> { self.hash_with_hashed_values::( &hash_to_bn254_field_size_be(&self.owner.to_bytes()) .unwrap() @@ -90,7 +96,7 @@ pub struct CompressedAccountWithMerkleContext { } impl CompressedAccountWithMerkleContext { - pub fn hash(&self) -> Result<[u8; 32]> { + pub fn hash(&self) -> Result<[u8; 32], LightSdkError> { self.compressed_account.hash::( &self.merkle_context.merkle_tree_pubkey, &self.merkle_context.leaf_index, @@ -118,11 +124,11 @@ pub struct OutputCompressedAccountWithPackedContext { /// /// This function should be used for input accounts, where including only a /// hash is sufficient. -pub fn hash_input_account(account: &T) -> Result +pub fn hash_input_account(account: &T) -> Result where T: AnchorSerialize + DataHasher + Discriminator, { - let data_hash = account.hash::().map_err(ProgramError::from)?; + let data_hash = account.hash::()?; Ok(CompressedAccountData { discriminator: T::discriminator(), // Sending only data hash to the system program is sufficient. @@ -135,12 +141,14 @@ where /// /// This function should be used for output accounts, where data has to be /// included for system-program to log in the ledger. -pub fn serialize_and_hash_output_account(account: &T) -> Result +pub fn serialize_and_hash_output_account( + account: &T, +) -> Result where T: AnchorSerialize + DataHasher + Discriminator, { let data = account.try_to_vec()?; - let data_hash = account.hash::().map_err(ProgramError::from)?; + let data_hash = account.hash::()?; Ok(CompressedAccountData { discriminator: T::discriminator(), data, @@ -151,14 +159,14 @@ where pub fn pack_compressed_accounts( compressed_accounts: &[CompressedAccountWithMerkleContext], root_indices: &[u16], - remaining_accounts: &mut RemainingAccounts, + accounts: &mut LightInstructionAccounts, ) -> Vec { compressed_accounts .iter() .zip(root_indices.iter()) .map(|(x, root_index)| PackedCompressedAccountWithMerkleContext { compressed_account: x.compressed_account.clone(), - merkle_context: pack_merkle_context(&x.merkle_context, remaining_accounts), + merkle_context: pack_merkle_context(&x.merkle_context, accounts), root_index: *root_index, read_only: false, }) @@ -168,7 +176,7 @@ pub fn pack_compressed_accounts( pub fn pack_compressed_account( compressed_account: CompressedAccountWithMerkleContext, root_index: u16, - remaining_accounts: &mut RemainingAccounts, + accounts: &mut LightInstructionAccounts, ) -> PackedCompressedAccountWithMerkleContext { - pack_compressed_accounts(&[compressed_account], &[root_index], remaining_accounts)[0].clone() + pack_compressed_accounts(&[compressed_account], &[root_index], accounts)[0].clone() } diff --git a/sdk/src/constants.rs b/sdk/src/constants.rs index d995c01a51..e5a64e9813 100644 --- a/sdk/src/constants.rs +++ b/sdk/src/constants.rs @@ -4,6 +4,8 @@ use solana_program::pubkey::Pubkey; /// Seed of the CPI authority. pub const CPI_AUTHORITY_PDA_SEED: &[u8] = b"cpi_authority"; +/// ID of the Solana system program. +pub const PROGRAM_ID_SYSTEM: Pubkey = pubkey!("11111111111111111111111111111111"); /// ID of the account-compression program. pub const PROGRAM_ID_ACCOUNT_COMPRESSION: Pubkey = pubkey!("compr6CUsB5m2jS4Y3831ztGSTnDpnKJTKS95d64XVq"); diff --git a/sdk/src/error.rs b/sdk/src/error.rs index aed49eebf8..5bb22e09f6 100644 --- a/sdk/src/error.rs +++ b/sdk/src/error.rs @@ -1,33 +1,81 @@ -use anchor_lang::prelude::error_code; +use light_hasher::HasherError; +use solana_program::program_error::ProgramError; +use thiserror::Error; -#[error_code] +#[derive(Debug, Error)] pub enum LightSdkError { - #[msg("Constraint violation")] + #[error("Constraint violation")] ConstraintViolation, - #[msg("Invalid light-system-program ID")] + #[error("Invalid light-system-program ID")] InvalidLightSystemProgram, - #[msg("Expected accounts in the instruction")] + #[error("Expected accounts in the instruction")] ExpectedAccounts, - #[msg("Expected address Merkle context to be provided")] + #[error("Expected address Merkle context to be provided")] ExpectedAddressMerkleContext, - #[msg("Expected address root index to be provided")] + #[error("Expected address root index to be provided")] ExpectedAddressRootIndex, - #[msg("Accounts with a specified input are expected to have data")] + #[error("Accounts with a specified input are expected to have data")] ExpectedData, - #[msg("Accounts with specified data are expected to have a discriminator")] + #[error("Accounts with specified data are expected to have a discriminator")] ExpectedDiscriminator, - #[msg("Accounts with specified data are expected to have a hash")] + #[error("Accounts with specified data are expected to have a hash")] ExpectedHash, - #[msg("`mut` and `close` accounts are expected to have a Merkle context")] + #[error("Expected the `{0}` light account to be provided")] + ExpectedLightSystemAccount(String), + #[error("`mut` and `close` accounts are expected to have a Merkle context")] ExpectedMerkleContext, - #[msg("Expected root index to be provided")] + #[error("Expected root index to be provided")] ExpectedRootIndex, - #[msg("Cannot transfer lamports from an account without input")] + #[error("Cannot transfer lamports from an account without input")] TransferFromNoInput, - #[msg("Cannot transfer from an account without lamports")] + #[error("Cannot transfer from an account without lamports")] TransferFromNoLamports, - #[msg("Account, from which a transfer was attempted, has insufficient amount of lamports")] + #[error("Account, from which a transfer was attempted, has insufficient amount of lamports")] TransferFromInsufficientLamports, - #[msg("Integer overflow resulting from too large resulting amount")] + #[error("Integer overflow resulting from too large resulting amount")] TransferIntegerOverflow, + + #[error(transparent)] + Borsh(#[from] borsh::maybestd::io::Error), + #[error(transparent)] + Hasher(#[from] HasherError), +} + +impl From for u32 { + fn from(e: LightSdkError) -> Self { + match e { + LightSdkError::ConstraintViolation => 14001, + LightSdkError::InvalidLightSystemProgram => 14002, + LightSdkError::ExpectedAccounts => 14003, + LightSdkError::ExpectedAddressMerkleContext => 14004, + LightSdkError::ExpectedAddressRootIndex => 14005, + LightSdkError::ExpectedData => 14006, + LightSdkError::ExpectedDiscriminator => 14007, + LightSdkError::ExpectedHash => 14008, + LightSdkError::ExpectedLightSystemAccount(_) => 14009, + LightSdkError::ExpectedMerkleContext => 14010, + LightSdkError::ExpectedRootIndex => 14011, + LightSdkError::TransferFromNoInput => 14012, + LightSdkError::TransferFromNoLamports => 14013, + LightSdkError::TransferFromInsufficientLamports => 14014, + LightSdkError::TransferIntegerOverflow => 14015, + + LightSdkError::Borsh(_) => 14016, + LightSdkError::Hasher(e) => e.into(), + } + } +} + +impl From for ProgramError { + fn from(e: LightSdkError) -> Self { + solana_program::program_error::ProgramError::Custom(e.into()) + } +} + +#[cfg(feature = "anchor")] +impl From for anchor_lang::error::Error { + fn from(e: LightSdkError) -> Self { + let prog_e: ProgramError = e.into(); + prog_e.into() + } } diff --git a/sdk/src/event.rs b/sdk/src/event.rs index 8874bbada0..4dbd2d286c 100644 --- a/sdk/src/event.rs +++ b/sdk/src/event.rs @@ -1,4 +1,7 @@ +#[cfg(feature = "anchor")] use anchor_lang::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize}; use solana_program::pubkey::Pubkey; use crate::compressed_account::OutputCompressedAccountWithPackedContext; diff --git a/sdk/src/instruction_accounts.rs b/sdk/src/instruction_accounts.rs new file mode 100644 index 0000000000..58259d7565 --- /dev/null +++ b/sdk/src/instruction_accounts.rs @@ -0,0 +1,374 @@ +use std::collections::HashMap; + +use solana_program::{instruction::AccountMeta, pubkey::Pubkey}; + +use crate::{ + system_accounts::SYSTEM_ACCOUNTS_LEN, PROGRAM_ID_ACCOUNT_COMPRESSION, PROGRAM_ID_LIGHT_SYSTEM, + PROGRAM_ID_NOOP, PROGRAM_ID_SYSTEM, +}; + +/// Collection of Light Protocol system accounts which are sent to the program. +pub struct LightInstructionAccounts { + next_index: u8, + map: HashMap, + // Optional accounts. + // + // We can't include them in the hash map, because there is no way to + // handle the `None` case with it (it would mean inserting the same + // element) twice. Instead, we insert the optional accounts at the + // beginning. + sol_pool_pda: Option, + decompression_recipient: Option, +} + +impl LightInstructionAccounts { + pub fn new( + registered_program_pda: &Pubkey, + account_compression_authority: &Pubkey, + program_id: &Pubkey, + sol_pool_pda: Option<&Pubkey>, + decompression_recipient: Option<&Pubkey>, + ) -> Self { + let mut accounts = Self { + // We reserve the first two incides for `sol_pool_pd` + next_index: 2, + map: HashMap::new(), + sol_pool_pda: sol_pool_pda.cloned(), + decompression_recipient: decompression_recipient.cloned(), + }; + + accounts.insert_or_get(*registered_program_pda); + accounts.insert_or_get(PROGRAM_ID_NOOP); + accounts.insert_or_get(*account_compression_authority); + accounts.insert_or_get(PROGRAM_ID_ACCOUNT_COMPRESSION); + accounts.insert_or_get(*program_id); + accounts.insert_or_get(PROGRAM_ID_SYSTEM); + accounts.insert_or_get(PROGRAM_ID_LIGHT_SYSTEM); + + accounts + } + + /// Returns the index of the provided `pubkey` in the collection. + /// + /// If the provided `pubkey` is not a part of the collection, it gets + /// inserted with a `next_index`. + /// + /// If the privided `pubkey` already exists in the collection, its already + /// existing index is returned. + pub fn insert_or_get(&mut self, pubkey: Pubkey) -> u8 { + *self.map.entry(pubkey).or_insert_with(|| { + let index = self.next_index; + self.next_index += 1; + index + }) + } + + /// Converts the collection of accounts to a vector of + /// [`AccountMeta`](solana_sdk::instruction::AccountMeta), which can be used + /// as remaining accounts in instructions or CPI calls. + pub fn to_account_metas(&self) -> Vec { + let mut account_metas = Vec::with_capacity(self.map.len() + 2); + + // The trick for having `None` accounts is to pass any repeating public + // key, see https://github.com/coral-xyz/anchor/pull/2101 + let sol_pool_pda = self.sol_pool_pda.unwrap_or(PROGRAM_ID_LIGHT_SYSTEM); + let decompression_recipient = self + .decompression_recipient + .unwrap_or(PROGRAM_ID_LIGHT_SYSTEM); + + let mut accounts = self + .map + .iter() + .map(|(k, i)| { + let i = *i as usize; + // Only Merkle tree accouts (specified after the system ones) + // are writable. + let is_writable = i >= SYSTEM_ACCOUNTS_LEN - 2; + ( + AccountMeta { + pubkey: *k, + is_signer: false, + is_writable, + }, + i, + ) + }) + .collect::>(); + + // Hash maps are not sorted (and there is no flavor of hash maps which + // are automatically sorted **by value**), so we need to sort manually + // and collect into a vector again. + accounts.sort_by(|a, b| a.1.cmp(&b.1)); + account_metas.extend(accounts.into_iter().map(|(k, _)| k)); + + // Insert `sol_pool_pda` and `decompression_recipient` in indices + // expected by light-system-program. + account_metas.insert( + // The expected index of `sol_pool_pda` is 7. + // But we don't include `fee_payer` and `authority` here yet. + // Therefore, 7-2 = 5. + 5, + AccountMeta { + pubkey: sol_pool_pda, + is_signer: false, + is_writable: false, + }, + ); + account_metas.insert( + // The expected index of `decompression_recipient` is 8. + // But we don't include `fee_payer` and `authority` here yet. + // Therefore, 8-2 = 6. + 6, + AccountMeta { + pubkey: decompression_recipient, + is_signer: false, + is_writable: false, + }, + ); + + println!("ACCOUNT METAS INIT: {account_metas:?}"); + + account_metas + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_remaining_accounts() { + let registered_program_pda = Pubkey::new_unique(); + let account_compression_authority = Pubkey::new_unique(); + let program_id = Pubkey::new_unique(); + + let mut accounts = LightInstructionAccounts::new( + ®istered_program_pda, + &account_compression_authority, + &program_id, + None, + None, + ); + + let pubkey_1 = Pubkey::new_unique(); + let pubkey_2 = Pubkey::new_unique(); + let pubkey_3 = Pubkey::new_unique(); + let pubkey_4 = Pubkey::new_unique(); + + // Initial insertion. + assert_eq!(accounts.insert_or_get(pubkey_1), 9); + assert_eq!(accounts.insert_or_get(pubkey_2), 10); + assert_eq!(accounts.insert_or_get(pubkey_3), 11); + + assert_eq!( + accounts.to_account_metas().as_slice(), + &[ + AccountMeta { + pubkey: PROGRAM_ID_LIGHT_SYSTEM, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: PROGRAM_ID_LIGHT_SYSTEM, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: registered_program_pda, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: PROGRAM_ID_NOOP, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: account_compression_authority, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: PROGRAM_ID_ACCOUNT_COMPRESSION, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: program_id, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: PROGRAM_ID_SYSTEM, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: PROGRAM_ID_LIGHT_SYSTEM, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: pubkey_1, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: pubkey_2, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: pubkey_3, + is_signer: false, + is_writable: true, + } + ] + ); + + // Insertion of already existing pubkeys. + assert_eq!(accounts.insert_or_get(pubkey_1), 9); + assert_eq!(accounts.insert_or_get(pubkey_2), 10); + assert_eq!(accounts.insert_or_get(pubkey_3), 11); + + assert_eq!( + accounts.to_account_metas().as_slice(), + &[ + AccountMeta { + pubkey: PROGRAM_ID_LIGHT_SYSTEM, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: PROGRAM_ID_LIGHT_SYSTEM, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: registered_program_pda, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: PROGRAM_ID_NOOP, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: account_compression_authority, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: PROGRAM_ID_ACCOUNT_COMPRESSION, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: program_id, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: PROGRAM_ID_SYSTEM, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: PROGRAM_ID_LIGHT_SYSTEM, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: pubkey_1, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: pubkey_2, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: pubkey_3, + is_signer: false, + is_writable: true, + } + ] + ); + + // Again, initial insertion. + assert_eq!(accounts.insert_or_get(pubkey_4), 12); + + assert_eq!( + accounts.to_account_metas().as_slice(), + &[ + AccountMeta { + pubkey: PROGRAM_ID_LIGHT_SYSTEM, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: PROGRAM_ID_LIGHT_SYSTEM, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: registered_program_pda, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: PROGRAM_ID_NOOP, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: account_compression_authority, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: PROGRAM_ID_ACCOUNT_COMPRESSION, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: program_id, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: PROGRAM_ID_SYSTEM, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: PROGRAM_ID_LIGHT_SYSTEM, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: pubkey_1, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: pubkey_2, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: pubkey_3, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: pubkey_4, + is_signer: false, + is_writable: true, + } + ] + ); + } +} diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index cf0fab912a..07e4989dfc 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -1,3 +1,4 @@ +pub use light_hasher as hasher; pub use light_macros::*; pub use light_sdk_macros::*; @@ -8,16 +9,20 @@ pub mod address; pub mod compressed_account; pub mod constants; pub use constants::*; +#[cfg(feature = "anchor")] pub mod context; pub mod error; pub mod event; +pub mod instruction_accounts; pub mod instruction_data; pub mod legacy; pub mod merkle_context; pub mod program_merkle_context; pub mod proof; pub mod state; +pub mod system_accounts; pub mod token; +#[cfg(feature = "anchor")] pub mod traits; pub mod transfer; pub mod utils; diff --git a/sdk/src/merkle_context.rs b/sdk/src/merkle_context.rs index f74730d41f..6a8f2a19f5 100644 --- a/sdk/src/merkle_context.rs +++ b/sdk/src/merkle_context.rs @@ -1,57 +1,10 @@ -use std::collections::HashMap; +#[cfg(feature = "anchor")] +use anchor_lang::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize}; +use solana_program::pubkey::Pubkey; -use anchor_lang::prelude::{AccountMeta, AnchorDeserialize, AnchorSerialize, Pubkey}; - -/// Collection of remaining accounts which are sent to the program. -#[derive(Default)] -pub struct RemainingAccounts { - next_index: u8, - map: HashMap, -} - -impl RemainingAccounts { - /// Returns the index of the provided `pubkey` in the collection. - /// - /// If the provided `pubkey` is not a part of the collection, it gets - /// inserted with a `next_index`. - /// - /// If the privided `pubkey` already exists in the collection, its already - /// existing index is returned. - pub fn insert_or_get(&mut self, pubkey: Pubkey) -> u8 { - *self.map.entry(pubkey).or_insert_with(|| { - let index = self.next_index; - self.next_index += 1; - index - }) - } - - /// Converts the collection of accounts to a vector of - /// [`AccountMeta`](solana_sdk::instruction::AccountMeta), which can be used - /// as remaining accounts in instructions or CPI calls. - pub fn to_account_metas(&self) -> Vec { - let mut remaining_accounts = self - .map - .iter() - .map(|(k, i)| { - ( - AccountMeta { - pubkey: *k, - is_signer: false, - is_writable: true, - }, - *i as usize, - ) - }) - .collect::>(); - // hash maps are not sorted so we need to sort manually and collect into a vector again - remaining_accounts.sort_by(|a, b| a.1.cmp(&b.1)); - let remaining_accounts = remaining_accounts - .iter() - .map(|(k, _)| k.clone()) - .collect::>(); - remaining_accounts - } -} +use crate::instruction_accounts::LightInstructionAccounts; #[derive(Debug, Clone, Copy, AnchorSerialize, AnchorDeserialize, PartialEq, Default)] pub struct QueueIndex { @@ -83,7 +36,7 @@ pub struct PackedMerkleContext { pub fn pack_merkle_contexts<'a, I>( merkle_contexts: I, - remaining_accounts: &'a mut RemainingAccounts, + remaining_accounts: &'a mut LightInstructionAccounts, ) -> impl Iterator + 'a where I: Iterator + 'a, @@ -93,7 +46,7 @@ where pub fn pack_merkle_context( merkle_context: &MerkleContext, - remaining_accounts: &mut RemainingAccounts, + remaining_accounts: &mut LightInstructionAccounts, ) -> PackedMerkleContext { let MerkleContext { merkle_tree_pubkey, @@ -128,7 +81,7 @@ pub struct PackedAddressMerkleContext { /// `remaining_accounts` based on the given `merkle_contexts`. pub fn pack_address_merkle_contexts<'a, I>( address_merkle_contexts: I, - remaining_accounts: &'a mut RemainingAccounts, + remaining_accounts: &'a mut LightInstructionAccounts, ) -> impl Iterator + 'a where I: Iterator + 'a, @@ -140,7 +93,7 @@ where /// based on the given `merkle_context`. pub fn pack_address_merkle_context( address_merkle_context: &AddressMerkleContext, - remaining_accounts: &mut RemainingAccounts, + remaining_accounts: &mut LightInstructionAccounts, ) -> PackedAddressMerkleContext { let AddressMerkleContext { address_merkle_tree_pubkey, @@ -160,100 +113,21 @@ pub fn pack_address_merkle_context( mod test { use super::*; - #[test] - fn test_remaining_accounts() { - let mut remaining_accounts = RemainingAccounts::default(); - - let pubkey_1 = Pubkey::new_unique(); - let pubkey_2 = Pubkey::new_unique(); - let pubkey_3 = Pubkey::new_unique(); - let pubkey_4 = Pubkey::new_unique(); - - // Initial insertion. - assert_eq!(remaining_accounts.insert_or_get(pubkey_1), 0); - assert_eq!(remaining_accounts.insert_or_get(pubkey_2), 1); - assert_eq!(remaining_accounts.insert_or_get(pubkey_3), 2); - - assert_eq!( - remaining_accounts.to_account_metas().as_slice(), - &[ - AccountMeta { - pubkey: pubkey_1, - is_signer: false, - is_writable: true, - }, - AccountMeta { - pubkey: pubkey_2, - is_signer: false, - is_writable: true, - }, - AccountMeta { - pubkey: pubkey_3, - is_signer: false, - is_writable: true, - } - ] - ); - - // Insertion of already existing pubkeys. - assert_eq!(remaining_accounts.insert_or_get(pubkey_1), 0); - assert_eq!(remaining_accounts.insert_or_get(pubkey_2), 1); - assert_eq!(remaining_accounts.insert_or_get(pubkey_3), 2); - - assert_eq!( - remaining_accounts.to_account_metas().as_slice(), - &[ - AccountMeta { - pubkey: pubkey_1, - is_signer: false, - is_writable: true, - }, - AccountMeta { - pubkey: pubkey_2, - is_signer: false, - is_writable: true, - }, - AccountMeta { - pubkey: pubkey_3, - is_signer: false, - is_writable: true, - } - ] - ); - - // Again, initial insertion. - assert_eq!(remaining_accounts.insert_or_get(pubkey_4), 3); - - assert_eq!( - remaining_accounts.to_account_metas().as_slice(), - &[ - AccountMeta { - pubkey: pubkey_1, - is_signer: false, - is_writable: true, - }, - AccountMeta { - pubkey: pubkey_2, - is_signer: false, - is_writable: true, - }, - AccountMeta { - pubkey: pubkey_3, - is_signer: false, - is_writable: true, - }, - AccountMeta { - pubkey: pubkey_4, - is_signer: false, - is_writable: true, - } - ] - ); - } + use crate::instruction_accounts::LightInstructionAccounts; #[test] fn test_pack_merkle_context() { - let mut remaining_accounts = RemainingAccounts::default(); + let registered_program_pda = Pubkey::new_unique(); + let account_compression_authority = Pubkey::new_unique(); + let program_id = Pubkey::new_unique(); + + let mut accounts = LightInstructionAccounts::new( + ®istered_program_pda, + &account_compression_authority, + &program_id, + None, + None, + ); let merkle_tree_pubkey = Pubkey::new_unique(); let nullifier_queue_pubkey = Pubkey::new_unique(); @@ -264,12 +138,12 @@ mod test { queue_index: None, }; - let packed_merkle_context = pack_merkle_context(&merkle_context, &mut remaining_accounts); + let packed_merkle_context = pack_merkle_context(&merkle_context, &mut accounts); assert_eq!( packed_merkle_context, PackedMerkleContext { - merkle_tree_pubkey_index: 0, - nullifier_queue_pubkey_index: 1, + merkle_tree_pubkey_index: 9, + nullifier_queue_pubkey_index: 10, leaf_index: 69, queue_index: None, } @@ -278,7 +152,17 @@ mod test { #[test] fn test_pack_merkle_contexts() { - let mut remaining_accounts = RemainingAccounts::default(); + let registered_program_pda = Pubkey::new_unique(); + let account_compression_authority = Pubkey::new_unique(); + let program_id = Pubkey::new_unique(); + + let mut accounts = LightInstructionAccounts::new( + ®istered_program_pda, + &account_compression_authority, + &program_id, + None, + None, + ); let merkle_contexts = &[ MerkleContext { @@ -304,20 +188,19 @@ mod test { }, ]; - let packed_merkle_contexts = - pack_merkle_contexts(merkle_contexts.iter(), &mut remaining_accounts); + let packed_merkle_contexts = pack_merkle_contexts(merkle_contexts.iter(), &mut accounts); assert_eq!( packed_merkle_contexts.collect::>(), &[ PackedMerkleContext { - merkle_tree_pubkey_index: 0, - nullifier_queue_pubkey_index: 1, + merkle_tree_pubkey_index: 9, + nullifier_queue_pubkey_index: 10, leaf_index: 10, queue_index: None }, PackedMerkleContext { - merkle_tree_pubkey_index: 2, - nullifier_queue_pubkey_index: 3, + merkle_tree_pubkey_index: 11, + nullifier_queue_pubkey_index: 12, leaf_index: 11, queue_index: Some(QueueIndex { queue_id: 69, @@ -325,8 +208,8 @@ mod test { }) }, PackedMerkleContext { - merkle_tree_pubkey_index: 4, - nullifier_queue_pubkey_index: 5, + merkle_tree_pubkey_index: 13, + nullifier_queue_pubkey_index: 14, leaf_index: 12, queue_index: None, } @@ -336,7 +219,17 @@ mod test { #[test] fn test_pack_address_merkle_context() { - let mut remaining_accounts = RemainingAccounts::default(); + let registered_program_pda = Pubkey::new_unique(); + let account_compression_authority = Pubkey::new_unique(); + let program_id = Pubkey::new_unique(); + + let mut accounts = LightInstructionAccounts::new( + ®istered_program_pda, + &account_compression_authority, + &program_id, + None, + None, + ); let address_merkle_context = AddressMerkleContext { address_merkle_tree_pubkey: Pubkey::new_unique(), @@ -344,19 +237,29 @@ mod test { }; let packed_address_merkle_context = - pack_address_merkle_context(&address_merkle_context, &mut remaining_accounts); + pack_address_merkle_context(&address_merkle_context, &mut accounts); assert_eq!( packed_address_merkle_context, PackedAddressMerkleContext { - address_merkle_tree_pubkey_index: 0, - address_queue_pubkey_index: 1, + address_merkle_tree_pubkey_index: 9, + address_queue_pubkey_index: 10, } ) } #[test] fn test_pack_address_merkle_contexts() { - let mut remaining_accounts = RemainingAccounts::default(); + let registered_program_pda = Pubkey::new_unique(); + let account_compression_authority = Pubkey::new_unique(); + let program_id = Pubkey::new_unique(); + + let mut accounts = LightInstructionAccounts::new( + ®istered_program_pda, + &account_compression_authority, + &program_id, + None, + None, + ); let address_merkle_contexts = &[ AddressMerkleContext { @@ -374,21 +277,21 @@ mod test { ]; let packed_address_merkle_contexts = - pack_address_merkle_contexts(address_merkle_contexts.iter(), &mut remaining_accounts); + pack_address_merkle_contexts(address_merkle_contexts.iter(), &mut accounts); assert_eq!( packed_address_merkle_contexts.collect::>(), &[ PackedAddressMerkleContext { - address_merkle_tree_pubkey_index: 0, - address_queue_pubkey_index: 1, + address_merkle_tree_pubkey_index: 9, + address_queue_pubkey_index: 10, }, PackedAddressMerkleContext { - address_merkle_tree_pubkey_index: 2, - address_queue_pubkey_index: 3, + address_merkle_tree_pubkey_index: 11, + address_queue_pubkey_index: 12, }, PackedAddressMerkleContext { - address_merkle_tree_pubkey_index: 4, - address_queue_pubkey_index: 5, + address_merkle_tree_pubkey_index: 13, + address_queue_pubkey_index: 14, } ] ); diff --git a/sdk/src/proof.rs b/sdk/src/proof.rs index 9d3ec8608c..e29ba58386 100644 --- a/sdk/src/proof.rs +++ b/sdk/src/proof.rs @@ -1,4 +1,7 @@ +#[cfg(feature = "anchor")] use anchor_lang::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize}; use light_indexed_merkle_tree::array::IndexedElement; use num_bigint::BigUint; use solana_program::pubkey::Pubkey; diff --git a/sdk/src/state.rs b/sdk/src/state.rs index 0281759baf..75e0fe25b5 100644 --- a/sdk/src/state.rs +++ b/sdk/src/state.rs @@ -1,4 +1,7 @@ +#[cfg(feature = "anchor")] use anchor_lang::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize}; use solana_program::pubkey::Pubkey; #[derive(AnchorDeserialize, AnchorSerialize, Debug, PartialEq, Default)] diff --git a/sdk/src/system_accounts.rs b/sdk/src/system_accounts.rs new file mode 100644 index 0000000000..f3a2f1d5e0 --- /dev/null +++ b/sdk/src/system_accounts.rs @@ -0,0 +1,143 @@ +use solana_program::{account_info::AccountInfo, instruction::AccountMeta}; + +#[repr(usize)] +pub enum LightSystemAccountIndex { + FeePayer = 0, + Authority, + RegisteredProgramPda, + NoopProgram, + AccountCompressionAuthority, + AccountCompressionProgram, + InvokingProgram, + SolPoolPda, + DecompressionRecipent, + SystemProgram, + LightSystemProgram, +} + +pub const SYSTEM_ACCOUNTS_LEN: usize = 11; + +pub struct LightCpiAccounts<'c, 'info> { + accounts: Vec<&'c AccountInfo<'info>>, +} + +impl<'c, 'info> LightCpiAccounts<'c, 'info> { + pub fn new( + fee_payer: &'c AccountInfo<'info>, + authority: &'c AccountInfo<'info>, + accounts: &'c [I], + ) -> Self + where + I: AsRef>, + { + let mut cpi_accounts = Vec::with_capacity(accounts.len() + 2); + cpi_accounts.push(fee_payer.as_ref()); + cpi_accounts.push(authority.as_ref()); + + cpi_accounts.extend(accounts.into_iter().map(|acc| acc.as_ref())); + + Self { + accounts: cpi_accounts, + } + } + + pub fn new_with_start_index( + fee_payer: &'c AccountInfo<'info>, + authority: &'c AccountInfo<'info>, + accounts: &'c [I], + start_index: usize, + ) -> Self + where + I: AsRef>, + { + let mut cpi_accounts = Vec::with_capacity(SYSTEM_ACCOUNTS_LEN); + cpi_accounts.push(fee_payer.as_ref()); + cpi_accounts.push(authority.as_ref()); + + // `split_at` doesn't make any copies. + + // Skip the `start_index` elements. + let (_, accounts) = accounts.split_at(start_index); + // Take `SYSTEM_ACCOUNTS_LEN` elements, minus `fee_payer` and + // `authority` + let (accounts, _) = accounts.split_at(SYSTEM_ACCOUNTS_LEN - 2); + cpi_accounts.extend(accounts.into_iter().map(|acc| acc.as_ref())); + + Self { + accounts: cpi_accounts, + } + } + + pub fn fee_payer(&self) -> &'c AccountInfo<'info> { + // PANICS: We are sure about the bounds of the slice. + self.accounts + .get(LightSystemAccountIndex::FeePayer as usize) + .unwrap() + } + + pub fn authority(&self) -> &'c AccountInfo<'info> { + // PANICS: We are sure about the bounds of the slice. + self.accounts + .get(LightSystemAccountIndex::Authority as usize) + .unwrap() + } + + pub fn invoking_program(&self) -> &'c AccountInfo<'info> { + // PANICS: We are sure about the bounds of the slice. + self.accounts + .get(LightSystemAccountIndex::InvokingProgram as usize) + .unwrap() + } + + pub fn light_system_program(&self) -> &'c AccountInfo<'info> { + // PANICS: We are sure about the bounds of the slice. + self.accounts + .get(LightSystemAccountIndex::LightSystemProgram as usize) + .unwrap() + } + + #[inline(always)] + pub fn setup_cpi_accounts(&self) -> (Vec>, Vec) { + let account_infos = self + .accounts + .iter() + // Skip `light_system_program`, it shouldn't be passed as a part + // of `account_infos` in `invoke_signed`. + // .take(SYSTEM_ACCOUNTS_LEN - 1) + .map(|acc| acc.as_ref().to_owned()) + .collect::>(); + let account_metas = account_infos + .iter() + .enumerate() + .map(|(i, acc)| { + if i == LightSystemAccountIndex::FeePayer as usize { + AccountMeta { + pubkey: acc.key.to_owned(), + is_signer: true, + is_writable: true, + } + } else if i == LightSystemAccountIndex::Authority as usize { + AccountMeta { + pubkey: acc.key.to_owned(), + is_signer: true, + is_writable: false, + } + } else if i < SYSTEM_ACCOUNTS_LEN { + AccountMeta { + pubkey: acc.key.to_owned(), + is_signer: false, + is_writable: false, + } + } else { + AccountMeta { + pubkey: acc.key.to_owned(), + is_signer: false, + is_writable: true, + } + } + }) + .collect::>(); + + (account_infos, account_metas) + } +} diff --git a/sdk/src/token.rs b/sdk/src/token.rs index 1e7417bb2c..a5604a0000 100644 --- a/sdk/src/token.rs +++ b/sdk/src/token.rs @@ -1,4 +1,7 @@ +#[cfg(feature = "anchor")] use anchor_lang::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize}; use solana_program::pubkey::Pubkey; use crate::compressed_account::CompressedAccountWithMerkleContext; diff --git a/sdk/src/transfer.rs b/sdk/src/transfer.rs index 86fe81cbcb..e09015c790 100644 --- a/sdk/src/transfer.rs +++ b/sdk/src/transfer.rs @@ -1,5 +1,3 @@ -use anchor_lang::Result; - use crate::{account_info::LightAccountInfo, error::LightSdkError}; /// Transfers a specified amount of lamports from one account to another. @@ -11,7 +9,7 @@ pub fn transfer_compressed_sol( from: &mut LightAccountInfo, to: &mut LightAccountInfo, lamports: u64, -) -> Result<()> { +) -> Result<(), LightSdkError> { let output_from = from .input .as_ref() @@ -109,7 +107,7 @@ mod tests { let mut to = mock_account(&to_pubkey, Some(500)); let result = transfer_compressed_sol(&mut from, &mut to, 300); - assert_eq!(result, Err(LightSdkError::TransferFromNoInput.into())); + assert!(matches!(result, Err(LightSdkError::TransferFromNoInput))); } #[test] @@ -120,7 +118,7 @@ mod tests { let mut to = mock_account(&to_pubkey, Some(500)); let result = transfer_compressed_sol(&mut from, &mut to, 300); - assert_eq!(result, Err(LightSdkError::TransferFromNoLamports.into())); + assert!(matches!(result, Err(LightSdkError::TransferFromNoLamports))); } #[test] @@ -131,10 +129,10 @@ mod tests { let mut to = mock_account(&to_pubkey, Some(500)); let result = transfer_compressed_sol(&mut from, &mut to, 300); - assert_eq!( + assert!(matches!( result, - Err(LightSdkError::TransferFromInsufficientLamports.into()) - ); + Err(LightSdkError::TransferFromInsufficientLamports), + )); } #[test] @@ -145,7 +143,10 @@ mod tests { let mut to = mock_account(&to_pubkey, Some(u64::MAX - 500)); let result = transfer_compressed_sol(&mut from, &mut to, 600); - assert_eq!(result, Err(LightSdkError::TransferIntegerOverflow.into())); + assert!(matches!( + result, + Err(LightSdkError::TransferIntegerOverflow) + )); } #[test] diff --git a/sdk/src/utils.rs b/sdk/src/utils.rs index 1fd006cb38..cc2f3730b1 100644 --- a/sdk/src/utils.rs +++ b/sdk/src/utils.rs @@ -1,14 +1,6 @@ -use anchor_lang::solana_program::pubkey::Pubkey; +use solana_program::pubkey::Pubkey; -use crate::{ - address::PackedNewAddressParams, - compressed_account::{ - OutputCompressedAccountWithPackedContext, PackedCompressedAccountWithMerkleContext, - }, - proof::CompressedProof, - verify::{CompressedCpiContext, InstructionDataInvokeCpi}, - PROGRAM_ID_ACCOUNT_COMPRESSION, -}; +use crate::PROGRAM_ID_ACCOUNT_COMPRESSION; pub fn get_registered_program_pda(program_id: &Pubkey) -> Pubkey { Pubkey::find_program_address( @@ -21,57 +13,3 @@ pub fn get_registered_program_pda(program_id: &Pubkey) -> Pubkey { pub fn get_cpi_authority_pda(program_id: &Pubkey) -> Pubkey { Pubkey::find_program_address(&[b"cpi_authority"], program_id).0 } - -/// Helper function to create data for creating a single PDA. -pub fn create_cpi_inputs_for_new_account( - proof: CompressedProof, - new_address_params: PackedNewAddressParams, - compressed_pda: OutputCompressedAccountWithPackedContext, - 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![], - output_compressed_accounts: vec![compressed_pda], - compress_or_decompress_lamports: None, - is_compress: false, - cpi_context, - } -} - -pub fn create_cpi_inputs_for_account_update( - proof: CompressedProof, - old_compressed_pda: PackedCompressedAccountWithMerkleContext, - new_compressed_pda: OutputCompressedAccountWithPackedContext, - 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, - cpi_context, - } -} - -pub fn create_cpi_inputs_for_account_deletion( - proof: CompressedProof, - compressed_pda: PackedCompressedAccountWithMerkleContext, - 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, - cpi_context, - } -} diff --git a/sdk/src/verify.rs b/sdk/src/verify.rs index 7ddad050b8..6096994439 100644 --- a/sdk/src/verify.rs +++ b/sdk/src/verify.rs @@ -1,19 +1,26 @@ -use anchor_lang::{prelude::*, Bumps}; +#[cfg(feature = "anchor")] +use anchor_lang::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize}; use light_hasher::{DataHasher, Discriminator}; -use solana_program::{instruction::Instruction, program::invoke_signed}; +use solana_program::{ + account_info::AccountInfo, + instruction::{AccountMeta, Instruction}, + program::invoke_signed, + program_error::ProgramError, + pubkey::Pubkey, +}; use crate::{ account::LightAccount, + account_info::LightAccountInfo, address::PackedNewAddressParams, compressed_account::{ OutputCompressedAccountWithPackedContext, PackedCompressedAccountWithMerkleContext, }, error::LightSdkError, proof::{CompressedProof, ProofRpcResult}, - traits::{ - InvokeAccounts, InvokeCpiAccounts, InvokeCpiContextAccount, LightSystemAccount, - SignerAccounts, - }, + system_accounts::LightCpiAccounts, CPI_AUTHORITY_PDA_SEED, PROGRAM_ID_LIGHT_SYSTEM, }; @@ -46,165 +53,32 @@ pub struct InstructionDataInvokeCpi { pub cpi_context: Option, } -#[inline(always)] -pub fn setup_cpi_accounts<'info>( - ctx: &Context< - '_, - '_, - '_, - 'info, - impl InvokeAccounts<'info> - + LightSystemAccount<'info> - + InvokeCpiAccounts<'info> - + SignerAccounts<'info> - + InvokeCpiContextAccount<'info> - + Bumps, - >, -) -> (Vec>, Vec) { - // The trick for having `None` accounts is to pass program ID, see - // https://github.com/coral-xyz/anchor/pull/2101 - let none_account_info = ctx.accounts.get_light_system_program().to_account_info(); - - let (cpi_context_account_info, cpi_context_account_meta) = - match ctx.accounts.get_cpi_context_account() { - Some(acc) => ( - acc.to_account_info(), - AccountMeta { - pubkey: acc.key(), - is_signer: false, - is_writable: true, - }, - ), - None => ( - none_account_info.clone(), - AccountMeta { - pubkey: ctx.accounts.get_light_system_program().key(), - is_signer: false, - is_writable: false, - }, - ), - }; - - let mut account_infos = vec![ - // fee_payer - ctx.accounts.get_fee_payer().to_account_info(), - // authority - ctx.accounts.get_authority().to_account_info(), - // registered_program_pda - ctx.accounts.get_registered_program_pda().to_account_info(), - // noop_program - ctx.accounts.get_noop_program().to_account_info(), - // account_compression_authority - ctx.accounts - .get_account_compression_authority() - .to_account_info(), - // account_compression_program - ctx.accounts - .get_account_compression_program() - .to_account_info(), - // invoking_program - ctx.accounts.get_invoking_program().to_account_info(), - // sol_pool_pda - none_account_info.clone(), - // decompression_recipient - none_account_info, - // system_program - ctx.accounts.get_system_program().to_account_info(), - // cpi_context_account - cpi_context_account_info, - ]; - for remaining_account in ctx.remaining_accounts { - account_infos.push(remaining_account.to_owned()); - } - - let mut account_metas = vec![ - // fee_payer - AccountMeta { - pubkey: account_infos[0].key(), - is_signer: true, - is_writable: true, - }, - // authority - AccountMeta { - pubkey: account_infos[1].key(), - is_signer: true, - is_writable: false, - }, - // registered_program_pda - AccountMeta { - pubkey: account_infos[2].key(), - is_signer: false, - is_writable: false, - }, - // noop_program - AccountMeta { - pubkey: account_infos[3].key(), - is_signer: false, - is_writable: false, - }, - // account_compression_authority - AccountMeta { - pubkey: account_infos[4].key(), - is_signer: false, - is_writable: false, - }, - // account_compression_program - AccountMeta { - pubkey: account_infos[5].key(), - is_signer: false, - is_writable: false, - }, - // invoking_program - AccountMeta { - pubkey: account_infos[6].key(), - is_signer: false, - is_writable: false, - }, - // sol_pool_pda - AccountMeta { - pubkey: account_infos[7].key(), - is_signer: false, - is_writable: false, - }, - // decompression_recipient - AccountMeta { - pubkey: account_infos[8].key(), - is_signer: false, - is_writable: false, - }, - // system_program - AccountMeta { - pubkey: account_infos[9].key(), - is_signer: false, - is_writable: false, - }, - cpi_context_account_meta, - ]; - for remaining_account in ctx.remaining_accounts { - account_metas.extend(remaining_account.to_account_metas(None)); - } - - (account_infos, account_metas) -} - #[derive(AnchorDeserialize, AnchorSerialize)] pub struct InvokeCpi { pub inputs: Vec, } #[inline(always)] -pub fn invoke_cpi( - account_infos: &[AccountInfo], +pub fn invoke_cpi<'info>( + account_infos: &[AccountInfo<'info>], accounts_metas: Vec, inputs: Vec, signer_seeds: &[&[&[u8]]], -) -> Result<()> { +) -> Result<(), ProgramError> { let instruction_data = InvokeCpi { inputs }; // `InvokeCpi`'s discriminator let mut data = [49, 212, 191, 129, 39, 194, 43, 196].to_vec(); data.extend(instruction_data.try_to_vec()?); + #[cfg(feature = "anchor")] + { + anchor_lang::prelude::msg!("ACCOUNT METAS (len: {}):", accounts_metas.len(),); + for (i, acc_meta) in accounts_metas.iter().enumerate() { + anchor_lang::prelude::msg!("{}: {:?}", i, acc_meta); + } + } + let instruction = Instruction { program_id: PROGRAM_ID_LIGHT_SYSTEM, accounts: accounts_metas, @@ -218,61 +92,87 @@ pub fn invoke_cpi( /// Invokes the light system program to verify and apply a zk-compressed state /// transition. Serializes CPI instruction data, configures necessary accounts, /// and executes the CPI. -pub fn verify<'info, 'a, 'b, 'c, T>( - ctx: &Context< - '_, - '_, - '_, - 'info, - impl InvokeAccounts<'info> - + LightSystemAccount<'info> - + InvokeCpiAccounts<'info> - + SignerAccounts<'info> - + InvokeCpiContextAccount<'info> - + Bumps, - >, +fn verify<'c, 'info, T>( + light_system_accounts: &'c LightCpiAccounts<'c, 'info>, inputs: &T, - signer_seeds: &'a [&'b [&'c [u8]]], -) -> Result<()> + signer_seeds: &[&[&[u8]]], +) -> Result<(), ProgramError> where T: AnchorSerialize, { - if ctx.accounts.get_light_system_program().key() != PROGRAM_ID_LIGHT_SYSTEM { - return err!(LightSdkError::InvalidLightSystemProgram); + if light_system_accounts.light_system_program().key != &PROGRAM_ID_LIGHT_SYSTEM { + return Err(LightSdkError::InvalidLightSystemProgram.into()); } let inputs = inputs.try_to_vec()?; - let (account_infos, account_metas) = setup_cpi_accounts(ctx); + let (account_infos, account_metas) = light_system_accounts.setup_cpi_accounts(); invoke_cpi(&account_infos, account_metas, inputs, signer_seeds)?; Ok(()) } -pub fn verify_light_accounts<'info, T>( - ctx: &Context< - '_, - '_, - '_, - 'info, - impl InvokeAccounts<'info> - + LightSystemAccount<'info> - + InvokeCpiAccounts<'info> - + SignerAccounts<'info> - + InvokeCpiContextAccount<'info> - + Bumps, - >, +pub fn verify_light_account_infos<'a, 'b, 'c, 'info>( + light_cpi_accounts: &'c LightCpiAccounts<'c, 'info>, + proof: Option, + light_accounts: &[LightAccountInfo], + compress_or_decompress_lamports: Option, + is_compress: bool, + cpi_context: Option, +) -> Result<(), ProgramError> { + let bump = Pubkey::find_program_address( + &[CPI_AUTHORITY_PDA_SEED], + light_cpi_accounts.invoking_program().key, + ) + .1; + let signer_seeds = [CPI_AUTHORITY_PDA_SEED, &[bump]]; + + let mut new_address_params = Vec::with_capacity(light_accounts.len()); + let mut input_compressed_accounts_with_merkle_context = + Vec::with_capacity(light_accounts.len()); + let mut output_compressed_accounts = Vec::with_capacity(light_accounts.len()); + + for light_account in light_accounts.iter() { + if let Some(new_address_param) = light_account.new_address_params() { + new_address_params.push(new_address_param); + } + if let Some(input_account) = light_account.input_compressed_account()? { + input_compressed_accounts_with_merkle_context.push(input_account); + } + if let Some(output_account) = light_account.output_compressed_account()? { + output_compressed_accounts.push(output_account); + } + } + + let instruction = InstructionDataInvokeCpi { + proof: proof.map(|proof| proof.proof), + new_address_params, + relay_fee: None, + input_compressed_accounts_with_merkle_context, + output_compressed_accounts, + compress_or_decompress_lamports, + is_compress, + cpi_context, + }; + + verify(light_cpi_accounts, &instruction, &[&signer_seeds[..]])?; + + Ok(()) +} + +pub fn verify_light_accounts<'a, 'b, 'c, 'info, T>( + light_cpi_accounts: &'c LightCpiAccounts<'c, 'info>, proof: Option, light_accounts: &[LightAccount], compress_or_decompress_lamports: Option, is_compress: bool, cpi_context: Option, -) -> Result<()> +) -> Result<(), ProgramError> where T: AnchorDeserialize + AnchorSerialize + Clone + DataHasher + Default + Discriminator, { let bump = Pubkey::find_program_address( &[CPI_AUTHORITY_PDA_SEED], - &ctx.accounts.get_invoking_program().key(), + light_cpi_accounts.invoking_program().key, ) .1; let signer_seeds = [CPI_AUTHORITY_PDA_SEED, &[bump]]; @@ -305,7 +205,7 @@ where cpi_context, }; - verify(ctx, &instruction, &[&signer_seeds[..]])?; + verify(light_cpi_accounts, &instruction, &[&signer_seeds[..]])?; Ok(()) } diff --git a/test-programs/sdk-test-program/.gitignore b/test-programs/sdk-anchor-test/.gitignore similarity index 100% rename from test-programs/sdk-test-program/.gitignore rename to test-programs/sdk-anchor-test/.gitignore diff --git a/test-programs/sdk-test-program/.prettierignore b/test-programs/sdk-anchor-test/.prettierignore similarity index 100% rename from test-programs/sdk-test-program/.prettierignore rename to test-programs/sdk-anchor-test/.prettierignore diff --git a/test-programs/sdk-test-program/Anchor.toml b/test-programs/sdk-anchor-test/Anchor.toml similarity index 100% rename from test-programs/sdk-test-program/Anchor.toml rename to test-programs/sdk-anchor-test/Anchor.toml diff --git a/test-programs/sdk-test-program/README.md b/test-programs/sdk-anchor-test/README.md similarity index 100% rename from test-programs/sdk-test-program/README.md rename to test-programs/sdk-anchor-test/README.md diff --git a/test-programs/sdk-test-program/package.json b/test-programs/sdk-anchor-test/package.json similarity index 100% rename from test-programs/sdk-test-program/package.json rename to test-programs/sdk-anchor-test/package.json diff --git a/test-programs/sdk-test-program/programs/sdk-test/Cargo.toml b/test-programs/sdk-anchor-test/programs/sdk-anchor-test/Cargo.toml similarity index 84% rename from test-programs/sdk-test-program/programs/sdk-test/Cargo.toml rename to test-programs/sdk-anchor-test/programs/sdk-anchor-test/Cargo.toml index 9b55b9bb02..a010fdc117 100644 --- a/test-programs/sdk-test-program/programs/sdk-test/Cargo.toml +++ b/test-programs/sdk-anchor-test/programs/sdk-anchor-test/Cargo.toml @@ -1,14 +1,14 @@ [package] -name = "sdk-test" +name = "sdk-anchor-test" version = "0.7.0" -description = "Test program for Light SDK and Light Macros" +description = "Test program for Light SDK and Light Macros with Anchor" edition = "2021" rust-version = "1.75.0" license = "Apache-2.0" [lib] crate-type = ["cdylib", "lib"] -name = "sdk_test" +name = "sdk_anchor_test" [features] no-entrypoint = [] @@ -25,7 +25,7 @@ anchor-lang = { workspace=true} borsh = { workspace = true } light-hasher = { workspace = true, features = ["solana"] } light-macros = { workspace = true } -light-sdk = { workspace = true } +light-sdk = { workspace = true, features = ["anchor"] } light-sdk-macros = { workspace = true } light-utils = { workspace = true } light-verifier = { workspace = true } diff --git a/test-programs/sdk-test-program/programs/sdk-test/Xargo.toml b/test-programs/sdk-anchor-test/programs/sdk-anchor-test/Xargo.toml similarity index 100% rename from test-programs/sdk-test-program/programs/sdk-test/Xargo.toml rename to test-programs/sdk-anchor-test/programs/sdk-anchor-test/Xargo.toml diff --git a/test-programs/sdk-test-program/programs/sdk-test/src/lib.rs b/test-programs/sdk-anchor-test/programs/sdk-anchor-test/src/lib.rs similarity index 100% rename from test-programs/sdk-test-program/programs/sdk-test/src/lib.rs rename to test-programs/sdk-anchor-test/programs/sdk-anchor-test/src/lib.rs diff --git a/test-programs/sdk-test-program/programs/sdk-test/tests/test.rs b/test-programs/sdk-anchor-test/programs/sdk-anchor-test/tests/test.rs similarity index 90% rename from test-programs/sdk-test-program/programs/sdk-test/tests/test.rs rename to test-programs/sdk-anchor-test/programs/sdk-anchor-test/tests/test.rs index c5cdc424a9..d5568da4d5 100644 --- a/test-programs/sdk-test-program/programs/sdk-test/tests/test.rs +++ b/test-programs/sdk-anchor-test/programs/sdk-anchor-test/tests/test.rs @@ -15,16 +15,18 @@ use light_sdk::verify::find_cpi_signer; use light_sdk::{PROGRAM_ID_ACCOUNT_COMPRESSION, PROGRAM_ID_LIGHT_SYSTEM, PROGRAM_ID_NOOP}; use light_test_utils::test_env::{setup_test_programs_with_accounts_v2, EnvAccounts}; use light_test_utils::{RpcConnection, RpcError}; -use sdk_test::{MyCompressedAccount, NestedData}; +use sdk_anchor_test::{MyCompressedAccount, NestedData}; use solana_sdk::instruction::Instruction; use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::{Keypair, Signer}; #[tokio::test] async fn test_sdk_test() { - let (mut rpc, env) = - setup_test_programs_with_accounts_v2(Some(vec![(String::from("sdk_test"), sdk_test::ID)])) - .await; + let (mut rpc, env) = setup_test_programs_with_accounts_v2(Some(vec![( + String::from("sdk_anchor_test"), + sdk_anchor_test::ID, + )])) + .await; let payer = rpc.get_payer().insecure_clone(); let mut test_indexer: TestIndexer = TestIndexer::new( @@ -52,7 +54,7 @@ async fn test_sdk_test() { let (address, _) = derive_address( &[b"compressed", b"test"], &address_merkle_context, - &sdk_test::ID, + &sdk_anchor_test::ID, ); let account_compression_authority = get_cpi_authority_pda(&PROGRAM_ID_LIGHT_SYSTEM); @@ -78,7 +80,7 @@ async fn test_sdk_test() { .unwrap(); // Check that it was created correctly. - let compressed_accounts = test_indexer.get_compressed_accounts_by_owner(&sdk_test::ID); + let compressed_accounts = test_indexer.get_compressed_accounts_by_owner(&sdk_anchor_test::ID); assert_eq!(compressed_accounts.len(), 1); let compressed_account = &compressed_accounts[0]; let record = &compressed_account @@ -118,7 +120,7 @@ async fn test_sdk_test() { .unwrap(); // Check that it was updated correctly. - let compressed_accounts = test_indexer.get_compressed_accounts_by_owner(&sdk_test::ID); + let compressed_accounts = test_indexer.get_compressed_accounts_by_owner(&sdk_anchor_test::ID); assert_eq!(compressed_accounts.len(), 1); let compressed_account = &compressed_accounts[0]; let record = &compressed_account @@ -174,18 +176,18 @@ where }; let inputs = inputs.serialize().unwrap(); - let instruction_data = sdk_test::instruction::WithNestedData { inputs, name }; + let instruction_data = sdk_anchor_test::instruction::WithNestedData { inputs, name }; - let cpi_signer = find_cpi_signer(&sdk_test::ID); + let cpi_signer = find_cpi_signer(&sdk_anchor_test::ID); - let accounts = sdk_test::accounts::WithNestedData { + let accounts = sdk_anchor_test::accounts::WithNestedData { signer: payer.pubkey(), light_system_program: *light_system_program, account_compression_program: PROGRAM_ID_ACCOUNT_COMPRESSION, account_compression_authority: *account_compression_authority, registered_program_pda: *registered_program_pda, noop_program: PROGRAM_ID_NOOP, - self_program: sdk_test::ID, + self_program: sdk_anchor_test::ID, cpi_signer, system_program: solana_sdk::system_program::id(), }; @@ -193,7 +195,7 @@ where let remaining_accounts = remaining_accounts.to_account_metas(); let instruction = Instruction { - program_id: sdk_test::ID, + program_id: sdk_anchor_test::ID, accounts: [accounts.to_account_metas(Some(true)), remaining_accounts].concat(), data: instruction_data.data(), }; @@ -244,21 +246,21 @@ where accounts: Some(vec![compressed_account]), }; let inputs = inputs.serialize().unwrap(); - let instruction_data = sdk_test::instruction::UpdateNestedData { + let instruction_data = sdk_anchor_test::instruction::UpdateNestedData { inputs, nested_data, }; - let cpi_signer = find_cpi_signer(&sdk_test::ID); + let cpi_signer = find_cpi_signer(&sdk_anchor_test::ID); - let accounts = sdk_test::accounts::UpdateNestedData { + let accounts = sdk_anchor_test::accounts::UpdateNestedData { signer: payer.pubkey(), light_system_program: *light_system_program, account_compression_program: PROGRAM_ID_ACCOUNT_COMPRESSION, account_compression_authority: *account_compression_authority, registered_program_pda: *registered_program_pda, noop_program: PROGRAM_ID_NOOP, - self_program: sdk_test::ID, + self_program: sdk_anchor_test::ID, cpi_signer, system_program: solana_sdk::system_program::id(), }; @@ -266,7 +268,7 @@ where let remaining_accounts = remaining_accounts.to_account_metas(); let instruction = Instruction { - program_id: sdk_test::ID, + program_id: sdk_anchor_test::ID, accounts: [accounts.to_account_metas(Some(true)), remaining_accounts].concat(), data: instruction_data.data(), }; diff --git a/test-programs/sdk-test-program/tsconfig.json b/test-programs/sdk-anchor-test/tsconfig.json similarity index 100% rename from test-programs/sdk-test-program/tsconfig.json rename to test-programs/sdk-anchor-test/tsconfig.json diff --git a/test-programs/sdk-test/Cargo.toml b/test-programs/sdk-test/Cargo.toml new file mode 100644 index 0000000000..f1799c527d --- /dev/null +++ b/test-programs/sdk-test/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "sdk-test" +version = "0.7.0" +description = "Test program for Light SDK and Light Macros" +edition = "2021" +license = "Apache-2.0" + +[lib] +crate-type = ["cdylib", "lib"] +name = "sdk_test" + +[dependencies] +borsh = { workspace = true } +light-sdk = { workspace = true } +solana-program = { workspace = true } +thiserror = { workspace = true } + +[dev-dependencies] +solana-program-test = { workspace = true } diff --git a/test-programs/sdk-test/src/lib.rs b/test-programs/sdk-test/src/lib.rs new file mode 100644 index 0000000000..3ae41505f0 --- /dev/null +++ b/test-programs/sdk-test/src/lib.rs @@ -0,0 +1,289 @@ +use std::{io::Cursor, mem}; + +use borsh::{BorshDeserialize, BorshSerialize}; +use light_sdk::{ + account_info::LightAccountInfo, + account_meta::LightAccountMeta, + address::derive_address, + error::LightSdkError, + hasher::Discriminator, + instruction_data::LightInstructionData, + program_merkle_context::unpack_address_merkle_context, + proof::ProofRpcResult, + verify::{verify_light_account_infos, LightCpiAccounts}, + LightDiscriminator, LightHasher, +}; +use solana_program::{ + account_info::{next_account_info, AccountInfo}, + entrypoint, + entrypoint::ProgramResult, + program_error::ProgramError, + pubkey::Pubkey, +}; +use thiserror::Error; + +entrypoint!(process_instruction); + +fn process_instruction( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + let instruction = Instruction::unpack(instruction_data)?; + + match instruction { + Instruction::WithCompressedAccount { inputs, name } => { + with_compressed_account(program_id, accounts, inputs, name) + } + Instruction::UpdateNestedData { + inputs, + nested_data, + } => update_nested_data(program_id, accounts, inputs, nested_data), + } +} + +fn with_compressed_account( + program_id: &Pubkey, + accounts: &[AccountInfo], + inputs: LightInstructionData, + name: String, +) -> ProgramResult { + let light_accounts = inputs + .accounts + .as_ref() + .ok_or(LightSdkError::ExpectedAccounts)?; + + let address_merkle_context = light_accounts[0] + .address_merkle_context + .ok_or(LightSdkError::ExpectedAddressMerkleContext)?; + let address_merkle_context = unpack_address_merkle_context(address_merkle_context, accounts); + let (address, address_seed) = derive_address( + &[b"compressed", name.as_bytes()], + &address_merkle_context, + program_id, + ); + + let my_compressed_account = LightAccountInfo::from_meta_init( + &light_accounts[0], + MyCompressedAccount::discriminator(), + address, + address_seed, + Some(name.len() + mem::size_of::()), + program_id, + )?; + + let cpda = MyCompressedAccount { + name, + nested: NestedData::default(), + }; + let data = my_compressed_account + .data + .ok_or(LightSdkError::ExpectedData)?; + let mut data = data.borrow_mut(); + let mut write_cursor = Cursor::new(&mut *data); + cpda.serialize(&mut write_cursor)?; + + let accounts = &mut accounts.iter(); + let fee_payer = next_account_info(accounts)?; + let authority = next_account_info(accounts)?; + let registered_program_pda = next_account_info(accounts)?; + let noop_program = next_account_info(accounts)?; + let account_compression_authority = next_account_info(accounts)?; + let account_compression_program = next_account_info(accounts)?; + let invoking_program = next_account_info(accounts)?; + let light_system_program = next_account_info(accounts)?; + let system_program = next_account_info(accounts)?; + + let light_cpi_accounts = LightCpiAccounts { + fee_payer, + authority, + registered_program_pda, + noop_program, + account_compression_authority, + account_compression_program, + invoking_program, + light_system_program, + system_program, + cpi_context: None, + }; + verify_light_account_infos( + light_cpi_accounts, + &[], + inputs.proof, + &[my_compressed_account], + None, + false, + None, + )?; + + Ok(()) +} + +fn update_nested_data( + program_id: &Pubkey, + accounts: &[AccountInfo], + inputs: LightInstructionData, + nested_data: NestedData, +) -> ProgramResult { + let light_accounts = inputs + .accounts + .as_ref() + .ok_or(LightSdkError::ExpectedAccounts)?; + + let my_compressed_account = LightAccountInfo::from_meta_mut( + &light_accounts[0], + MyCompressedAccount::discriminator(), + program_id, + )?; + + let data = my_compressed_account + .data + .ok_or(LightSdkError::ExpectedData)?; + let mut data = data.borrow_mut(); + let mut cpda = MyCompressedAccount::deserialize(&mut data.as_slice())?; + + cpda.nested = nested_data; + + let mut write_cursor = Cursor::new(&mut *data); + cpda.serialize(&mut write_cursor)?; + + let accounts = &mut accounts.iter(); + let fee_payer = next_account_info(accounts)?; + let authority = next_account_info(accounts)?; + let registered_program_pda = next_account_info(accounts)?; + let noop_program = next_account_info(accounts)?; + let account_compression_authority = next_account_info(accounts)?; + let account_compression_program = next_account_info(accounts)?; + let invoking_program = next_account_info(accounts)?; + let light_system_program = next_account_info(accounts)?; + let system_program = next_account_info(accounts)?; + + let light_cpi_accounts = LightCpiAccounts { + fee_payer, + authority, + registered_program_pda, + noop_program, + account_compression_authority, + account_compression_program, + invoking_program, + light_system_program, + system_program, + cpi_context: None, + }; + verify_light_account_infos( + light_cpi_accounts, + &[], + inputs.proof, + &[my_compressed_account], + None, + false, + None, + )?; + + Ok(()) +} + +#[derive(Debug, Error)] +pub enum MyError { + #[error("invalid instruction")] + InvalidInstruction, +} + +impl From for ProgramError { + fn from(e: MyError) -> Self { + let e = match e { + MyError::InvalidInstruction => 1, + }; + ProgramError::Custom(e) + } +} + +pub enum Instruction { + WithCompressedAccount { + inputs: LightInstructionData, + name: String, + }, + UpdateNestedData { + inputs: LightInstructionData, + nested_data: NestedData, + }, +} + +impl Instruction { + pub fn unpack(input: &[u8]) -> Result { + let (tag, rest) = input.split_first().ok_or(MyError::InvalidInstruction)?; + match tag { + 0 => { + let mut cur = Cursor::new(rest); + + let proof = Option::::deserialize_reader(&mut cur)?; + let accounts = Option::>::deserialize_reader(&mut cur)?; + let inputs = LightInstructionData { proof, accounts }; + + let name = String::deserialize_reader(&mut cur)?; + + Ok(Self::WithCompressedAccount { inputs, name }) + } + 1 => { + let mut cur = Cursor::new(rest); + + let proof = Option::::deserialize_reader(&mut cur)?; + let accounts = Option::>::deserialize_reader(&mut cur)?; + let inputs = LightInstructionData { proof, accounts }; + + let nested_data = NestedData::deserialize_reader(&mut cur)?; + + Ok(Self::UpdateNestedData { + inputs, + nested_data, + }) + } + _ => Err(MyError::InvalidInstruction.into()), + } + } +} + +#[derive( + BorshDeserialize, BorshSerialize, Clone, Debug, Default, LightDiscriminator, LightHasher, +)] +pub struct MyCompressedAccount { + name: String, + #[nested] + pub nested: NestedData, +} + +// Illustrates nested hashing feature. +#[derive(BorshDeserialize, BorshSerialize, Clone, Debug, LightHasher)] +pub struct NestedData { + pub one: u16, + pub two: u16, + pub three: u16, + pub four: u16, + pub five: u16, + pub six: u16, + pub seven: u16, + pub eight: u16, + pub nine: u16, + pub ten: u16, + pub eleven: u16, + pub twelve: u16, +} + +impl Default for NestedData { + fn default() -> Self { + Self { + one: 1, + two: 2, + three: 3, + four: 4, + five: 5, + six: 6, + seven: 7, + eight: 8, + nine: 9, + ten: 10, + eleven: 11, + twelve: 12, + } + } +} diff --git a/test-programs/sdk-test/tests/test.rs b/test-programs/sdk-test/tests/test.rs new file mode 100644 index 0000000000..8915615eb1 --- /dev/null +++ b/test-programs/sdk-test/tests/test.rs @@ -0,0 +1 @@ +#![cfg(feature = "test-sbf")]