From fa77f78787dad9abca209e2b252c0728c23cc201 Mon Sep 17 00:00:00 2001 From: Jac0xb Date: Sun, 17 Dec 2023 21:50:00 -0500 Subject: [PATCH] Implement token account balance assertion --- macros/src/lib.rs | 4 +- programs/lighthouse/program/src/lib.rs | 2 +- .../program/src/processor/v1/assert.rs | 236 ++++++++---------- .../program/src/structs/assert/assertion.rs | 63 +++++ .../program/src/structs/assert/mod.rs | 2 + .../program/src/structs/assertion.rs | 26 -- .../program/src/structs/data_value.rs | 56 +++-- .../lighthouse/program/src/structs/mod.rs | 2 - .../program/src/structs/operator.rs | 2 - .../program/src/structs/write/program.rs | 9 +- .../program/src/structs/write_type.rs | 2 +- programs/lighthouse/program/src/utils.rs | 20 +- .../program/tests/suites/assert/mod.rs | 1 + .../suites/assert/token_account_balance.rs | 44 ++++ .../lighthouse/program/tests/utils/program.rs | 100 +++++++- .../lighthouse/program/tests/utils/utils.rs | 12 +- 16 files changed, 384 insertions(+), 197 deletions(-) create mode 100644 programs/lighthouse/program/src/structs/assert/assertion.rs delete mode 100644 programs/lighthouse/program/src/structs/assertion.rs create mode 100644 programs/lighthouse/program/tests/suites/assert/token_account_balance.rs diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 58dca09..7b04f1e 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -4,8 +4,8 @@ use proc_macro::TokenStream; // use proc_macro::TokenStream; use quote::quote; use syn::{ - parse_macro_input, punctuated::Punctuated, token::Comma, Attribute, Data, DataStruct, - DeriveInput, Field, Fields, + parse_macro_input, punctuated::Punctuated, token::Comma, Data, DataStruct, DeriveInput, Field, + Fields, }; // use syn::{parse_macro_input, DeriveInput}; diff --git a/programs/lighthouse/program/src/lib.rs b/programs/lighthouse/program/src/lib.rs index 27df9bd..4522524 100644 --- a/programs/lighthouse/program/src/lib.rs +++ b/programs/lighthouse/program/src/lib.rs @@ -44,7 +44,7 @@ pub mod lighthouse { ctx: Context<'_, '_, '_, 'info, AssertV1<'info>>, assertions: Vec, logical_expression: Option>, - options: Option, + options: Option, ) -> Result<()> { processor::assert(ctx, assertions, logical_expression, options) } diff --git a/programs/lighthouse/program/src/processor/v1/assert.rs b/programs/lighthouse/program/src/processor/v1/assert.rs index dd03a6b..f232af9 100644 --- a/programs/lighthouse/program/src/processor/v1/assert.rs +++ b/programs/lighthouse/program/src/processor/v1/assert.rs @@ -1,11 +1,10 @@ -use std::collections::BTreeSet; - use anchor_lang::prelude::*; use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::program_pack::Pack; use crate::error::ProgramError; use crate::structs::{AccountInfoDataField, Assertion, AssertionState, Expression}; -use crate::utils::print_result; +use crate::utils::print_assertion_result; #[derive(Accounts)] pub struct AssertV1<'info> { @@ -14,7 +13,7 @@ pub struct AssertV1<'info> { } #[derive(BorshDeserialize, BorshSerialize, Debug)] -pub struct Config { +pub struct AssertionConfig { pub verbose: bool, } @@ -22,192 +21,163 @@ pub fn assert<'info>( ctx: Context<'_, '_, '_, 'info, AssertV1<'info>>, assertions: Vec, logical_expression: Option>, - options: Option, + config: Option, ) -> Result<()> { let remaining_accounts = &mut ctx.remaining_accounts.iter(); - - let verbose = options.map(|options| options.verbose).unwrap_or(false); let mut assertion_state = AssertionState::new(assertions.clone(), logical_expression)?; for (i, assertion) in assertions.into_iter().enumerate() { - let mut assertion_result = false; - - match assertion { + let assertion_result: Result = match &assertion { Assertion::AccountOwnedBy(pubkey, operator) => { let account = remaining_accounts.next().unwrap(); - assertion_result = account.owner.key().eq(&pubkey); + let result = account.owner.key().eq(pubkey); let value_str = account.owner.key().to_string(); let expected_value_str = pubkey.to_string(); - if verbose { - print_result(assertion_result, i, operator, value_str, expected_value_str); - } + print_assertion_result( + &config, + assertion.format(), + result, + i, + operator, + value_str, + expected_value_str, + ); + + Ok(result) } Assertion::Memory(cache_offset, operator, memory_value) => { let cache = ctx.accounts.cache.as_ref().unwrap(); // TODO: Graceful error handling let cache_data = cache.try_borrow_data()?; // TODO: Graceful error handling let (value_str, expected_value_str, result) = memory_value - .deserialize_and_compare(cache_data, (cache_offset + 8) as usize, &operator)?; - - assertion_result = result; - - if verbose { - print_result(assertion_result, i, operator, value_str, expected_value_str); - } + .deserialize_and_compare(cache_data, (cache_offset + 8) as usize, operator)?; + + print_assertion_result( + &config, + assertion.format(), + result, + i, + operator, + value_str, + expected_value_str, + ); + + Ok(result) } Assertion::AccountData(account_offset, operator, memory_value) => { let account = remaining_accounts.next().unwrap(); let account_data = account.try_borrow_data()?; let (value_str, expected_value_str, result) = memory_value - .deserialize_and_compare(account_data, account_offset as usize, &operator)?; - - assertion_result = result; - - if verbose { - print_result(assertion_result, i, operator, value_str, expected_value_str); - } + .deserialize_and_compare(account_data, (*account_offset) as usize, operator)?; + + print_assertion_result( + &config, + assertion.format(), + result, + i, + operator, + value_str, + expected_value_str, + ); + + Ok(result) + } + Assertion::AccountBalance(balance_value, operator) => { + let account = remaining_accounts.next().unwrap(); + let result = operator.evaluate(&**account.try_borrow_lamports()?, balance_value); + + let value_str = account.get_lamports().to_string(); + let expected_value_str = balance_value.to_string(); + + print_assertion_result( + &config, + assertion.format(), + result, + i, + operator, + value_str, + expected_value_str, + ); + + Ok(result) } - Assertion::AccountBalance(expected_balance, operator) => { + Assertion::TokenAccountBalance(balance_value, operator) => { let account = remaining_accounts.next().unwrap(); - assertion_result = - operator.evaluate(&**account.try_borrow_lamports()?, &expected_balance); - - if verbose { - print_result( - assertion_result, - i, - operator, - account.get_lamports().to_string(), - expected_balance.to_string(), - ); + if account.owner.eq(&spl_associated_token_account::id()) { + return Err(ProgramError::InvalidAccount.into()); } - } - Assertion::TokenAccountBalance(_, _) => { - return Err(ProgramError::Unimplemented.into()); + + let token_account = + spl_token::state::Account::unpack_from_slice(&account.try_borrow_data()?)?; + + let result = operator.evaluate(&token_account.amount, balance_value); + + let value_str = token_account.amount.to_string(); + let expected_value_str = balance_value.to_string(); + + print_assertion_result( + &config, + assertion.format(), + result, + i, + operator, + value_str, + expected_value_str, + ); + + Ok(result) } Assertion::AccountInfo(account_info_fields, operator) => { let account = remaining_accounts.next().unwrap(); + let operator_result = true; for account_info_field in account_info_fields { - match account_info_field { + let operator_result = match account_info_field { AccountInfoDataField::Key(pubkey) => { - assertion_result = operator.evaluate(&account.key(), &pubkey); + operator.evaluate(&account.key(), pubkey) } AccountInfoDataField::Owner(pubkey) => { - assertion_result = operator.evaluate(account.owner, &pubkey); + operator.evaluate(account.owner, pubkey) } AccountInfoDataField::Lamports(lamports) => { - assertion_result = - operator.evaluate(&account.get_lamports(), &lamports); + operator.evaluate(&account.get_lamports(), lamports) } AccountInfoDataField::DataLength(data_length) => { - assertion_result = - operator.evaluate(&(account.data_len() as u64), &data_length); + operator.evaluate(&(account.data_len() as u64), data_length) } AccountInfoDataField::Executable(executable) => { - assertion_result = operator.evaluate(&account.executable, &executable); + operator.evaluate(&account.executable, executable) } AccountInfoDataField::IsSigner(is_signer) => { - assertion_result = operator.evaluate(&account.is_signer, &is_signer); + operator.evaluate(&account.is_signer, is_signer) } AccountInfoDataField::IsWritable(is_writable) => { - assertion_result = - operator.evaluate(&account.is_writable, &is_writable); + operator.evaluate(&account.is_writable, is_writable) } AccountInfoDataField::RentEpoch(rent_epoch) => { - assertion_result = - operator.evaluate(&account.rent_epoch as &u64, &rent_epoch); + operator.evaluate(&account.rent_epoch as &u64, rent_epoch) } + }; + + if !operator_result { + break; } } - } - } - - assertion_state.record_result(i, assertion_result)?; - // assertion_results.push(assertion_result); + Ok(operator_result) + } + }; - // if (logical_expression.is_none() - // || !logically_dependent_assertions - // .as_ref() - // .unwrap() - // .contains(&(i as u8))) - // && !assertion_result - // { - // return Err(ProgramError::AssertionFailed.into()); - // } + assertion_state.record_result(i, assertion_result?)?; } msg!("assertion_state: {:?}", assertion_state); assertion_state.evaluate()?; - // if let Some(logical_expressions) = &logical_expression { - // for logical_expression in logical_expressions { - // match logical_expression { - // Expression::And(assertion_indexes) => { - // let mut result = true; - - // for assertion_index in assertion_indexes { - // result = result && assertion_results[*assertion_index as usize]; - // } - - // if verbose { - // msg!( - // "{} Expression::And -> {:?} {}", - // if result { - // "[✅] SUCCESS" - // } else { - // "[❌] FAIL " - // }, - // result, - // assertion_indexes - // .iter() - // .map(|i| format!("[{}]", i)) - // .collect::>() - // .join(" AND ") - // ); - // } - - // if !result { - // return Err(ProgramError::AssertionFailed.into()); - // } - // } - // Expression::Or(assertion_indexes) => { - // let mut result = false; - - // for assertion_index in assertion_indexes { - // result = result || assertion_results[*assertion_index as usize]; - // } - - // if verbose { - // msg!( - // "{} Expression::Or -> {:?} {}", - // if result { - // "[✅] SUCCESS" - // } else { - // "[❌] FAIL " - // }, - // result, - // assertion_indexes - // .iter() - // .map(|i| format!("[{}]", i)) - // .collect::>() - // .join(" OR ") - // ); - // } - - // if !result { - // return Err(ProgramError::AssertionFailed.into()); - // } - // } - // } - // } - // } - Ok(()) } diff --git a/programs/lighthouse/program/src/structs/assert/assertion.rs b/programs/lighthouse/program/src/structs/assert/assertion.rs new file mode 100644 index 0000000..2691429 --- /dev/null +++ b/programs/lighthouse/program/src/structs/assert/assertion.rs @@ -0,0 +1,63 @@ +use anchor_lang::prelude::{ + borsh, + borsh::{BorshDeserialize, BorshSerialize}, +}; +use solana_program::pubkey::Pubkey; + +use crate::structs::{operator::Operator, AccountInfoDataField, DataValue}; + +#[derive(BorshDeserialize, BorshSerialize, Debug, Clone)] +pub enum Assertion { + // memory offset, assertion + Memory(u16, Operator, DataValue), + + AccountInfo(Vec, Operator), + + // account data offset, borsh type, operator + AccountData(u16, Operator, DataValue), + + // balance, operator + AccountBalance(u64, Operator), + + AccountOwnedBy(Pubkey, Operator), + + // token balance, operator + TokenAccountBalance(u64, Operator), + // TODO + // IsSigner, +} + +impl Assertion { + pub fn format(&self) -> String { + match self { + Assertion::Memory(offset, operator, value) => { + format!( + "Memory[{}] {} {}", + offset, + operator.format(), + value.format() + ) + } + Assertion::AccountData(offset, operator, value) => { + format!( + "AccountData[{}] {} {}", + offset, + operator.format(), + value.format() + ) + } + Assertion::AccountBalance(balance, operator) => { + format!("AccountBalance {} {}", balance, operator.format()) + } + Assertion::AccountOwnedBy(pubkey, operator) => { + format!("AccountOwnedBy {} {}", pubkey, operator.format()) + } + Assertion::TokenAccountBalance(balance, operator) => { + format!("TokenAccountBalance {} {}", balance, operator.format()) + } + Assertion::AccountInfo(fields, operator) => { + format!("AccountInfo {:?} {}", fields, operator.format()) + } + } + } +} diff --git a/programs/lighthouse/program/src/structs/assert/mod.rs b/programs/lighthouse/program/src/structs/assert/mod.rs index e8849ed..cb9b439 100644 --- a/programs/lighthouse/program/src/structs/assert/mod.rs +++ b/programs/lighthouse/program/src/structs/assert/mod.rs @@ -1,3 +1,5 @@ +pub mod assertion; pub mod assertion_state; +pub use assertion::*; pub use assertion_state::*; diff --git a/programs/lighthouse/program/src/structs/assertion.rs b/programs/lighthouse/program/src/structs/assertion.rs deleted file mode 100644 index bf10eba..0000000 --- a/programs/lighthouse/program/src/structs/assertion.rs +++ /dev/null @@ -1,26 +0,0 @@ -use anchor_lang::prelude::{ - borsh, - borsh::{BorshDeserialize, BorshSerialize}, -}; -use solana_program::pubkey::Pubkey; - -use super::{AccountInfoDataField, DataValue, Operator}; - -#[derive(BorshDeserialize, BorshSerialize, Debug, Clone)] -pub enum Assertion { - // memory offset, assertion - Memory(u16, Operator, DataValue), - - // account data offset, borsh type, operator - AccountData(u16, Operator, DataValue), - - // balance, operator - AccountBalance(u64, Operator), - - AccountOwnedBy(Pubkey, Operator), - - // token balance, operator - TokenAccountBalance(u64, Operator), - - AccountInfo(Vec, Operator), -} diff --git a/programs/lighthouse/program/src/structs/data_value.rs b/programs/lighthouse/program/src/structs/data_value.rs index ab0a61b..108ee11 100644 --- a/programs/lighthouse/program/src/structs/data_value.rs +++ b/programs/lighthouse/program/src/structs/data_value.rs @@ -44,6 +44,34 @@ pub enum DataValue { Pubkey(Pubkey), } +impl DataValue { + pub fn format(&self) -> String { + match self { + DataValue::Bool(value) => value.to_string(), + DataValue::U8(value) => value.to_string(), + DataValue::I8(value) => value.to_string(), + DataValue::U16(value) => value.to_string(), + DataValue::I16(value) => value.to_string(), + DataValue::U32(value) => value.to_string(), + DataValue::I32(value) => value.to_string(), + DataValue::U64(value) => value.to_string(), + DataValue::I64(value) => value.to_string(), + DataValue::U128(value) => value.to_string(), + DataValue::I128(value) => value.to_string(), + DataValue::Bytes(value) => { + let value_str = value + .iter() + .take(15) + .map(|byte| format!("{:02x}", byte)) + .collect::>() + .join(""); + format!("0x{}", value_str) + } + DataValue::Pubkey(value) => value.to_string(), + } + } +} + impl DataValue { pub fn get_data_type(&self) -> DataType { match self { @@ -144,7 +172,7 @@ impl DataValue { } pub fn deserialize_and_compare( - self, + &self, data: Ref<'_, &mut [u8]>, offset: usize, operator: &Operator, @@ -161,7 +189,7 @@ impl DataValue { let value_str = value.to_string(); let expected_value_str = expected_value.to_string(); - let assertion_result = operator.evaluate(&value, &expected_value); + let assertion_result = operator.evaluate(&value, expected_value); Ok((value_str, expected_value_str, assertion_result)) } DataValue::U8(expected_value) => { @@ -172,7 +200,7 @@ impl DataValue { let value_str = value.to_string(); let expected_value_str = expected_value.to_string(); - let assertion_result = operator.evaluate(&value, &expected_value); + let assertion_result = operator.evaluate(&value, expected_value); Ok((value_str, expected_value_str, assertion_result)) } DataValue::I8(expected_value) => { @@ -183,7 +211,7 @@ impl DataValue { let value_str = value.to_string(); let expected_value_str = expected_value.to_string(); - let assertion_result = operator.evaluate(&value, &expected_value); + let assertion_result = operator.evaluate(&value, expected_value); Ok((value_str, expected_value_str, assertion_result)) } DataValue::U16(expected_value) => { @@ -194,7 +222,7 @@ impl DataValue { let value_str = value.to_string(); let expected_value_str = expected_value.to_string(); - let assertion_result = operator.evaluate(&value, &expected_value); + let assertion_result = operator.evaluate(&value, expected_value); Ok((value_str, expected_value_str, assertion_result)) } DataValue::I16(expected_value) => { @@ -205,7 +233,7 @@ impl DataValue { let value_str = value.to_string(); let expected_value_str = expected_value.to_string(); - let assertion_result = operator.evaluate(&value, &expected_value); + let assertion_result = operator.evaluate(&value, expected_value); Ok((value_str, expected_value_str, assertion_result)) } DataValue::U32(expected_value) => { @@ -216,7 +244,7 @@ impl DataValue { let value_str = value.to_string(); let expected_value_str = expected_value.to_string(); - let assertion_result = operator.evaluate(&value, &expected_value); + let assertion_result = operator.evaluate(&value, expected_value); Ok((value_str, expected_value_str, assertion_result)) } DataValue::I32(expected_value) => { @@ -227,7 +255,7 @@ impl DataValue { let value_str = value.to_string(); let expected_value_str = expected_value.to_string(); - let assertion_result = operator.evaluate(&value, &expected_value); + let assertion_result = operator.evaluate(&value, expected_value); Ok((value_str, expected_value_str, assertion_result)) } DataValue::U64(expected_value) => { @@ -238,7 +266,7 @@ impl DataValue { let value_str = value.to_string(); let expected_value_str = expected_value.to_string(); - let assertion_result = operator.evaluate(&value, &expected_value); + let assertion_result = operator.evaluate(&value, expected_value); Ok((value_str, expected_value_str, assertion_result)) } DataValue::I64(expected_value) => { @@ -249,7 +277,7 @@ impl DataValue { let value_str = value.to_string(); let expected_value_str = expected_value.to_string(); - let assertion_result = operator.evaluate(&value, &expected_value); + let assertion_result = operator.evaluate(&value, expected_value); Ok((value_str, expected_value_str, assertion_result)) } DataValue::U128(expected_value) => { @@ -260,7 +288,7 @@ impl DataValue { let value_str = value.to_string(); let expected_value_str = expected_value.to_string(); - let assertion_result = operator.evaluate(&value, &expected_value); + let assertion_result = operator.evaluate(&value, expected_value); Ok((value_str, expected_value_str, assertion_result)) } DataValue::I128(expected_value) => { @@ -271,7 +299,7 @@ impl DataValue { let value_str = value.to_string(); let expected_value_str = expected_value.to_string(); - let assertion_result = operator.evaluate(&value, &expected_value); + let assertion_result = operator.evaluate(&value, expected_value); Ok((value_str, expected_value_str, assertion_result)) } DataValue::Bytes(expected_value) => { @@ -297,7 +325,7 @@ impl DataValue { .map(|byte| format!("{:02x}", byte)) .collect::>() .join(""); - let assertion_result = operator.evaluate(&value, &expected_value); + let assertion_result = operator.evaluate(&value, expected_value); Ok((value_str, expected_value_str, assertion_result)) } @@ -315,7 +343,7 @@ impl DataValue { let value_str = value.to_string(); let expected_value_str = expected_value.to_string(); - let assertion_result = operator.evaluate(&value, &expected_value); + let assertion_result = operator.evaluate(&value, expected_value); Ok((value_str, expected_value_str, assertion_result)) } diff --git a/programs/lighthouse/program/src/structs/mod.rs b/programs/lighthouse/program/src/structs/mod.rs index ba3e506..1cec384 100644 --- a/programs/lighthouse/program/src/structs/mod.rs +++ b/programs/lighthouse/program/src/structs/mod.rs @@ -1,5 +1,4 @@ pub mod assert; -pub mod assertion; pub mod borsh_field; pub mod data_value; pub mod expression; @@ -8,7 +7,6 @@ pub mod write; pub mod write_type; pub use assert::*; -pub use assertion::*; pub use borsh_field::*; pub use data_value::*; pub use expression::*; diff --git a/programs/lighthouse/program/src/structs/operator.rs b/programs/lighthouse/program/src/structs/operator.rs index 9f39806..f31de75 100644 --- a/programs/lighthouse/program/src/structs/operator.rs +++ b/programs/lighthouse/program/src/structs/operator.rs @@ -11,8 +11,6 @@ pub enum Operator { LessThan, GreaterThanOrEqual, LessThanOrEqual, - // Todo - // WithinThreshold } impl Operator { diff --git a/programs/lighthouse/program/src/structs/write/program.rs b/programs/lighthouse/program/src/structs/write/program.rs index c4a9361..b3c1c2a 100644 --- a/programs/lighthouse/program/src/structs/write/program.rs +++ b/programs/lighthouse/program/src/structs/write/program.rs @@ -1,9 +1,6 @@ -use anchor_lang::{ - prelude::{ - borsh, - borsh::{BorshDeserialize, BorshSerialize}, - }, - Id, +use anchor_lang::prelude::{ + borsh, + borsh::{BorshDeserialize, BorshSerialize}, }; use solana_program::pubkey::Pubkey; diff --git a/programs/lighthouse/program/src/structs/write_type.rs b/programs/lighthouse/program/src/structs/write_type.rs index 15ff18f..0f3e67a 100644 --- a/programs/lighthouse/program/src/structs/write_type.rs +++ b/programs/lighthouse/program/src/structs/write_type.rs @@ -1,4 +1,4 @@ -use anchor_lang::{accounts::account, prelude::*}; +use anchor_lang::prelude::*; use borsh::{BorshDeserialize, BorshSerialize}; use super::DataValue; diff --git a/programs/lighthouse/program/src/utils.rs b/programs/lighthouse/program/src/utils.rs index e2e645b..667b091 100644 --- a/programs/lighthouse/program/src/utils.rs +++ b/programs/lighthouse/program/src/utils.rs @@ -1,22 +1,32 @@ -use crate::structs::Operator; +use crate::{processor::assert::AssertionConfig, structs::Operator}; use solana_program::msg; -pub fn print_result( +pub fn print_assertion_result( + config: &Option, + assertion_info: String, assertion_result: bool, assertion_index: usize, - operator: Operator, + operator: &Operator, value_str: String, expected_value_str: String, ) { + if let Some(config) = config { + if !config.verbose { + return; + } + } else { + return; + } + msg!( - "{} {} Assertion::Memory ({}) -> {} {} {}", + "{} {} {} -> {} {} {}", format!("[{:?}]", assertion_index), if assertion_result { "[✅] SUCCESS" } else { "[❌] FAIL " }, - "Cache...".to_string(), + assertion_info, value_str, operator.format(), expected_value_str, diff --git a/programs/lighthouse/program/tests/suites/assert/mod.rs b/programs/lighthouse/program/tests/suites/assert/mod.rs index c8b4296..4eec92d 100644 --- a/programs/lighthouse/program/tests/suites/assert/mod.rs +++ b/programs/lighthouse/program/tests/suites/assert/mod.rs @@ -1,3 +1,4 @@ pub mod account_balance; pub mod account_data; pub mod logical_expression; +pub mod token_account_balance; diff --git a/programs/lighthouse/program/tests/suites/assert/token_account_balance.rs b/programs/lighthouse/program/tests/suites/assert/token_account_balance.rs new file mode 100644 index 0000000..c7d1474 --- /dev/null +++ b/programs/lighthouse/program/tests/suites/assert/token_account_balance.rs @@ -0,0 +1,44 @@ +use anchor_spl::associated_token::get_associated_token_address; +use lighthouse::structs::{Assertion, Operator}; +use solana_program_test::tokio; +use solana_sdk::signer::Signer; + +use crate::utils::process_transaction_assert_success; +use crate::utils::program::{create_mint, mint_to}; +use crate::utils::{ + context::TestContext, + program::{create_user, Program}, +}; + +#[tokio::test] +async fn test_basic() { + let context = &mut TestContext::new().await.unwrap(); + let mut program = Program::new(context.client()); + let user = create_user(context).await.unwrap(); + + let (tx, mint) = create_mint(context, &user).await.unwrap(); + process_transaction_assert_success(context, Ok(tx)).await; + + let tx = mint_to(context, &mint.pubkey(), &user, &user.pubkey(), 100) + .await + .unwrap(); + process_transaction_assert_success(context, Ok(tx)).await; + + let token_account = get_associated_token_address(&user.pubkey(), &mint.pubkey()); + let mut tx_builder = program.create_assertion( + &user, + vec![ + Assertion::TokenAccountBalance(0, Operator::GreaterThan), + Assertion::TokenAccountBalance(101, Operator::LessThan), + Assertion::TokenAccountBalance(100, Operator::LessThanOrEqual), + Assertion::TokenAccountBalance(100, Operator::GreaterThanOrEqual), + Assertion::TokenAccountBalance(100, Operator::Equal), + Assertion::TokenAccountBalance(99, Operator::NotEqual), + ], + vec![token_account; 6], + None, + None, + ); + + process_transaction_assert_success(context, tx_builder.to_transaction(vec![]).await).await; +} diff --git a/programs/lighthouse/program/tests/utils/program.rs b/programs/lighthouse/program/tests/utils/program.rs index 7566bc2..80b2f17 100644 --- a/programs/lighthouse/program/tests/utils/program.rs +++ b/programs/lighthouse/program/tests/utils/program.rs @@ -9,15 +9,17 @@ use super::{ Error, Result, }; use anchor_lang::*; +use anchor_spl::associated_token; use lighthouse::{ - processor::Config, + processor::AssertionConfig, structs::{Assertion, Expression, WriteTypeParameter}, }; use solana_program::{ instruction::{AccountMeta, Instruction}, + program_pack::Pack, pubkey::Pubkey, rent::Rent, - system_program, sysvar, + system_instruction, system_program, sysvar, }; use solana_program_test::BanksClient; use solana_sdk::{ @@ -25,6 +27,7 @@ use solana_sdk::{ signer::signers::Signers, transaction::Transaction, }; +use spl_token::state::Mint; pub struct Program { client: BanksClient, @@ -107,7 +110,7 @@ impl Program { let data = lighthouse::instruction::AssertV1 { assertions, logical_expression, - options: Some(Config { verbose: true }), + options: Some(AssertionConfig { verbose: true }), }; self.tx_builder( @@ -120,7 +123,7 @@ impl Program { data: (lighthouse::instruction::AssertV1 { assertions: assertion_clone, logical_expression: logical_expression_clone, - options: Some(Config { verbose: true }), + options: Some(AssertionConfig { verbose: true }), }) .data(), }], @@ -277,3 +280,92 @@ pub async fn create_user(ctx: &mut TestContext) -> Result { Ok(user) } + +pub async fn create_mint(ctx: &mut TestContext, payer: &Keypair) -> Result<(Transaction, Keypair)> { + let mint = Keypair::new(); + + let mint_rent = Rent::default().minimum_balance(Mint::get_packed_len()); + let create_ix = system_instruction::create_account( + &payer.pubkey(), + &mint.pubkey(), + mint_rent, + Mint::get_packed_len() as u64, + &spl_token::id(), + ); + + let mint_ix = spl_token::instruction::initialize_mint2( + &spl_token::id(), + &mint.pubkey(), + &payer.pubkey(), + None, + 100, + ) + .unwrap(); + + let mut tx = Transaction::new_with_payer(&[create_ix, mint_ix], Some(&payer.pubkey())); + + let signers: &[Keypair; 2] = &[payer.insecure_clone(), mint.insecure_clone()]; + + // print all the accounts in tx and is_signer + for (i, account) in tx.message().account_keys.iter().enumerate() { + println!("account: {} {}", account, tx.message.is_signer(i)); + } + + // print the signers pubkey in array + for signer in signers.iter() { + let pos = tx.get_signing_keypair_positions(&[signer.pubkey()]); + println!( + "signer: {} {}", + signer.insecure_clone().pubkey(), + pos.unwrap()[0].unwrap_or(0) + ); + } + + tx.try_partial_sign( + &signers.iter().collect::>(), + ctx.client().get_latest_blockhash().await.unwrap(), + ) + .unwrap(); + + Ok((tx, mint)) +} + +pub async fn mint_to( + ctx: &mut TestContext, + mint: &Pubkey, + authority: &Keypair, + dest: &Pubkey, + amount: u64, +) -> Result { + let token_account = associated_token::get_associated_token_address(dest, mint); + let create_account_ix = + spl_associated_token_account::instruction::create_associated_token_account( + &authority.pubkey(), + dest, + mint, + &spl_token::id(), + ); + + let mint_to_ix = spl_token::instruction::mint_to( + &spl_token::id(), + mint, + &token_account, + &authority.pubkey(), + &[], + amount, + ) + .unwrap(); + + let mut tx = + Transaction::new_with_payer(&[create_account_ix, mint_to_ix], Some(&authority.pubkey())); + + let signers: &[Keypair; 1] = &[authority.insecure_clone()]; + + tx.try_partial_sign( + &signers.iter().collect::>(), + ctx.client().get_latest_blockhash().await.unwrap(), + ) + .unwrap(); + + Ok(tx) +} diff --git a/programs/lighthouse/program/tests/utils/utils.rs b/programs/lighthouse/program/tests/utils/utils.rs index 8872d5c..bdbe0ee 100644 --- a/programs/lighthouse/program/tests/utils/utils.rs +++ b/programs/lighthouse/program/tests/utils/utils.rs @@ -25,7 +25,17 @@ pub async fn process_transaction_assert_success( ) { let tx = tx.expect("Should have been processed"); - let tx_metadata = process_transaction(context, &tx).await.unwrap(); + let tx_metadata = process_transaction(context, &tx).await; + + if let Err(err) = tx_metadata { + panic!("Transaction failed to process: {:?}", err); + } + + let tx_metadata = tx_metadata.unwrap(); + + if tx_metadata.result.is_err() { + println!("Tx Result {:?}", tx_metadata.result.clone().err()); + } let logs = tx_metadata.metadata.unwrap().log_messages; for log in logs {