Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ jobs:
echo "🧪 Running RPC integration tests..."
cargo llvm-cov test --no-report -p tests --test rpc

- name: Build transfer hook program for Token 2022 tests
run: |
echo "🔧 Building transfer hook program..."
make build-transfer-hook

- name: Run token integration tests
run: |
echo "🧪 Running token integration tests..."
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
*.pdb
generated/

/tests/src/common/transfer-hook-example/target

# SDK
node_modules/
dist/
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ include makefiles/DOCUMENTATION.makefile
include makefiles/COVERAGE.makefile
include makefiles/METRICS.makefile

.PHONY: check lint test build run clean all install generate-key setup-test-env test-integration test-all test-ts coverage coverage-clean build-bin build-lib build-cli run-presigned openapi gen-ts-client run-metrics
.PHONY: check lint test build run clean all install generate-key setup-test-env test-integration test-all test-ts coverage coverage-clean build-bin build-lib build-cli run-presigned openapi gen-ts-client run-metrics build-transfer-hook

# Default target
all: check test build
Expand Down
7 changes: 3 additions & 4 deletions crates/lib/src/transaction/versioned_transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,9 @@ impl VersionedTransactionResolved {
.map_err(|e| KoraError::RpcError(format!("Failed to simulate transaction: {e}")))?;

if let Some(err) = simulation_result.value.err {
log::warn!("Transaction simulation failed: {err}");
return Err(KoraError::InvalidTransaction(
"Transaction inner instructions fetching failed.".to_string(),
));
return Err(KoraError::InvalidTransaction(format!(
"Transaction simulation failed: {err}"
)));
}

if let Some(inner_instructions) = simulation_result.value.inner_instructions {
Expand Down
9 changes: 9 additions & 0 deletions makefiles/RUST_TESTS.makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@
test:
@cargo test --lib --workspace --exclude tests --quiet 2>/dev/null || true

# Build transfer hook program (is checked in, so only need to build if changes are made)
build-transfer-hook:
$(call print_header,BUILDING TRANSFER HOOK PROGRAM)
$(call print_step,Building transfer hook program...)
cd tests/src/common/transfer-hook-example && \
chmod +x build.sh && \
./build.sh
$(call print_success,Transfer hook program built at tests/src/common/transfer-hook-example/target/deploy/)

# Run all integration tests with clean output
test-integration:
$(call print_header,KORA INTEGRATION TEST SUITE)
Expand Down
11 changes: 10 additions & 1 deletion makefiles/UTILS.makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ MULTI_SIGNERS_CONFIG := tests/src/common/fixtures/multi-signers.toml
REGULAR_CONFIG := tests/src/common/fixtures/kora-test.toml
AUTH_CONFIG := tests/src/common/fixtures/auth-test.toml
PAYMENT_ADDRESS_CONFIG := tests/src/common/fixtures/paymaster-address-test.toml
TRANSFER_HOOK_PROGRAM_ID := Bcdikjss8HWzKEuj6gEQoFq9TCnGnk6v3kUnRU1gb6hA

# CI-aware timeouts
VALIDATOR_TIMEOUT := $(if $(CI),20,30)
Expand Down Expand Up @@ -55,13 +56,21 @@ define print_error
@printf " $(RED)✗$(RESET) $(1)\n"
endef


# Solana validator lifecycle management functions
define start_solana_validator
$(call print_step,Starting Solana test validator...)
@pkill -f "solana-test-validator" 2>/dev/null || true
@sleep 2
@rm -rf test-ledger 2>/dev/null || true
@solana-test-validator --reset --quiet $(QUIET_OUTPUT) &
@if [ -f "tests/src/common/transfer-hook-example/transfer_hook_example.so" ]; then \
printf " $(YELLOW)•$(RESET) Loading transfer hook program: $(TRANSFER_HOOK_PROGRAM_ID)\\n"; \
printf " $(YELLOW)•$(RESET) Program file: tests/src/common/transfer-hook-example/transfer_hook_example.so\\n"; \
solana-test-validator --reset --quiet --bpf-program $(TRANSFER_HOOK_PROGRAM_ID) tests/src/common/transfer-hook-example/transfer_hook_example.so $(QUIET_OUTPUT) & \
else \
printf " $(RED)✗$(RESET) Transfer hook program not found, starting validator without it\\n"; \
solana-test-validator --reset --quiet $(QUIET_OUTPUT) & \
fi
@echo $$! > .validator.pid
@counter=0; \
while [ $$counter -lt $(VALIDATOR_TIMEOUT) ]; do \
Expand Down
1 change: 1 addition & 0 deletions tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ solana-client = { workspace = true }
spl-token = { workspace = true }
spl-token-2022 = { workspace = true }
spl-associated-token-account = { workspace = true }
spl-transfer-hook-interface = { version = "0.10.0" }
solana-compute-budget-interface = "2.2.2"
bincode = { workspace = true }
bs58 = { workspace = true }
Expand Down
7 changes: 7 additions & 0 deletions tests/src/common/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ pub const USDC_MINT_2022_KEYPAIR_PATH: &str =
pub const INTEREST_BEARING_MINT_KEYPAIR_PATH: &str =
concat!(env!("CARGO_MANIFEST_DIR"), "/src/common/local-keys/mint-2022-interest-bearing.json");

/// Transfer hook mint keypair path (local testing only)
pub const TRANSFER_HOOK_MINT_KEYPAIR_PATH: &str =
concat!(env!("CARGO_MANIFEST_DIR"), "/src/common/local-keys/mint-transfer-hook-local.json");

/// Second signer keypair path (for multi-signer tests)
pub const SIGNER2_KEYPAIR_PATH: &str =
concat!(env!("CARGO_MANIFEST_DIR"), "/src/common/local-keys/signer2-local.json");
Expand All @@ -62,6 +66,9 @@ pub const TEST_PAYMENT_ADDRESS: &str = "CWvWnVwqAb9HzqwCGkn4purGEUuu27aNsPQM252u
/// PYUSD token mint on devnet
pub const PYUSD_MINT: &str = "CXk2AMBfi3TwaEL2468s6zP8xq9NxTXjp9gjMgzeUynM";

/// Transfer hook program ID
pub const TRANSFER_HOOK_PROGRAM_ID: &str = "Bcdikjss8HWzKEuj6gEQoFq9TCnGnk6v3kUnRU1gb6hA";

// ============================================================================
// Test Configuration
// ============================================================================
Expand Down
121 changes: 116 additions & 5 deletions tests/src/common/extension_helpers.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
use crate::common::USDCMintTestHelper;
use anyhow::Result;
use solana_client::nonblocking::rpc_client::RpcClient;
use solana_sdk::{
instruction::AccountMeta,
pubkey::Pubkey,
signature::{Keypair, Signer},
transaction::Transaction,
};
use solana_system_interface::instruction::create_account;
use spl_token_2022::{
extension::{interest_bearing_mint::instruction::initialize, ExtensionType},
extension::{interest_bearing_mint::instruction::initialize, transfer_hook, ExtensionType},
instruction as token_2022_instruction,
state::{Account as Token2022Account, Mint as Token2022Mint},
};
use spl_transfer_hook_interface::{
get_extra_account_metas_address, instruction::initialize_extra_account_meta_list,
};
use std::sync::Arc;

use crate::common::USDCMintTestHelper;

/// Helper functions for creating Token 2022 accounts with specific extensions for testing
pub struct ExtensionHelpers;

Expand All @@ -36,7 +40,7 @@ impl ExtensionHelpers {

let rent = rpc_client.get_minimum_balance_for_rent_exemption(space).await?;

let create_account_instruction = solana_sdk::system_instruction::create_account(
let create_account_instruction = create_account(
&payer.pubkey(),
&mint_keypair.pubkey(),
rent,
Expand Down Expand Up @@ -93,7 +97,7 @@ impl ExtensionHelpers {
])?;
let rent = rpc_client.get_minimum_balance_for_rent_exemption(account_space).await?;

let create_account_instruction = solana_sdk::system_instruction::create_account(
let create_account_instruction = create_account(
&payer.pubkey(),
&token_account_keypair.pubkey(),
rent,
Expand Down Expand Up @@ -165,4 +169,111 @@ impl ExtensionHelpers {
rpc_client.send_and_confirm_transaction(&transaction).await?;
Ok(())
}

/// Create a mint with TransferHook extension for testing
pub async fn create_mint_with_transfer_hook(
rpc_client: &Arc<RpcClient>,
payer: &Keypair,
mint_keypair: &Keypair,
hook_program_id: &Pubkey,
) -> Result<()> {
if (rpc_client.get_account(&mint_keypair.pubkey()).await).is_ok() {
return Ok(());
}

// Calculate space for mint with TransferHook extension
let space = ExtensionType::try_calculate_account_len::<Token2022Mint>(&[
ExtensionType::TransferHook,
])?;

let rent = rpc_client.get_minimum_balance_for_rent_exemption(space).await?;

let create_account_instruction = create_account(
&payer.pubkey(),
&mint_keypair.pubkey(),
rent,
space as u64,
&spl_token_2022::id(),
);

// Initialize the transfer hook extension
let initialize_hook_instruction = transfer_hook::instruction::initialize(
&spl_token_2022::id(),
&mint_keypair.pubkey(),
Some(payer.pubkey()),
Some(*hook_program_id),
)?;

let initialize_mint_instruction = token_2022_instruction::initialize_mint2(
&spl_token_2022::id(),
&mint_keypair.pubkey(),
&payer.pubkey(),
Some(&payer.pubkey()),
USDCMintTestHelper::get_test_usdc_mint_decimals(),
)?;

let recent_blockhash = rpc_client.get_latest_blockhash().await?;

let transaction = Transaction::new_signed_with_payer(
&[create_account_instruction, initialize_hook_instruction, initialize_mint_instruction],
Some(&payer.pubkey()),
&[payer, mint_keypair],
recent_blockhash,
);

rpc_client.send_and_confirm_transaction(&transaction).await?;

// After mint is created, we need to initialize the Extra Account Meta List
Self::initialize_extra_account_meta_list(
rpc_client,
payer,
&mint_keypair.pubkey(),
hook_program_id,
)
.await?;

Ok(())
}

/// Initialize Extra Account Meta List for transfer hook
async fn initialize_extra_account_meta_list(
rpc_client: &Arc<RpcClient>,
payer: &Keypair,
mint: &Pubkey,
hook_program_id: &Pubkey,
) -> Result<()> {
let extra_account_metas_address = get_extra_account_metas_address(mint, hook_program_id);

if rpc_client.get_account(&extra_account_metas_address).await.is_ok() {
return Ok(());
}

// Create an empty list of extra account metas (our simple hook doesn't need any)
let extra_account_metas = vec![];

let mut initialize_instruction = initialize_extra_account_meta_list(
hook_program_id,
&extra_account_metas_address,
mint,
&payer.pubkey(),
&extra_account_metas,
);

// Add the system program account which is needed for PDA creation
initialize_instruction
.accounts
.push(AccountMeta::new_readonly(solana_sdk::system_program::id(), false));

let recent_blockhash = rpc_client.get_latest_blockhash().await?;
let transaction = Transaction::new_signed_with_payer(
&[initialize_instruction],
Some(&payer.pubkey()),
&[payer],
recent_blockhash,
);

rpc_client.send_and_confirm_transaction(&transaction).await?;

Ok(())
}
}
15 changes: 15 additions & 0 deletions tests/src/common/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,19 @@ impl USDCMint2022TestHelper {
pub fn get_test_interest_bearing_mint_pubkey() -> Pubkey {
Self::get_test_interest_bearing_mint_keypair().pubkey()
}

pub fn get_test_transfer_hook_mint_keypair() -> Keypair {
dotenv::dotenv().ok();
let mint_keypair = match std::env::var("TEST_TRANSFER_HOOK_MINT_KEYPAIR") {
Ok(key) => key,
Err(_) => std::fs::read_to_string(TRANSFER_HOOK_MINT_KEYPAIR_PATH)
.expect("Failed to read transfer hook mint private key file"),
};
parse_private_key_string(&mint_keypair)
.expect("Failed to parse test transfer hook mint private key")
}

pub fn get_test_transfer_hook_mint_pubkey() -> Pubkey {
Self::get_test_transfer_hook_mint_keypair().pubkey()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[24,76,19,115,76,184,74,133,28,64,179,98,233,151,179,252,37,171,7,151,148,42,5,250,7,241,157,57,104,35,105,33,106,60,44,67,36,199,253,163,7,64,41,206,101,8,228,177,46,134,218,229,147,97,112,71,47,2,198,225,131,180,176,140]
Loading
Loading