//! @brief Example Rust-based BPF program that issues a cross-program-invocation #![allow(unreachable_code)] extern crate solana_program; use solana_bpf_rust_invoked::instruction::*; use solana_program::{ account_info::AccountInfo, entrypoint, entrypoint::{ProgramResult, MAX_PERMITTED_DATA_INCREASE}, msg, program::{invoke, invoke_signed}, program_error::ProgramError, pubkey::{Pubkey, PubkeyError}, system_instruction, }; const TEST_SUCCESS: u8 = 1; const TEST_PRIVILEGE_ESCALATION_SIGNER: u8 = 2; const TEST_PRIVILEGE_ESCALATION_WRITABLE: u8 = 3; const TEST_PPROGRAM_NOT_EXECUTABLE: u8 = 4; const TEST_EMPTY_ACCOUNTS_SLICE: u8 = 5; const TEST_CAP_SEEDS: u8 = 6; const TEST_CAP_SIGNERS: u8 = 7; const TEST_ALLOC_ACCESS_VIOLATION: u8 = 8; const TEST_INSTRUCTION_DATA_TOO_LARGE: u8 = 9; const TEST_INSTRUCTION_META_TOO_LARGE: u8 = 10; const TEST_RETURN_ERROR: u8 = 11; // const MINT_INDEX: usize = 0; const ARGUMENT_INDEX: usize = 1; const INVOKED_PROGRAM_INDEX: usize = 2; const INVOKED_ARGUMENT_INDEX: usize = 3; const INVOKED_PROGRAM_DUP_INDEX: usize = 4; // const ARGUMENT_DUP_INDEX: usize = 5; const DERIVED_KEY1_INDEX: usize = 6; const DERIVED_KEY2_INDEX: usize = 7; const DERIVED_KEY3_INDEX: usize = 8; const SYSTEM_PROGRAM_INDEX: usize = 9; const FROM_INDEX: usize = 10; entrypoint!(process_instruction); fn process_instruction( program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8], ) -> ProgramResult { msg!("invoke Rust program"); let bump_seed1 = instruction_data[1]; let bump_seed2 = instruction_data[2]; let bump_seed3 = instruction_data[3]; match instruction_data[0] { TEST_SUCCESS => { msg!("Call system program create account"); { let from_lamports = accounts[FROM_INDEX].lamports(); let to_lamports = accounts[DERIVED_KEY1_INDEX].lamports(); assert_eq!(accounts[DERIVED_KEY1_INDEX].data_len(), 0); assert!(solana_program::system_program::check_id( accounts[DERIVED_KEY1_INDEX].owner )); let instruction = system_instruction::create_account( accounts[FROM_INDEX].key, accounts[DERIVED_KEY1_INDEX].key, 42, MAX_PERMITTED_DATA_INCREASE as u64, program_id, ); invoke_signed( &instruction, accounts, &[&[b"You pass butter", &[bump_seed1]]], )?; assert_eq!(accounts[FROM_INDEX].lamports(), from_lamports - 42); assert_eq!(accounts[DERIVED_KEY1_INDEX].lamports(), to_lamports + 42); assert_eq!(program_id, accounts[DERIVED_KEY1_INDEX].owner); assert_eq!( accounts[DERIVED_KEY1_INDEX].data_len(), MAX_PERMITTED_DATA_INCREASE ); let mut data = accounts[DERIVED_KEY1_INDEX].try_borrow_mut_data()?; assert_eq!(data[MAX_PERMITTED_DATA_INCREASE - 1], 0); data[MAX_PERMITTED_DATA_INCREASE - 1] = 0x0f; assert_eq!(data[MAX_PERMITTED_DATA_INCREASE - 1], 0x0f); for i in 0..20 { data[i] = i as u8; } } msg!("Call system program transfer"); { let from_lamports = accounts[FROM_INDEX].lamports(); let to_lamports = accounts[DERIVED_KEY1_INDEX].lamports(); let instruction = system_instruction::transfer( accounts[FROM_INDEX].key, accounts[DERIVED_KEY1_INDEX].key, 1, ); invoke(&instruction, accounts)?; assert_eq!(accounts[FROM_INDEX].lamports(), from_lamports - 1); assert_eq!(accounts[DERIVED_KEY1_INDEX].lamports(), to_lamports + 1); } msg!("Test data translation"); { { let mut data = accounts[ARGUMENT_INDEX].try_borrow_mut_data()?; for i in 0..100 { data[i as usize] = i; } } let instruction = create_instruction( *accounts[INVOKED_PROGRAM_INDEX].key, &[ (accounts[ARGUMENT_INDEX].key, true, true), (accounts[INVOKED_ARGUMENT_INDEX].key, true, true), (accounts[INVOKED_PROGRAM_INDEX].key, false, false), (accounts[INVOKED_PROGRAM_DUP_INDEX].key, false, false), ], vec![VERIFY_TRANSLATIONS, 1, 2, 3, 4, 5], ); invoke(&instruction, accounts)?; } msg!("Test no instruction data"); { let instruction = create_instruction( *accounts[INVOKED_PROGRAM_INDEX].key, &[(accounts[ARGUMENT_INDEX].key, true, true)], vec![], ); invoke(&instruction, accounts)?; } msg!("Test refcell usage"); { let writable = INVOKED_ARGUMENT_INDEX; let readable = INVOKED_PROGRAM_INDEX; let instruction = create_instruction( *accounts[INVOKED_PROGRAM_INDEX].key, &[ (accounts[writable].key, true, true), (accounts[readable].key, false, false), ], vec![RETURN_OK, 1, 2, 3, 4, 5], ); // success with this account configuration as a check invoke(&instruction, accounts)?; { // writable but lamports borrow_mut'd let _ref_mut = accounts[writable].try_borrow_mut_lamports()?; assert_eq!( invoke(&instruction, accounts), Err(ProgramError::AccountBorrowFailed) ); } { // writable but data borrow_mut'd let _ref_mut = accounts[writable].try_borrow_mut_data()?; assert_eq!( invoke(&instruction, accounts), Err(ProgramError::AccountBorrowFailed) ); } { // writable but lamports borrow'd let _ref_mut = accounts[writable].try_borrow_lamports()?; assert_eq!( invoke(&instruction, accounts), Err(ProgramError::AccountBorrowFailed) ); } { // writable but data borrow'd let _ref_mut = accounts[writable].try_borrow_data()?; assert_eq!( invoke(&instruction, accounts), Err(ProgramError::AccountBorrowFailed) ); } { // readable but lamports borrow_mut'd let _ref_mut = accounts[readable].try_borrow_mut_lamports()?; assert_eq!( invoke(&instruction, accounts), Err(ProgramError::AccountBorrowFailed) ); } { // readable but data borrow_mut'd let _ref_mut = accounts[readable].try_borrow_mut_data()?; assert_eq!( invoke(&instruction, accounts), Err(ProgramError::AccountBorrowFailed) ); } { // readable but lamports borrow'd let _ref_mut = accounts[readable].try_borrow_lamports()?; invoke(&instruction, accounts)?; } { // readable but data borrow'd let _ref_mut = accounts[readable].try_borrow_data()?; invoke(&instruction, accounts)?; } } msg!("Test create_program_address"); { assert_eq!( &Pubkey::create_program_address( &[b"You pass butter", &[bump_seed1]], program_id )?, accounts[DERIVED_KEY1_INDEX].key ); assert_eq!( Pubkey::create_program_address(&[b"You pass butter"], &Pubkey::default()) .unwrap_err(), PubkeyError::InvalidSeeds ); } msg!("Test try_find_program_address"); { let (address, bump_seed) = Pubkey::try_find_program_address(&[b"You pass butter"], program_id).unwrap(); assert_eq!(&address, accounts[DERIVED_KEY1_INDEX].key); assert_eq!(bump_seed, bump_seed1); assert_eq!( Pubkey::create_program_address(&[b"You pass butter"], &Pubkey::default()) .unwrap_err(), PubkeyError::InvalidSeeds ); } msg!("Test derived signers"); { assert!(!accounts[DERIVED_KEY1_INDEX].is_signer); assert!(!accounts[DERIVED_KEY2_INDEX].is_signer); assert!(!accounts[DERIVED_KEY3_INDEX].is_signer); let invoked_instruction = create_instruction( *accounts[INVOKED_PROGRAM_INDEX].key, &[ (accounts[INVOKED_PROGRAM_INDEX].key, false, false), (accounts[DERIVED_KEY1_INDEX].key, true, true), (accounts[DERIVED_KEY2_INDEX].key, true, false), (accounts[DERIVED_KEY3_INDEX].key, false, false), ], vec![DERIVED_SIGNERS, bump_seed2, bump_seed3], ); invoke_signed( &invoked_instruction, accounts, &[&[b"You pass butter", &[bump_seed1]]], )?; } msg!("Test readonly with writable account"); { let invoked_instruction = create_instruction( *accounts[INVOKED_PROGRAM_INDEX].key, &[(accounts[ARGUMENT_INDEX].key, false, true)], vec![VERIFY_WRITER], ); invoke(&invoked_instruction, accounts)?; } msg!("Test nested invoke"); { assert!(accounts[ARGUMENT_INDEX].is_signer); **accounts[ARGUMENT_INDEX].lamports.borrow_mut() -= 5; **accounts[INVOKED_ARGUMENT_INDEX].lamports.borrow_mut() += 5; msg!("First invoke"); let instruction = create_instruction( *accounts[INVOKED_PROGRAM_INDEX].key, &[ (accounts[ARGUMENT_INDEX].key, true, true), (accounts[INVOKED_ARGUMENT_INDEX].key, true, true), (accounts[INVOKED_PROGRAM_DUP_INDEX].key, false, false), (accounts[INVOKED_PROGRAM_DUP_INDEX].key, false, false), ], vec![NESTED_INVOKE], ); invoke(&instruction, accounts)?; msg!("2nd invoke from first program"); invoke(&instruction, accounts)?; assert_eq!(accounts[ARGUMENT_INDEX].lamports(), 42 - 5 + 1 + 1 + 1 + 1); assert_eq!( accounts[INVOKED_ARGUMENT_INDEX].lamports(), 10 + 5 - 1 - 1 - 1 - 1 ); } msg!("Verify data values are retained and updated"); { let data = accounts[ARGUMENT_INDEX].try_borrow_data()?; for i in 0..100 { assert_eq!(data[i as usize], i); } let data = accounts[INVOKED_ARGUMENT_INDEX].try_borrow_data()?; for i in 0..10 { assert_eq!(data[i as usize], i); } } } TEST_PRIVILEGE_ESCALATION_SIGNER => { msg!("Test privilege escalation signer"); let mut invoked_instruction = create_instruction( *accounts[INVOKED_PROGRAM_INDEX].key, &[(accounts[DERIVED_KEY3_INDEX].key, false, false)], vec![VERIFY_PRIVILEGE_ESCALATION], ); invoke(&invoked_instruction, accounts)?; // Signer privilege escalation will always fail the whole transaction invoked_instruction.accounts[0].is_signer = true; invoke(&invoked_instruction, accounts)?; } TEST_PRIVILEGE_ESCALATION_WRITABLE => { msg!("Test privilege escalation writable"); let mut invoked_instruction = create_instruction( *accounts[INVOKED_PROGRAM_INDEX].key, &[(accounts[DERIVED_KEY3_INDEX].key, false, false)], vec![VERIFY_PRIVILEGE_ESCALATION], ); invoke(&invoked_instruction, accounts)?; // Writable privilege escalation will always fail the whole transaction invoked_instruction.accounts[0].is_writable = true; invoke(&invoked_instruction, accounts)?; } TEST_PPROGRAM_NOT_EXECUTABLE => { msg!("Test program not executable"); let instruction = create_instruction( *accounts[ARGUMENT_INDEX].key, &[(accounts[ARGUMENT_INDEX].key, true, true)], vec![RETURN_OK], ); invoke(&instruction, accounts)?; } TEST_EMPTY_ACCOUNTS_SLICE => { msg!("Empty accounts slice"); let instruction = create_instruction( *accounts[INVOKED_PROGRAM_INDEX].key, &[(accounts[INVOKED_ARGUMENT_INDEX].key, false, false)], vec![], ); invoke(&instruction, &[])?; } TEST_CAP_SEEDS => { msg!("Test program max seeds"); let instruction = create_instruction(*accounts[INVOKED_PROGRAM_INDEX].key, &[], vec![]); invoke_signed( &instruction, accounts, &[&[ b"1", b"2", b"3", b"4", b"5", b"6", b"7", b"8", b"9", b"0", b"1", b"2", b"3", b"4", b"5", b"6", b"7", ]], )?; } TEST_CAP_SIGNERS => { msg!("Test program max signers"); let instruction = create_instruction(*accounts[INVOKED_PROGRAM_INDEX].key, &[], vec![]); invoke_signed( &instruction, accounts, &[ &[b"1"], &[b"2"], &[b"3"], &[b"4"], &[b"5"], &[b"6"], &[b"7"], &[b"8"], &[b"9"], &[b"0"], &[b"1"], &[b"2"], &[b"3"], &[b"4"], &[b"5"], &[b"6"], &[b"7"], ], )?; } TEST_ALLOC_ACCESS_VIOLATION => { msg!("Test resize violation"); let pubkey = *accounts[FROM_INDEX].key; let owner = *accounts[FROM_INDEX].owner; let ptr = accounts[FROM_INDEX].data.borrow().as_ptr() as u64 as *mut _; let len = accounts[FROM_INDEX].data_len(); let mut data = unsafe { std::slice::from_raw_parts_mut(ptr, len) }; let mut lamports = accounts[FROM_INDEX].lamports(); let from_info = AccountInfo::new( &pubkey, false, true, &mut lamports, &mut data, &owner, false, 0, ); let pubkey = *accounts[DERIVED_KEY1_INDEX].key; let owner = *accounts[DERIVED_KEY1_INDEX].owner; // Point to top edge of heap, attempt to allocate into unprivileged memory let mut data = unsafe { std::slice::from_raw_parts_mut(0x300007ff8 as *mut _, 0) }; let mut lamports = accounts[DERIVED_KEY1_INDEX].lamports(); let derived_info = AccountInfo::new( &pubkey, false, true, &mut lamports, &mut data, &owner, false, 0, ); let pubkey = *accounts[SYSTEM_PROGRAM_INDEX].key; let owner = *accounts[SYSTEM_PROGRAM_INDEX].owner; let ptr = accounts[SYSTEM_PROGRAM_INDEX].data.borrow().as_ptr() as u64 as *mut _; let len = accounts[SYSTEM_PROGRAM_INDEX].data_len(); let mut data = unsafe { std::slice::from_raw_parts_mut(ptr, len) }; let mut lamports = accounts[SYSTEM_PROGRAM_INDEX].lamports(); let system_info = AccountInfo::new( &pubkey, false, false, &mut lamports, &mut data, &owner, true, 0, ); let instruction = system_instruction::create_account( accounts[FROM_INDEX].key, accounts[DERIVED_KEY1_INDEX].key, 42, MAX_PERMITTED_DATA_INCREASE as u64, program_id, ); invoke_signed( &instruction, &[system_info.clone(), from_info.clone(), derived_info.clone()], &[&[b"You pass butter", &[bump_seed1]]], )?; } TEST_INSTRUCTION_DATA_TOO_LARGE => { msg!("Test instruction data too large"); let instruction = create_instruction(*accounts[INVOKED_PROGRAM_INDEX].key, &[], vec![0; 1500]); invoke_signed(&instruction, &[], &[])?; } TEST_INSTRUCTION_META_TOO_LARGE => { msg!("Test instruction metas too large"); let instruction = create_instruction( *accounts[INVOKED_PROGRAM_INDEX].key, &[(&Pubkey::default(), false, false); 40], vec![], ); invoke_signed(&instruction, &[], &[])?; } TEST_RETURN_ERROR => { msg!("Test return error"); let instruction = create_instruction( *accounts[INVOKED_PROGRAM_INDEX].key, &[(accounts[INVOKED_ARGUMENT_INDEX].key, false, true)], vec![RETURN_ERROR], ); let _ = invoke(&instruction, accounts); } _ => panic!(), } Ok(()) } #[cfg(test)] mod test { use super::*; #[test] fn create_program_address_is_defined() { assert_eq!( Pubkey::create_program_address(&[b"You pass butter"], &Pubkey::default()).unwrap_err(), PubkeyError::InvalidSeeds ); } }