diff --git a/Cargo.lock b/Cargo.lock index 515f2a1d419..11025d53a99 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8967,6 +8967,58 @@ dependencies = [ "spl-type-length-value 0.7.0", ] +[[package]] +name = "spl-transfer-hook-example-downgrade" +version = "1.0.0" +dependencies = [ + "solana-account-info", + "solana-program-entrypoint", + "solana-program-error", + "solana-pubkey", +] + +[[package]] +name = "spl-transfer-hook-example-fail" +version = "1.0.0" +dependencies = [ + "solana-account-info", + "solana-program-entrypoint", + "solana-program-error", + "solana-pubkey", +] + +[[package]] +name = "spl-transfer-hook-example-success" +version = "1.0.0" +dependencies = [ + "solana-account-info", + "solana-program-entrypoint", + "solana-program-error", + "solana-pubkey", +] + +[[package]] +name = "spl-transfer-hook-example-swap" +version = "1.0.0" +dependencies = [ + "solana-account-info", + "solana-program-entrypoint", + "solana-program-error", + "solana-pubkey", + "spl-token-2022 5.0.2", +] + +[[package]] +name = "spl-transfer-hook-example-swap-with-fee" +version = "1.0.0" +dependencies = [ + "solana-account-info", + "solana-program-entrypoint", + "solana-program-error", + "solana-pubkey", + "spl-token-2022 5.0.2", +] + [[package]] name = "spl-transfer-hook-interface" version = "0.7.0" @@ -8989,12 +9041,22 @@ version = "0.8.2" dependencies = [ "arrayref", "bytemuck", + "num-derive", + "num-traits", + "solana-account-info", + "solana-cpi", + "solana-decode-error", + "solana-instruction", + "solana-msg", "solana-program", + "solana-program-error", + "solana-pubkey", "spl-discriminator 0.4.0", "spl-pod 0.5.0", "spl-program-error 0.6.0", "spl-tlv-account-resolution 0.9.0", "spl-type-length-value 0.7.0", + "thiserror", "tokio", ] diff --git a/Cargo.toml b/Cargo.toml index a2ca9427310..3aa974f74d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,6 +69,11 @@ members = [ "token/program", "token/program-2022", "token/program-2022-test", + "token/program-2022-test/transfer-hook-test-programs/downgrade", + "token/program-2022-test/transfer-hook-test-programs/fail", + "token/program-2022-test/transfer-hook-test-programs/success", + "token/program-2022-test/transfer-hook-test-programs/swap", + "token/program-2022-test/transfer-hook-test-programs/swap-with-fee", "token/transfer-hook/cli", "token/transfer-hook/example", "token/transfer-hook/interface", diff --git a/token/program-2022-test/README.md b/token/program-2022-test/README.md new file mode 100644 index 00000000000..11d5768709b --- /dev/null +++ b/token/program-2022-test/README.md @@ -0,0 +1,26 @@ +## SPL Token 2022 Program Tests + +All of the end-to-end tests for spl-token-2022 exist in this directory. + +### Requirements + +These tests require other built on-chain programs, including: + +* spl-instruction-padding +* spl-transfer-hook-example +* spl-transfer-hook-example-downgrade +* spl-transfer-hook-example-fail +* spl-transfer-hook-example-success +* spl-transfer-hook-example-swap +* spl-transfer-hook-example-swap-with-fee + +Built versions of these programs exist in `tests/fixtures`, and may be +regenerated from the following places in this repo: + +* instruction-padding/program +* token/transfer-hook/example +* token/program-2022-test/transfer-hook-test-programs/downgrade +* token/program-2022-test/transfer-hook-test-programs/fail +* token/program-2022-test/transfer-hook-test-programs/success +* token/program-2022-test/transfer-hook-test-programs/swap +* token/program-2022-test/transfer-hook-test-programs/swap-with-fee diff --git a/token/program-2022-test/tests/fixtures/spl_instruction_padding.so b/token/program-2022-test/tests/fixtures/spl_instruction_padding.so new file mode 100755 index 00000000000..16a50fee6e7 Binary files /dev/null and b/token/program-2022-test/tests/fixtures/spl_instruction_padding.so differ diff --git a/token/program-2022-test/tests/fixtures/spl_transfer_hook_example.so b/token/program-2022-test/tests/fixtures/spl_transfer_hook_example.so new file mode 100755 index 00000000000..c3ad4a44acc Binary files /dev/null and b/token/program-2022-test/tests/fixtures/spl_transfer_hook_example.so differ diff --git a/token/program-2022-test/tests/fixtures/spl_transfer_hook_example_downgrade.so b/token/program-2022-test/tests/fixtures/spl_transfer_hook_example_downgrade.so new file mode 100755 index 00000000000..fdda8fb327b Binary files /dev/null and b/token/program-2022-test/tests/fixtures/spl_transfer_hook_example_downgrade.so differ diff --git a/token/program-2022-test/tests/fixtures/spl_transfer_hook_example_fail.so b/token/program-2022-test/tests/fixtures/spl_transfer_hook_example_fail.so new file mode 100755 index 00000000000..6e46894064d Binary files /dev/null and b/token/program-2022-test/tests/fixtures/spl_transfer_hook_example_fail.so differ diff --git a/token/program-2022-test/tests/fixtures/spl_transfer_hook_example_success.so b/token/program-2022-test/tests/fixtures/spl_transfer_hook_example_success.so new file mode 100755 index 00000000000..258c11432a2 Binary files /dev/null and b/token/program-2022-test/tests/fixtures/spl_transfer_hook_example_success.so differ diff --git a/token/program-2022-test/tests/fixtures/spl_transfer_hook_example_swap.so b/token/program-2022-test/tests/fixtures/spl_transfer_hook_example_swap.so new file mode 100755 index 00000000000..8d68f1f2669 Binary files /dev/null and b/token/program-2022-test/tests/fixtures/spl_transfer_hook_example_swap.so differ diff --git a/token/program-2022-test/tests/fixtures/spl_transfer_hook_example_swap_with_fee.so b/token/program-2022-test/tests/fixtures/spl_transfer_hook_example_swap_with_fee.so new file mode 100755 index 00000000000..b8184a8aa7f Binary files /dev/null and b/token/program-2022-test/tests/fixtures/spl_transfer_hook_example_swap_with_fee.so differ diff --git a/token/program-2022-test/tests/transfer_hook.rs b/token/program-2022-test/tests/transfer_hook.rs index 584b88e133d..32f929857a4 100644 --- a/token/program-2022-test/tests/transfer_hook.rs +++ b/token/program-2022-test/tests/transfer_hook.rs @@ -6,13 +6,10 @@ use { program_test::{ ConfidentialTokenAccountBalances, ConfidentialTokenAccountMeta, TestContext, TokenContext, }, - solana_program_test::{processor, tokio, ProgramTest}, + solana_program_test::{tokio, ProgramTest}, solana_sdk::{ account::Account, - account_info::{next_account_info, AccountInfo}, - entrypoint::ProgramResult, instruction::{AccountMeta, Instruction, InstructionError}, - program_error::ProgramError, program_option::COption, pubkey::Pubkey, signature::Signer, @@ -28,8 +25,7 @@ use { transfer_hook::{TransferHook, TransferHookAccount}, BaseStateWithExtensions, }, - instruction, offchain, onchain, - processor::Processor, + instruction, offchain, }, spl_token_client::token::{ExtensionInitializationParams, TokenError as TokenClientError}, spl_transfer_hook_interface::{ @@ -41,161 +37,6 @@ use { const TEST_MAXIMUM_FEE: u64 = 10_000_000; const TEST_FEE_BASIS_POINTS: u16 = 100; -/// Test program to fail transfer hook, conforms to transfer-hook-interface -pub fn process_instruction_fail( - _program_id: &Pubkey, - _accounts: &[AccountInfo], - _input: &[u8], -) -> ProgramResult { - Err(ProgramError::InvalidInstructionData) -} - -/// Test program to succeed transfer hook, conforms to transfer-hook-interface -pub fn process_instruction_success( - _program_id: &Pubkey, - _accounts: &[AccountInfo], - _input: &[u8], -) -> ProgramResult { - Ok(()) -} - -/// Test program to check signer / write downgrade for repeated accounts, -/// conforms to transfer-hook-interface -pub fn process_instruction_downgrade( - _program_id: &Pubkey, - accounts: &[AccountInfo], - _input: &[u8], -) -> ProgramResult { - let account_info_iter = &mut accounts.iter(); - - let source_account_info = next_account_info(account_info_iter)?; - let _mint_info = next_account_info(account_info_iter)?; - let _destination_account_info = next_account_info(account_info_iter)?; - let authority_info = next_account_info(account_info_iter)?; - let _extra_account_metas_info = next_account_info(account_info_iter)?; - - let source_account_info_again = next_account_info(account_info_iter)?; - let authority_info_again = next_account_info(account_info_iter)?; - - if source_account_info.key != source_account_info_again.key { - return Err(ProgramError::InvalidAccountData); - } - - if source_account_info_again.is_writable { - return Err(ProgramError::InvalidAccountData); - } - - if authority_info.key != authority_info_again.key { - return Err(ProgramError::InvalidAccountData); - } - - if authority_info.is_signer { - return Err(ProgramError::InvalidAccountData); - } - Ok(()) -} - -/// Test program to transfer two types of tokens with transfer hooks at once -pub fn process_instruction_swap( - _program_id: &Pubkey, - accounts: &[AccountInfo], - _input: &[u8], -) -> ProgramResult { - let account_info_iter = &mut accounts.iter(); - - let source_a_account_info = next_account_info(account_info_iter)?; - let mint_a_info = next_account_info(account_info_iter)?; - let destination_a_account_info = next_account_info(account_info_iter)?; - let authority_a_info = next_account_info(account_info_iter)?; - let token_program_a_info = next_account_info(account_info_iter)?; - - let source_b_account_info = next_account_info(account_info_iter)?; - let mint_b_info = next_account_info(account_info_iter)?; - let destination_b_account_info = next_account_info(account_info_iter)?; - let authority_b_info = next_account_info(account_info_iter)?; - let token_program_b_info = next_account_info(account_info_iter)?; - - let remaining_accounts = account_info_iter.as_slice(); - - onchain::invoke_transfer_checked( - token_program_a_info.key, - source_a_account_info.clone(), - mint_a_info.clone(), - destination_a_account_info.clone(), - authority_a_info.clone(), - remaining_accounts, - 1, - 9, - &[], - )?; - - onchain::invoke_transfer_checked( - token_program_b_info.key, - source_b_account_info.clone(), - mint_b_info.clone(), - destination_b_account_info.clone(), - authority_b_info.clone(), - remaining_accounts, - 1, - 9, - &[], - )?; - - Ok(()) -} - -// Test program to transfer two types of tokens with transfer hooks at once with -// fees -pub fn process_instruction_swap_with_fee( - _program_id: &Pubkey, - accounts: &[AccountInfo], - _input: &[u8], -) -> ProgramResult { - let account_info_iter = &mut accounts.iter(); - - let source_a_account_info = next_account_info(account_info_iter)?; - let mint_a_info = next_account_info(account_info_iter)?; - let destination_a_account_info = next_account_info(account_info_iter)?; - let authority_a_info = next_account_info(account_info_iter)?; - let token_program_a_info = next_account_info(account_info_iter)?; - - let source_b_account_info = next_account_info(account_info_iter)?; - let mint_b_info = next_account_info(account_info_iter)?; - let destination_b_account_info = next_account_info(account_info_iter)?; - let authority_b_info = next_account_info(account_info_iter)?; - let token_program_b_info = next_account_info(account_info_iter)?; - - let remaining_accounts = account_info_iter.as_slice(); - - onchain::invoke_transfer_checked_with_fee( - token_program_a_info.key, - source_a_account_info.clone(), - mint_a_info.clone(), - destination_a_account_info.clone(), - authority_a_info.clone(), - remaining_accounts, - 1_000_000_000, - 9, - 10_000_000, - &[], - )?; - - onchain::invoke_transfer_checked_with_fee( - token_program_b_info.key, - source_b_account_info.clone(), - mint_b_info.clone(), - destination_b_account_info.clone(), - authority_b_info.clone(), - remaining_accounts, - 1_000_000_000, - 9, - 10_000_000, - &[], - )?; - - Ok(()) -} - async fn setup_accounts( token_context: &TokenContext, alice_account: Keypair, @@ -231,17 +72,8 @@ async fn setup_accounts( fn setup_program_test(program_id: &Pubkey) -> ProgramTest { let mut program_test = ProgramTest::default(); - program_test.prefer_bpf(false); - program_test.add_program( - "spl_token_2022", - spl_token_2022::id(), - processor!(Processor::process), - ); - program_test.add_program( - "my_transfer_hook", - *program_id, - processor!(spl_transfer_hook_example::processor::process), - ); + program_test.add_program("spl_token_2022", spl_token_2022::id(), None); + program_test.add_program("spl_transfer_hook_example", *program_id, None); program_test } @@ -413,12 +245,7 @@ async fn success_init() { #[tokio::test] async fn fail_init_all_none() { let mut program_test = ProgramTest::default(); - program_test.prefer_bpf(false); - program_test.add_program( - "spl_token_2022", - spl_token_2022::id(), - processor!(Processor::process), - ); + program_test.add_program("spl_token_2022", spl_token_2022::id(), None); let context = program_test.start_with_context().await; let context = Arc::new(tokio::sync::Mutex::new(context)); let mut context = TestContext { @@ -743,17 +570,8 @@ async fn fail_transfer_hook_program() { let program_id = Pubkey::new_unique(); let mint = Keypair::new(); let mut program_test = ProgramTest::default(); - program_test.prefer_bpf(false); - program_test.add_program( - "spl_token_2022", - spl_token_2022::id(), - processor!(Processor::process), - ); - program_test.add_program( - "my_transfer_hook", - program_id, - processor!(process_instruction_fail), - ); + program_test.add_program("spl_token_2022", spl_token_2022::id(), None); + program_test.add_program("spl_transfer_hook_example_fail", program_id, None); let validation_address = get_extra_account_metas_address(&mint.pubkey(), &program_id); program_test.add_account( validation_address, @@ -812,17 +630,8 @@ async fn success_downgrade_writable_and_signer_accounts() { let program_id = Pubkey::new_unique(); let mint = Keypair::new(); let mut program_test = ProgramTest::default(); - program_test.prefer_bpf(false); - program_test.add_program( - "spl_token_2022", - spl_token_2022::id(), - processor!(Processor::process), - ); - program_test.add_program( - "my_transfer_hook", - program_id, - processor!(process_instruction_downgrade), - ); + program_test.add_program("spl_token_2022", spl_token_2022::id(), None); + program_test.add_program("spl_transfer_hook_example_downgrade", program_id, None); let alice = Keypair::new(); let alice_account = Keypair::new(); let validation_address = get_extra_account_metas_address(&mint.pubkey(), &program_id); @@ -898,11 +707,7 @@ async fn success_transfers_using_onchain_helper() { let swap_program_id = Pubkey::new_unique(); let mut program_test = setup_program_test(&program_id); - program_test.add_program( - "my_swap", - swap_program_id, - processor!(process_instruction_swap), - ); + program_test.add_program("spl_transfer_hook_example_swap", swap_program_id, None); add_validation_account(&mut program_test, &mint_a, &program_id); add_validation_account(&mut program_test, &mint_b, &program_id); @@ -1027,9 +832,9 @@ async fn success_transfers_with_fee_using_onchain_helper() { let swap_program_id = Pubkey::new_unique(); let mut program_test = setup_program_test(&program_id); program_test.add_program( - "my_swap", + "spl_transfer_hook_example_swap_with_fee", swap_program_id, - processor!(process_instruction_swap_with_fee), + None, ); add_validation_account(&mut program_test, &mint_a, &program_id); add_validation_account(&mut program_test, &mint_b, &program_id); @@ -1263,17 +1068,8 @@ async fn success_without_validation_account() { let program_id = Pubkey::new_unique(); let mint = Keypair::new(); let mut program_test = ProgramTest::default(); - program_test.prefer_bpf(false); - program_test.add_program( - "spl_token_2022", - spl_token_2022::id(), - processor!(Processor::process), - ); - program_test.add_program( - "my_transfer_hook", - program_id, - processor!(process_instruction_success), - ); + program_test.add_program("spl_token_2022", spl_token_2022::id(), None); + program_test.add_program("spl_transfer_hook_example_success", program_id, None); let context = program_test.start_with_context().await; let context = Arc::new(tokio::sync::Mutex::new(context)); let mut context = TestContext { diff --git a/token/program-2022-test/transfer-hook-test-programs/downgrade/Cargo.toml b/token/program-2022-test/transfer-hook-test-programs/downgrade/Cargo.toml new file mode 100644 index 00000000000..8434af65498 --- /dev/null +++ b/token/program-2022-test/transfer-hook-test-programs/downgrade/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "spl-transfer-hook-example-downgrade" +version = "1.0.0" +description = "Solana Program Library Transfer Hook Example: Downgrade" +authors = ["Solana Labs Maintainers "] +repository = "https://github.com/solana-labs/solana-program-library" +license = "Apache-2.0" +edition = "2021" +publish = false + +[dependencies] +solana-account-info = "2.1.0" +solana-program-entrypoint = "2.1.0" +solana-program-error = "2.1.0" +solana-pubkey = "2.1.0" + +[lib] +crate-type = ["cdylib", "lib"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/token/program-2022-test/transfer-hook-test-programs/downgrade/src/lib.rs b/token/program-2022-test/transfer-hook-test-programs/downgrade/src/lib.rs new file mode 100644 index 00000000000..9b2ab4e156a --- /dev/null +++ b/token/program-2022-test/transfer-hook-test-programs/downgrade/src/lib.rs @@ -0,0 +1,42 @@ +//! Program implementation + +use { + solana_account_info::{next_account_info, AccountInfo}, + solana_program_error::{ProgramError, ProgramResult}, + solana_pubkey::Pubkey, +}; + +solana_program_entrypoint::entrypoint!(process_instruction); +fn process_instruction( + _program_id: &Pubkey, + accounts: &[AccountInfo], + _instruction_data: &[u8], +) -> ProgramResult { + let account_info_iter = &mut accounts.iter(); + + let source_account_info = next_account_info(account_info_iter)?; + let _mint_info = next_account_info(account_info_iter)?; + let _destination_account_info = next_account_info(account_info_iter)?; + let authority_info = next_account_info(account_info_iter)?; + let _extra_account_metas_info = next_account_info(account_info_iter)?; + + let source_account_info_again = next_account_info(account_info_iter)?; + let authority_info_again = next_account_info(account_info_iter)?; + + if source_account_info.key != source_account_info_again.key { + return Err(ProgramError::InvalidAccountData); + } + + if source_account_info_again.is_writable { + return Err(ProgramError::InvalidAccountData); + } + + if authority_info.key != authority_info_again.key { + return Err(ProgramError::InvalidAccountData); + } + + if authority_info.is_signer { + return Err(ProgramError::InvalidAccountData); + } + Ok(()) +} diff --git a/token/program-2022-test/transfer-hook-test-programs/fail/Cargo.toml b/token/program-2022-test/transfer-hook-test-programs/fail/Cargo.toml new file mode 100644 index 00000000000..b8e6d4bfe68 --- /dev/null +++ b/token/program-2022-test/transfer-hook-test-programs/fail/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "spl-transfer-hook-example-fail" +version = "1.0.0" +description = "Solana Program Library Transfer Hook Example: Fail" +authors = ["Solana Labs Maintainers "] +repository = "https://github.com/solana-labs/solana-program-library" +license = "Apache-2.0" +edition = "2021" +publish = false + +[dependencies] +solana-account-info = "2.1.0" +solana-program-entrypoint = "2.1.0" +solana-program-error = "2.1.0" +solana-pubkey = "2.1.0" + +[lib] +crate-type = ["cdylib", "lib"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/token/program-2022-test/transfer-hook-test-programs/fail/src/lib.rs b/token/program-2022-test/transfer-hook-test-programs/fail/src/lib.rs new file mode 100644 index 00000000000..e82ab842ae6 --- /dev/null +++ b/token/program-2022-test/transfer-hook-test-programs/fail/src/lib.rs @@ -0,0 +1,16 @@ +//! Program implementation + +use { + solana_account_info::AccountInfo, + solana_program_error::{ProgramError, ProgramResult}, + solana_pubkey::Pubkey, +}; + +solana_program_entrypoint::entrypoint!(process_instruction); +fn process_instruction( + _program_id: &Pubkey, + _accounts: &[AccountInfo], + _instruction_data: &[u8], +) -> ProgramResult { + Err(ProgramError::InvalidInstructionData) +} diff --git a/token/program-2022-test/transfer-hook-test-programs/success/Cargo.toml b/token/program-2022-test/transfer-hook-test-programs/success/Cargo.toml new file mode 100644 index 00000000000..26c7a507aa5 --- /dev/null +++ b/token/program-2022-test/transfer-hook-test-programs/success/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "spl-transfer-hook-example-success" +version = "1.0.0" +description = "Solana Program Library Transfer Hook Example: Success" +authors = ["Solana Labs Maintainers "] +repository = "https://github.com/solana-labs/solana-program-library" +license = "Apache-2.0" +edition = "2021" +publish = false + +[dependencies] +solana-account-info = "2.1.0" +solana-program-entrypoint = "2.1.0" +solana-program-error = "2.1.0" +solana-pubkey = "2.1.0" + +[lib] +crate-type = ["cdylib", "lib"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/token/program-2022-test/transfer-hook-test-programs/success/src/lib.rs b/token/program-2022-test/transfer-hook-test-programs/success/src/lib.rs new file mode 100644 index 00000000000..5baa0db1f18 --- /dev/null +++ b/token/program-2022-test/transfer-hook-test-programs/success/src/lib.rs @@ -0,0 +1,14 @@ +//! Program implementation + +use { + solana_account_info::AccountInfo, solana_program_error::ProgramResult, solana_pubkey::Pubkey, +}; + +solana_program_entrypoint::entrypoint!(process_instruction); +fn process_instruction( + _program_id: &Pubkey, + _accounts: &[AccountInfo], + _instruction_data: &[u8], +) -> ProgramResult { + Ok(()) +} diff --git a/token/program-2022-test/transfer-hook-test-programs/swap-with-fee/Cargo.toml b/token/program-2022-test/transfer-hook-test-programs/swap-with-fee/Cargo.toml new file mode 100644 index 00000000000..6a263434801 --- /dev/null +++ b/token/program-2022-test/transfer-hook-test-programs/swap-with-fee/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "spl-transfer-hook-example-swap-with-fee" +version = "1.0.0" +description = "Solana Program Library Transfer Hook Example: Swap with Fee" +authors = ["Solana Labs Maintainers "] +repository = "https://github.com/solana-labs/solana-program-library" +license = "Apache-2.0" +edition = "2021" +publish = false + +[dependencies] +solana-account-info = "2.1.0" +solana-program-entrypoint = "2.1.0" +solana-program-error = "2.1.0" +solana-pubkey = "2.1.0" +spl-token-2022 = { path = "../../../program-2022", features = ["no-entrypoint"] } + +[lib] +crate-type = ["cdylib", "lib"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/token/program-2022-test/transfer-hook-test-programs/swap-with-fee/src/lib.rs b/token/program-2022-test/transfer-hook-test-programs/swap-with-fee/src/lib.rs new file mode 100644 index 00000000000..5d21fb88440 --- /dev/null +++ b/token/program-2022-test/transfer-hook-test-programs/swap-with-fee/src/lib.rs @@ -0,0 +1,59 @@ +//! Program implementation + +use { + solana_account_info::{next_account_info, AccountInfo}, + solana_program_error::ProgramResult, + solana_pubkey::Pubkey, + spl_token_2022::onchain, +}; + +solana_program_entrypoint::entrypoint!(process_instruction); +fn process_instruction( + _program_id: &Pubkey, + accounts: &[AccountInfo], + _instruction_data: &[u8], +) -> ProgramResult { + let account_info_iter = &mut accounts.iter(); + + let source_a_account_info = next_account_info(account_info_iter)?; + let mint_a_info = next_account_info(account_info_iter)?; + let destination_a_account_info = next_account_info(account_info_iter)?; + let authority_a_info = next_account_info(account_info_iter)?; + let token_program_a_info = next_account_info(account_info_iter)?; + + let source_b_account_info = next_account_info(account_info_iter)?; + let mint_b_info = next_account_info(account_info_iter)?; + let destination_b_account_info = next_account_info(account_info_iter)?; + let authority_b_info = next_account_info(account_info_iter)?; + let token_program_b_info = next_account_info(account_info_iter)?; + + let remaining_accounts = account_info_iter.as_slice(); + + onchain::invoke_transfer_checked_with_fee( + token_program_a_info.key, + source_a_account_info.clone(), + mint_a_info.clone(), + destination_a_account_info.clone(), + authority_a_info.clone(), + remaining_accounts, + 1_000_000_000, + 9, + 10_000_000, + &[], + )?; + + onchain::invoke_transfer_checked_with_fee( + token_program_b_info.key, + source_b_account_info.clone(), + mint_b_info.clone(), + destination_b_account_info.clone(), + authority_b_info.clone(), + remaining_accounts, + 1_000_000_000, + 9, + 10_000_000, + &[], + )?; + + Ok(()) +} diff --git a/token/program-2022-test/transfer-hook-test-programs/swap/Cargo.toml b/token/program-2022-test/transfer-hook-test-programs/swap/Cargo.toml new file mode 100644 index 00000000000..05d4d40d4a7 --- /dev/null +++ b/token/program-2022-test/transfer-hook-test-programs/swap/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "spl-transfer-hook-example-swap" +version = "1.0.0" +description = "Solana Program Library Transfer Hook Example: Swap" +authors = ["Solana Labs Maintainers "] +repository = "https://github.com/solana-labs/solana-program-library" +license = "Apache-2.0" +edition = "2021" +publish = false + +[dependencies] +solana-account-info = "2.1.0" +solana-program-entrypoint = "2.1.0" +solana-program-error = "2.1.0" +solana-pubkey = "2.1.0" +spl-token-2022 = { path = "../../../program-2022", features = ["no-entrypoint"] } + +[lib] +crate-type = ["cdylib", "lib"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/token/program-2022-test/transfer-hook-test-programs/swap/src/lib.rs b/token/program-2022-test/transfer-hook-test-programs/swap/src/lib.rs new file mode 100644 index 00000000000..c299f6217b0 --- /dev/null +++ b/token/program-2022-test/transfer-hook-test-programs/swap/src/lib.rs @@ -0,0 +1,57 @@ +//! Program implementation + +use { + solana_account_info::{next_account_info, AccountInfo}, + solana_program_error::ProgramResult, + solana_pubkey::Pubkey, + spl_token_2022::onchain, +}; + +solana_program_entrypoint::entrypoint!(process_instruction); +fn process_instruction( + _program_id: &Pubkey, + accounts: &[AccountInfo], + _instruction_data: &[u8], +) -> ProgramResult { + let account_info_iter = &mut accounts.iter(); + + let source_a_account_info = next_account_info(account_info_iter)?; + let mint_a_info = next_account_info(account_info_iter)?; + let destination_a_account_info = next_account_info(account_info_iter)?; + let authority_a_info = next_account_info(account_info_iter)?; + let token_program_a_info = next_account_info(account_info_iter)?; + + let source_b_account_info = next_account_info(account_info_iter)?; + let mint_b_info = next_account_info(account_info_iter)?; + let destination_b_account_info = next_account_info(account_info_iter)?; + let authority_b_info = next_account_info(account_info_iter)?; + let token_program_b_info = next_account_info(account_info_iter)?; + + let remaining_accounts = account_info_iter.as_slice(); + + onchain::invoke_transfer_checked( + token_program_a_info.key, + source_a_account_info.clone(), + mint_a_info.clone(), + destination_a_account_info.clone(), + authority_a_info.clone(), + remaining_accounts, + 1, + 9, + &[], + )?; + + onchain::invoke_transfer_checked( + token_program_b_info.key, + source_b_account_info.clone(), + mint_b_info.clone(), + destination_b_account_info.clone(), + authority_b_info.clone(), + remaining_accounts, + 1, + 9, + &[], + )?; + + Ok(()) +} diff --git a/token/transfer-hook/interface/Cargo.toml b/token/transfer-hook/interface/Cargo.toml index 634ce42c2ed..fa40b4b9444 100644 --- a/token/transfer-hook/interface/Cargo.toml +++ b/token/transfer-hook/interface/Cargo.toml @@ -10,17 +10,27 @@ edition = "2021" [dependencies] arrayref = "0.3.9" bytemuck = { version = "1.19.0", features = ["derive"] } -solana-program = "2.1.0" -spl-discriminator = { version = "0.4.0", path = "../../../libraries/discriminator" } +num-derive = "0.4" +num-traits = "0.2" +solana-account-info = "2.1.0" +solana-cpi = "2.1.0" +solana-decode-error = "2.1.0" +solana-instruction = { version = "2.1.0", features = ["std"] } +solana-msg = "2.1.0" +solana-program-error = "2.1.0" +solana-pubkey = { version = "2.1.0", features = ["curve25519"] } +spl-discriminator = { version = "0.4.0" , path = "../../../libraries/discriminator" } spl-program-error = { version = "0.6.0", path = "../../../libraries/program-error" } spl-tlv-account-resolution = { version = "0.9.0", path = "../../../libraries/tlv-account-resolution" } spl-type-length-value = { version = "0.7.0", path = "../../../libraries/type-length-value" } spl-pod = { version = "0.5.0", path = "../../../libraries/pod" } +thiserror = "1.0" [lib] crate-type = ["cdylib", "lib"] [dev-dependencies] +solana-program = "2.1.0" tokio = { version = "1.41.0", features = ["full"] } [package.metadata.docs.rs] diff --git a/token/transfer-hook/interface/src/error.rs b/token/transfer-hook/interface/src/error.rs index aaf25e19627..de50ec827c7 100644 --- a/token/transfer-hook/interface/src/error.rs +++ b/token/transfer-hook/interface/src/error.rs @@ -1,13 +1,18 @@ //! Error types -use spl_program_error::*; +use { + solana_decode_error::DecodeError, + solana_msg::msg, + solana_program_error::{PrintProgramError, ProgramError}, +}; /// Errors that may be returned by the interface. -#[spl_program_error(hash_error_code_start = 2_110_272_652)] +#[repr(u32)] +#[derive(Clone, Debug, Eq, thiserror::Error, num_derive::FromPrimitive, PartialEq)] pub enum TransferHookError { /// Incorrect account provided #[error("Incorrect account provided")] - IncorrectAccount, + IncorrectAccount = 2_110_272_652, /// Mint has no mint authority #[error("Mint has no mint authority")] MintHasNoMintAuthority, @@ -18,3 +23,41 @@ pub enum TransferHookError { #[error("Program called outside of a token transfer")] ProgramCalledOutsideOfTransfer, } + +impl From for ProgramError { + fn from(e: TransferHookError) -> Self { + ProgramError::Custom(e as u32) + } +} + +impl DecodeError for TransferHookError { + fn type_of() -> &'static str { + "TransferHookError" + } +} + +impl PrintProgramError for TransferHookError { + fn print(&self) + where + E: 'static + + std::error::Error + + DecodeError + + PrintProgramError + + num_traits::FromPrimitive, + { + match self { + TransferHookError::IncorrectAccount => { + msg!("Incorrect account provided") + } + TransferHookError::MintHasNoMintAuthority => { + msg!("Mint has no mint authority") + } + TransferHookError::IncorrectMintAuthority => { + msg!("Incorrect mint authority has signed the instruction") + } + TransferHookError::ProgramCalledOutsideOfTransfer => { + msg!("Program called outside of a token transfer") + } + } + } +} diff --git a/token/transfer-hook/interface/src/instruction.rs b/token/transfer-hook/interface/src/instruction.rs index d6d71323450..52419b6fc5d 100644 --- a/token/transfer-hook/interface/src/instruction.rs +++ b/token/transfer-hook/interface/src/instruction.rs @@ -1,18 +1,17 @@ //! Instruction types use { - solana_program::{ - instruction::{AccountMeta, Instruction}, - program_error::ProgramError, - pubkey::Pubkey, - system_program, - }, + solana_instruction::{AccountMeta, Instruction}, + solana_program_error::ProgramError, + solana_pubkey::Pubkey, spl_discriminator::{ArrayDiscriminator, SplDiscriminate}, spl_pod::{bytemuck::pod_slice_to_bytes, slice::PodSlice}, spl_tlv_account_resolution::account::ExtraAccountMeta, std::convert::TryInto, }; +const SYSTEM_PROGRAM_ID: Pubkey = Pubkey::from_str_const("11111111111111111111111111111111"); + /// Instructions supported by the transfer hook interface. #[repr(C)] #[derive(Clone, Debug, PartialEq)] @@ -215,7 +214,7 @@ pub fn initialize_extra_account_meta_list( AccountMeta::new(*extra_account_metas_pubkey, false), AccountMeta::new_readonly(*mint_pubkey, false), AccountMeta::new_readonly(*authority_pubkey, true), - AccountMeta::new_readonly(system_program::id(), false), + AccountMeta::new_readonly(SYSTEM_PROGRAM_ID, false), ]; Instruction { @@ -255,6 +254,11 @@ pub fn update_extra_account_meta_list( mod test { use {super::*, crate::NAMESPACE, solana_program::hash, spl_pod::bytemuck::pod_from_bytes}; + #[test] + fn system_program_id() { + assert_eq!(solana_program::system_program::id(), SYSTEM_PROGRAM_ID); + } + #[test] fn validate_packing() { let amount = 111_111_111; diff --git a/token/transfer-hook/interface/src/lib.rs b/token/transfer-hook/interface/src/lib.rs index d22b7f89f18..0e4f382ebc5 100644 --- a/token/transfer-hook/interface/src/lib.rs +++ b/token/transfer-hook/interface/src/lib.rs @@ -14,8 +14,11 @@ pub mod onchain; // Export current sdk types for downstream users building with a different sdk // version -pub use solana_program; -use solana_program::pubkey::Pubkey; +use solana_pubkey::Pubkey; +pub use { + solana_account_info, solana_cpi, solana_decode_error, solana_instruction, solana_msg, + solana_program_error, solana_pubkey, +}; /// Namespace for all programs implementing transfer-hook pub const NAMESPACE: &str = "spl-transfer-hook-interface"; diff --git a/token/transfer-hook/interface/src/offchain.rs b/token/transfer-hook/interface/src/offchain.rs index 50c8c57e4b7..46e078e38f8 100644 --- a/token/transfer-hook/interface/src/offchain.rs +++ b/token/transfer-hook/interface/src/offchain.rs @@ -7,11 +7,9 @@ use { get_extra_account_metas_address, instruction::{execute, ExecuteInstruction}, }, - solana_program::{ - instruction::{AccountMeta, Instruction}, - program_error::ProgramError, - pubkey::Pubkey, - }, + solana_instruction::{AccountMeta, Instruction}, + solana_program_error::ProgramError, + solana_pubkey::Pubkey, spl_tlv_account_resolution::state::ExtraAccountMetaList, std::future::Future, }; diff --git a/token/transfer-hook/interface/src/onchain.rs b/token/transfer-hook/interface/src/onchain.rs index 1e332579e80..7bd8eff3925 100644 --- a/token/transfer-hook/interface/src/onchain.rs +++ b/token/transfer-hook/interface/src/onchain.rs @@ -3,13 +3,11 @@ use { crate::{error::TransferHookError, get_extra_account_metas_address, instruction}, - solana_program::{ - account_info::AccountInfo, - entrypoint::ProgramResult, - instruction::{AccountMeta, Instruction}, - program::invoke, - pubkey::Pubkey, - }, + solana_account_info::AccountInfo, + solana_cpi::invoke, + solana_instruction::{AccountMeta, Instruction}, + solana_program_error::ProgramResult, + solana_pubkey::Pubkey, spl_tlv_account_resolution::state::ExtraAccountMetaList, }; /// Helper to CPI into a transfer-hook program on-chain, looking through the