From 1a35e848c087bab2fca5f7d3e47289c55354d4c6 Mon Sep 17 00:00:00 2001 From: Luis Aguilar Date: Tue, 21 May 2024 16:25:16 -0400 Subject: [PATCH 1/4] feat(post-conditions): improve emitted events for Clarity post-conditions --- stackslib/src/chainstate/stacks/db/blocks.rs | 4 ++ .../src/chainstate/stacks/db/transactions.rs | 40 ++++++++++++++++++- stackslib/src/chainstate/stacks/events.rs | 1 + stackslib/src/chainstate/stacks/mod.rs | 36 +++++++++++++++++ 4 files changed, 79 insertions(+), 2 deletions(-) diff --git a/stackslib/src/chainstate/stacks/db/blocks.rs b/stackslib/src/chainstate/stacks/db/blocks.rs index 971351b1d5..02cde99fbc 100644 --- a/stackslib/src/chainstate/stacks/db/blocks.rs +++ b/stackslib/src/chainstate/stacks/db/blocks.rs @@ -4219,6 +4219,7 @@ impl StacksChainState { microblock_header: None, tx_index: 0, vm_error: None, + failed_post_conditions: None, }; all_receipts.push(receipt); @@ -4314,6 +4315,7 @@ impl StacksChainState { microblock_header: None, tx_index: 0, vm_error: None, + failed_post_conditions: None, }) } Err(e) => { @@ -4420,6 +4422,7 @@ impl StacksChainState { microblock_header: None, tx_index: 0, vm_error: None, + failed_post_conditions: None, }; all_receipts.push(receipt); @@ -4528,6 +4531,7 @@ impl StacksChainState { microblock_header: None, tx_index: 0, vm_error: None, + failed_post_conditions: None, }; all_receipts.push(receipt); diff --git a/stackslib/src/chainstate/stacks/db/transactions.rs b/stackslib/src/chainstate/stacks/db/transactions.rs index 35ba532667..f1a3580e3d 100644 --- a/stackslib/src/chainstate/stacks/db/transactions.rs +++ b/stackslib/src/chainstate/stacks/db/transactions.rs @@ -97,6 +97,7 @@ impl StacksTransactionReceipt { microblock_header: None, tx_index: 0, vm_error: None, + failed_post_conditions: None, } } @@ -118,6 +119,7 @@ impl StacksTransactionReceipt { microblock_header: None, tx_index: 0, vm_error: None, + failed_post_conditions: None, } } @@ -127,6 +129,7 @@ impl StacksTransactionReceipt { result: Value, burned: u128, cost: ExecutionCost, + failed_post_conditions: Vec, ) -> StacksTransactionReceipt { StacksTransactionReceipt { transaction: tx.into(), @@ -139,6 +142,7 @@ impl StacksTransactionReceipt { microblock_header: None, tx_index: 0, vm_error: None, + failed_post_conditions: Some(failed_post_conditions), } } @@ -160,6 +164,7 @@ impl StacksTransactionReceipt { microblock_header: None, tx_index: 0, vm_error: None, + failed_post_conditions: None, } } @@ -169,6 +174,7 @@ impl StacksTransactionReceipt { burned: u128, analysis: ContractAnalysis, cost: ExecutionCost, + failed_post_conditions: Vec, ) -> StacksTransactionReceipt { StacksTransactionReceipt { transaction: tx.into(), @@ -181,6 +187,7 @@ impl StacksTransactionReceipt { microblock_header: None, tx_index: 0, vm_error: None, + failed_post_conditions: Some(failed_post_conditions), } } @@ -196,6 +203,7 @@ impl StacksTransactionReceipt { microblock_header: None, tx_index: 0, vm_error: None, + failed_post_conditions: None, } } @@ -238,6 +246,7 @@ impl StacksTransactionReceipt { microblock_header: None, tx_index: 0, vm_error: Some(error_string), + failed_post_conditions: None, } } @@ -257,6 +266,7 @@ impl StacksTransactionReceipt { microblock_header: None, tx_index: 0, vm_error: None, + failed_post_conditions: None, } } @@ -277,6 +287,7 @@ impl StacksTransactionReceipt { microblock_header: None, tx_index: 0, vm_error: Some(format!("{}", &error)), + failed_post_conditions: None, } } @@ -296,6 +307,7 @@ impl StacksTransactionReceipt { microblock_header: None, tx_index: 0, vm_error: Some(format!("{}", &error)), + failed_post_conditions: None, } } @@ -311,6 +323,7 @@ impl StacksTransactionReceipt { microblock_header: None, tx_index: 0, vm_error: None, + failed_post_conditions: None, } } @@ -578,6 +591,7 @@ impl StacksChainState { post_condition_mode: &TransactionPostConditionMode, origin_account: &StacksAccount, asset_map: &AssetMap, + failed_post_conditions: &mut Vec, ) -> Result { let mut checked_fungible_assets: HashMap> = HashMap::new(); @@ -608,6 +622,9 @@ impl StacksChainState { "Post-condition check failure on STX owned by {}: {:?} {:?} {}", account_principal, amount_sent_condition, condition_code, amount_sent ); + + failed_post_conditions.push(postcond.clone()); + return Ok(false); } @@ -651,6 +668,7 @@ impl StacksChainState { .unwrap_or(0); if !condition_code.check(u128::from(*amount_sent_condition), amount_sent) { info!("Post-condition check failure on fungible asset {} owned by {}: {} {:?} {}", &asset_id, account_principal, amount_sent_condition, condition_code, amount_sent); + failed_post_conditions.push(postcond.clone()); return Ok(false); } @@ -685,6 +703,7 @@ impl StacksChainState { .unwrap_or(&empty_assets); if !condition_code.check(asset_value, assets_sent) { info!("Post-condition check failure on non-fungible asset {} owned by {}: {:?} {:?}", &asset_id, account_principal, &asset_value, condition_code); + failed_post_conditions.push(postcond.clone()); return Ok(false); } @@ -1026,6 +1045,7 @@ impl StacksChainState { let cost_before = clarity_tx.cost_so_far(); let sponsor = tx.sponsor_address().map(|a| a.to_account_principal()); let epoch_id = clarity_tx.get_epoch(); + let mut failed_post_conditions = vec![]; let contract_call_resp = clarity_tx.run_contract_call( &origin_account.principal, @@ -1039,6 +1059,7 @@ impl StacksChainState { &tx.post_condition_mode, origin_account, asset_map, + &mut failed_post_conditions, ) .expect("FATAL: error while evaluating post-conditions") }, @@ -1081,13 +1102,15 @@ impl StacksChainState { "origin_nonce" => %origin_account.nonce, "contract_name" => %contract_id, "function_name" => %contract_call.function_name, - "function_args" => %VecDisplay(&contract_call.function_args)); + "function_args" => %VecDisplay(&contract_call.function_args), + "failed_post_conditions" => %VecDisplay(&failed_post_conditions)); let receipt = StacksTransactionReceipt::from_condition_aborted_contract_call( tx.clone(), events, value.expect("BUG: Post condition contract call must provide would-have-been-returned value"), assets.get_stx_burned_total()?, - total_cost); + total_cost, + failed_post_conditions); return Ok(receipt); } ClarityRuntimeTxError::CostError(cost_after, budget) => { @@ -1189,6 +1212,9 @@ impl StacksChainState { &contract_code_str, ast_rules, ); + + let mut failed_post_conditions = vec![]; + let (contract_ast, contract_analysis) = match analysis_resp { Ok(x) => x, Err(e) => { @@ -1274,6 +1300,7 @@ impl StacksChainState { &tx.post_condition_mode, origin_account, asset_map, + &mut failed_post_conditions, ) .expect("FATAL: error while evaluating post-conditions") }, @@ -1314,6 +1341,7 @@ impl StacksChainState { microblock_header: None, tx_index: 0, vm_error: Some(error.to_string()), + failed_post_conditions: None, }; return Ok(receipt); } @@ -1325,6 +1353,7 @@ impl StacksChainState { assets.get_stx_burned_total()?, contract_analysis, total_cost, + failed_post_conditions, ); return Ok(receipt); } @@ -6867,12 +6896,14 @@ pub mod test { let post_conditions = &test.1; let mode = &test.2; let origin = &test.3; + let mut failed_post_conditions = vec![]; let result = StacksChainState::check_transaction_postconditions( post_conditions, mode, origin, &ft_transfer_2, + &mut failed_post_conditions, // TODO: validate failed post-conditions ) .unwrap(); if result != expected_result { @@ -7220,12 +7251,14 @@ pub mod test { let post_conditions = &test.1; let mode = &test.2; let origin = &test.3; + let mut failed_post_conditions = vec![]; let result = StacksChainState::check_transaction_postconditions( post_conditions, mode, origin, &nft_transfer_2, + &mut failed_post_conditions, // TODO: check failed post-conditions ) .unwrap(); if result != expected_result { @@ -8038,11 +8071,14 @@ pub mod test { let post_condition_mode = &test.2; let origin_account = &test.3; + let mut failed_post_conditions = vec![]; + let result = StacksChainState::check_transaction_postconditions( post_conditions, post_condition_mode, origin_account, asset_map, + &mut failed_post_conditions, // TODO: check failed post-conditions ) .unwrap(); if result != expected_result { diff --git a/stackslib/src/chainstate/stacks/events.rs b/stackslib/src/chainstate/stacks/events.rs index a744d126b4..f6fb7108d0 100644 --- a/stackslib/src/chainstate/stacks/events.rs +++ b/stackslib/src/chainstate/stacks/events.rs @@ -56,6 +56,7 @@ pub struct StacksTransactionReceipt { pub tx_index: u32, /// This is really a string-formatted CheckError (which can't be clone()'ed) pub vm_error: Option, + pub failed_post_conditions: Option>, } #[derive(Clone)] diff --git a/stackslib/src/chainstate/stacks/mod.rs b/stackslib/src/chainstate/stacks/mod.rs index 92da8ac283..47faca681e 100644 --- a/stackslib/src/chainstate/stacks/mod.rs +++ b/stackslib/src/chainstate/stacks/mod.rs @@ -980,6 +980,18 @@ pub enum PostConditionPrincipal { Contract(StacksAddress, ContractName), } +impl fmt::Display for PostConditionPrincipal { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + PostConditionPrincipal::Origin => write!(f, "Origin"), + PostConditionPrincipal::Standard(address) => write!(f, "Standard({})", address), + PostConditionPrincipal::Contract(address, contract_name) => { + write!(f, "Contract({}, {})", address, contract_name) + } + } + } +} + impl PostConditionPrincipal { pub fn to_principal_data(&self, origin_principal: &PrincipalData) -> PrincipalData { match *self { @@ -1023,6 +1035,30 @@ pub enum TransactionPostCondition { ), } +impl fmt::Display for TransactionPostCondition { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + TransactionPostCondition::STX(principal, condition_code, amount) => { + write!(f, "STX({}, {:?}, {})", principal, condition_code, amount) + } + TransactionPostCondition::Fungible(principal, asset_info, condition_code, amount) => { + write!( + f, + "Fungible({}, {:?}, {:?}, {})", + principal, asset_info, condition_code, amount + ) + } + TransactionPostCondition::Nonfungible(principal, asset_info, value, condition_code) => { + write!( + f, + "Nonfungible({}, {:?}, {:?}, {:?})", + principal, asset_info, value, condition_code + ) + } + } + } +} + /// Post-condition modes for unspecified assets #[repr(u8)] #[derive(Debug, Clone, PartialEq, Copy, Serialize, Deserialize)] From f530ec5f34b700864aca3d6ffba70a2c4eac4599 Mon Sep 17 00:00:00 2001 From: Luis Aguilar Date: Sun, 26 May 2024 12:18:13 -0400 Subject: [PATCH 2/4] chore(post-conditions): add tests and matchers --- stackslib/src/chainstate/stacks/db/blocks.rs | 8 +- .../src/chainstate/stacks/db/transactions.rs | 412 +++++++++++++++--- stackslib/src/chainstate/stacks/events.rs | 3 +- stackslib/src/chainstate/stacks/mod.rs | 27 +- 4 files changed, 392 insertions(+), 58 deletions(-) diff --git a/stackslib/src/chainstate/stacks/db/blocks.rs b/stackslib/src/chainstate/stacks/db/blocks.rs index 02cde99fbc..b87ee7c33d 100644 --- a/stackslib/src/chainstate/stacks/db/blocks.rs +++ b/stackslib/src/chainstate/stacks/db/blocks.rs @@ -4219,7 +4219,7 @@ impl StacksChainState { microblock_header: None, tx_index: 0, vm_error: None, - failed_post_conditions: None, + post_condition_status: None, }; all_receipts.push(receipt); @@ -4315,7 +4315,7 @@ impl StacksChainState { microblock_header: None, tx_index: 0, vm_error: None, - failed_post_conditions: None, + post_condition_status: None, }) } Err(e) => { @@ -4422,7 +4422,7 @@ impl StacksChainState { microblock_header: None, tx_index: 0, vm_error: None, - failed_post_conditions: None, + post_condition_status: None, }; all_receipts.push(receipt); @@ -4531,7 +4531,7 @@ impl StacksChainState { microblock_header: None, tx_index: 0, vm_error: None, - failed_post_conditions: None, + post_condition_status: None, }; all_receipts.push(receipt); diff --git a/stackslib/src/chainstate/stacks/db/transactions.rs b/stackslib/src/chainstate/stacks/db/transactions.rs index f1a3580e3d..8cd5799875 100644 --- a/stackslib/src/chainstate/stacks/db/transactions.rs +++ b/stackslib/src/chainstate/stacks/db/transactions.rs @@ -97,7 +97,7 @@ impl StacksTransactionReceipt { microblock_header: None, tx_index: 0, vm_error: None, - failed_post_conditions: None, + post_condition_status: None, } } @@ -119,7 +119,7 @@ impl StacksTransactionReceipt { microblock_header: None, tx_index: 0, vm_error: None, - failed_post_conditions: None, + post_condition_status: None, } } @@ -129,7 +129,7 @@ impl StacksTransactionReceipt { result: Value, burned: u128, cost: ExecutionCost, - failed_post_conditions: Vec, + post_condition_status: TransactionPostConditionStatus, ) -> StacksTransactionReceipt { StacksTransactionReceipt { transaction: tx.into(), @@ -142,7 +142,7 @@ impl StacksTransactionReceipt { microblock_header: None, tx_index: 0, vm_error: None, - failed_post_conditions: Some(failed_post_conditions), + post_condition_status: Some(post_condition_status), } } @@ -164,7 +164,7 @@ impl StacksTransactionReceipt { microblock_header: None, tx_index: 0, vm_error: None, - failed_post_conditions: None, + post_condition_status: None, } } @@ -174,7 +174,7 @@ impl StacksTransactionReceipt { burned: u128, analysis: ContractAnalysis, cost: ExecutionCost, - failed_post_conditions: Vec, + post_condition_status: TransactionPostConditionStatus, ) -> StacksTransactionReceipt { StacksTransactionReceipt { transaction: tx.into(), @@ -187,7 +187,7 @@ impl StacksTransactionReceipt { microblock_header: None, tx_index: 0, vm_error: None, - failed_post_conditions: Some(failed_post_conditions), + post_condition_status: Some(post_condition_status), } } @@ -203,7 +203,7 @@ impl StacksTransactionReceipt { microblock_header: None, tx_index: 0, vm_error: None, - failed_post_conditions: None, + post_condition_status: None, } } @@ -246,7 +246,7 @@ impl StacksTransactionReceipt { microblock_header: None, tx_index: 0, vm_error: Some(error_string), - failed_post_conditions: None, + post_condition_status: None, } } @@ -266,7 +266,7 @@ impl StacksTransactionReceipt { microblock_header: None, tx_index: 0, vm_error: None, - failed_post_conditions: None, + post_condition_status: None, } } @@ -287,7 +287,7 @@ impl StacksTransactionReceipt { microblock_header: None, tx_index: 0, vm_error: Some(format!("{}", &error)), - failed_post_conditions: None, + post_condition_status: None, } } @@ -307,7 +307,7 @@ impl StacksTransactionReceipt { microblock_header: None, tx_index: 0, vm_error: Some(format!("{}", &error)), - failed_post_conditions: None, + post_condition_status: None, } } @@ -323,7 +323,7 @@ impl StacksTransactionReceipt { microblock_header: None, tx_index: 0, vm_error: None, - failed_post_conditions: None, + post_condition_status: None, } } @@ -591,7 +591,7 @@ impl StacksChainState { post_condition_mode: &TransactionPostConditionMode, origin_account: &StacksAccount, asset_map: &AssetMap, - failed_post_conditions: &mut Vec, + post_condition_statuses: &mut Vec, ) -> Result { let mut checked_fungible_assets: HashMap> = HashMap::new(); @@ -623,7 +623,9 @@ impl StacksChainState { account_principal, amount_sent_condition, condition_code, amount_sent ); - failed_post_conditions.push(postcond.clone()); + post_condition_statuses.push( + TransactionPostConditionStatus::UnmetPostCondition(postcond.clone()), + ); return Ok(false); } @@ -668,7 +670,9 @@ impl StacksChainState { .unwrap_or(0); if !condition_code.check(u128::from(*amount_sent_condition), amount_sent) { info!("Post-condition check failure on fungible asset {} owned by {}: {} {:?} {}", &asset_id, account_principal, amount_sent_condition, condition_code, amount_sent); - failed_post_conditions.push(postcond.clone()); + post_condition_statuses.push( + TransactionPostConditionStatus::UnmetPostCondition(postcond.clone()), + ); return Ok(false); } @@ -703,7 +707,9 @@ impl StacksChainState { .unwrap_or(&empty_assets); if !condition_code.check(asset_value, assets_sent) { info!("Post-condition check failure on non-fungible asset {} owned by {}: {:?} {:?}", &asset_id, account_principal, &asset_value, condition_code); - failed_post_conditions.push(postcond.clone()); + post_condition_statuses.push( + TransactionPostConditionStatus::UnmetPostCondition(postcond.clone()), + ); return Ok(false); } @@ -746,17 +752,30 @@ impl StacksChainState { for v in values { if !nfts.contains(&v.clone().try_into()?) { info!("Post-condition check failure: Non-fungible asset {} value {:?} was moved by {} but not checked", &asset_identifier, &v, &principal); + post_condition_statuses.push(TransactionPostConditionStatus::UncheckedNonFungibleAsset((asset_identifier.clone(), principal, Some(v)))); return Ok(false); } } } else { // no values covered info!("Post-condition check failure: No checks for non-fungible asset type {} moved by {}", &asset_identifier, &principal); + post_condition_statuses.push( + TransactionPostConditionStatus::UncheckedNonFungibleAsset( + (asset_identifier.clone(), principal, None), + ), + ); return Ok(false); } } else { // no NFT for this principal info!("Post-condition check failure: No checks for any non-fungible assets, but moved {} by {}", &asset_identifier, &principal); + post_condition_statuses.push( + TransactionPostConditionStatus::UncheckedNonFungibleAsset(( + asset_identifier.clone(), + principal, + None, + )), + ); return Ok(false); } } @@ -767,10 +786,24 @@ impl StacksChainState { { if !checked_ft_asset_ids.contains(&asset_identifier) { info!("Post-condition check failure: checks did not cover transfer of {} by {}", &asset_identifier, &principal); + post_condition_statuses.push( + TransactionPostConditionStatus::UncheckedFungibleAsset(( + asset_identifier.clone(), + principal, + None, + )), + ); return Ok(false); } } else { info!("Post-condition check failure: No checks for fungible token type {} moved by {}", &asset_identifier, &principal); + post_condition_statuses.push( + TransactionPostConditionStatus::UncheckedFungibleAsset(( + asset_identifier.clone(), + principal, + None, + )), + ); return Ok(false); } } @@ -778,6 +811,7 @@ impl StacksChainState { } } } + post_condition_statuses.push(TransactionPostConditionStatus::Success); return Ok(true); } @@ -1045,7 +1079,8 @@ impl StacksChainState { let cost_before = clarity_tx.cost_so_far(); let sponsor = tx.sponsor_address().map(|a| a.to_account_principal()); let epoch_id = clarity_tx.get_epoch(); - let mut failed_post_conditions = vec![]; + + let mut post_condition_statuses = vec![]; let contract_call_resp = clarity_tx.run_contract_call( &origin_account.principal, @@ -1059,7 +1094,7 @@ impl StacksChainState { &tx.post_condition_mode, origin_account, asset_map, - &mut failed_post_conditions, + &mut post_condition_statuses, ) .expect("FATAL: error while evaluating post-conditions") }, @@ -1096,21 +1131,27 @@ impl StacksChainState { (Value::err_none(), AssetMap::new(), vec![]) } ClarityRuntimeTxError::AbortedByCallback(value, assets, events) => { + let post_condition_status = post_condition_statuses + .first() + .unwrap_or(&TransactionPostConditionStatus::Success) + .clone(); + info!("Contract-call aborted by post-condition"; - "txid" => %tx.txid(), - "origin" => %origin_account.principal, - "origin_nonce" => %origin_account.nonce, - "contract_name" => %contract_id, - "function_name" => %contract_call.function_name, - "function_args" => %VecDisplay(&contract_call.function_args), - "failed_post_conditions" => %VecDisplay(&failed_post_conditions)); + "txid" => %tx.txid(), + "origin" => %origin_account.principal, + "origin_nonce" => %origin_account.nonce, + "contract_name" => %contract_id, + "function_name" => %contract_call.function_name, + "function_args" => %VecDisplay(&contract_call.function_args), + "post_condition_status" => %post_condition_status, + ); let receipt = StacksTransactionReceipt::from_condition_aborted_contract_call( tx.clone(), events, value.expect("BUG: Post condition contract call must provide would-have-been-returned value"), assets.get_stx_burned_total()?, total_cost, - failed_post_conditions); + post_condition_status); return Ok(receipt); } ClarityRuntimeTxError::CostError(cost_after, budget) => { @@ -1213,7 +1254,7 @@ impl StacksChainState { ast_rules, ); - let mut failed_post_conditions = vec![]; + let mut post_condition_statuses = vec![]; let (contract_ast, contract_analysis) = match analysis_resp { Ok(x) => x, @@ -1300,7 +1341,7 @@ impl StacksChainState { &tx.post_condition_mode, origin_account, asset_map, - &mut failed_post_conditions, + &mut post_condition_statuses, ) .expect("FATAL: error while evaluating post-conditions") }, @@ -1341,11 +1382,16 @@ impl StacksChainState { microblock_header: None, tx_index: 0, vm_error: Some(error.to_string()), - failed_post_conditions: None, + post_condition_status: None, }; return Ok(receipt); } ClarityRuntimeTxError::AbortedByCallback(_, assets, events) => { + let post_condition_status = post_condition_statuses + .first() + .unwrap_or(&TransactionPostConditionStatus::Success) + .clone(); + let receipt = StacksTransactionReceipt::from_condition_aborted_smart_contract( tx.clone(), @@ -1353,7 +1399,7 @@ impl StacksChainState { assets.get_stx_burned_total()?, contract_analysis, total_cost, - failed_post_conditions, + post_condition_status, ); return Ok(receipt); } @@ -1643,6 +1689,42 @@ pub mod test { &TestBurnStateDB_2_05 as &dyn BurnStateDB, ]; + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] + pub enum TransactionPostConditionStatusAssert { + Success, + UnmetPostCondition, + UncheckedFungibleAsset, + UncheckedNonFungibleAsset, + } + + impl TransactionPostConditionStatusAssert { + fn validate(&self, status: &TransactionPostConditionStatus) -> bool { + match self { + TransactionPostConditionStatusAssert::Success => { + matches!(status, TransactionPostConditionStatus::Success) + } + TransactionPostConditionStatusAssert::UnmetPostCondition => { + matches!( + status, + TransactionPostConditionStatus::UnmetPostCondition(_) + ) + } + TransactionPostConditionStatusAssert::UncheckedFungibleAsset => { + matches!( + status, + TransactionPostConditionStatus::UncheckedFungibleAsset(_) + ) + } + TransactionPostConditionStatusAssert::UncheckedNonFungibleAsset => { + matches!( + status, + TransactionPostConditionStatus::UncheckedNonFungibleAsset(_) + ) + } + } + } + } + #[test] fn contract_publish_runtime_error() { let contract_id = QualifiedContractIdentifier::local("contract").unwrap(); @@ -5139,6 +5221,7 @@ pub mod test { vec![], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // one post-condition on origin in allow mode ( @@ -5151,6 +5234,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5162,6 +5246,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5173,6 +5258,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5184,6 +5270,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5195,6 +5282,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // two post-conditions on origin in allow mode ( @@ -5215,6 +5303,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5234,6 +5323,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5253,6 +5343,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5272,6 +5363,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5291,6 +5383,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // three post-conditions on origin in allow mode, one with sending 0 tokens ( @@ -5317,6 +5410,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5342,6 +5436,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5367,6 +5462,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5392,6 +5488,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5417,6 +5514,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // four post-conditions on origin in allow mode, one with sending 0 tokens, one with // an unchecked address and a vacuous amount @@ -5450,6 +5548,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5481,6 +5580,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5512,6 +5612,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5543,6 +5644,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5574,6 +5676,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // one post-condition on origin in allow mode, explicit origin ( @@ -5586,6 +5689,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5597,6 +5701,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5608,6 +5713,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5619,6 +5725,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5630,6 +5737,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // two post-conditions on origin in allow mode, explicit origin ( @@ -5650,6 +5758,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5669,6 +5778,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5688,6 +5798,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5707,6 +5818,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5726,6 +5838,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // three post-conditions on origin in allow mode, one with sending 0 tokens, explicit // origin @@ -5753,6 +5866,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5778,6 +5892,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5803,6 +5918,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5828,6 +5944,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5853,6 +5970,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // four post-conditions on origin in allow mode, one with sending 0 tokens, one with // an unchecked address and a vacuous amount, explicit origin @@ -5886,6 +6004,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5917,6 +6036,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5948,6 +6068,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5979,6 +6100,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -6010,6 +6132,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // no-postconditions in deny mode ( @@ -6017,6 +6140,7 @@ pub mod test { vec![], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedFungibleAsset), ), // one post-condition on origin in allow mode ( @@ -6029,6 +6153,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedFungibleAsset), ), ( false, @@ -6040,6 +6165,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedFungibleAsset), ), ( false, @@ -6051,6 +6177,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedFungibleAsset), ), ( false, @@ -6062,6 +6189,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedFungibleAsset), ), ( false, @@ -6073,6 +6201,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedFungibleAsset), ), // two post-conditions on origin in allow mode ( @@ -6093,6 +6222,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6112,6 +6242,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6131,6 +6262,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6150,6 +6282,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6169,6 +6302,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // three post-conditions on origin in allow mode, one with sending 0 tokens ( @@ -6195,6 +6329,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6220,6 +6355,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6245,6 +6381,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6270,6 +6407,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6295,6 +6433,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // four post-conditions on origin in allow mode, one with sending 0 tokens, one with // an unchecked address and a vacuous amount @@ -6328,6 +6467,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6359,6 +6499,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6390,6 +6531,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6421,6 +6563,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6452,6 +6595,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // one post-condition on origin in allow mode, explicit origin ( @@ -6464,6 +6608,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedFungibleAsset), ), ( false, @@ -6475,6 +6620,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedFungibleAsset), ), ( false, @@ -6486,6 +6632,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedFungibleAsset), ), ( false, @@ -6497,6 +6644,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedFungibleAsset), ), ( false, @@ -6508,6 +6656,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedFungibleAsset), ), // two post-conditions on origin in allow mode, explicit origin ( @@ -6528,6 +6677,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6547,6 +6697,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6566,6 +6717,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6585,6 +6737,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6604,6 +6757,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // three post-conditions on origin in allow mode, one with sending 0 tokens, explicit // origin @@ -6631,6 +6785,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6656,6 +6811,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6681,6 +6837,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6706,6 +6863,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6731,6 +6889,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // four post-conditions on origin in allow mode, one with sending 0 tokens, one with // an unchecked address and a vacuous amount, explicit origin @@ -6764,6 +6923,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6795,6 +6955,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6826,6 +6987,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6857,6 +7019,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6888,25 +7051,48 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ]; for test in tests { - let expected_result = test.0; - let post_conditions = &test.1; - let mode = &test.2; - let origin = &test.3; - let mut failed_post_conditions = vec![]; + let (expected_result, post_conditions, mode, origin, post_condition_status_assert) = + &test; + + let mut post_condition_statuses = vec![]; let result = StacksChainState::check_transaction_postconditions( post_conditions, mode, origin, &ft_transfer_2, - &mut failed_post_conditions, // TODO: validate failed post-conditions + &mut post_condition_statuses, ) .unwrap(); - if result != expected_result { + + let post_condition_status = post_condition_statuses.first(); + + match post_condition_status_assert { + None => { + assert!( + post_condition_status.is_some_and(|post_condition| post_condition.eq(&TransactionPostConditionStatus::Success)), + "transaction contains failed post conditions but test did not specify any expectations.\nasset map: {:?}\nscenario: {:?}\nmatcher: {:?}\nstatus: {:?}", + ft_transfer_2, &test, post_condition_status_assert, post_condition_status, + ); + } + Some(post_condition_status_assert) => { + let post_condition_status = + post_condition_status.unwrap_or(&TransactionPostConditionStatus::Success); + + assert!( + post_condition_status_assert.validate(post_condition_status), + "transaction contains failed post conditions but did not match expectations.\nasset map: {:?}\nscenario: {:?}\nmatcher: {:?}\nstatus: {:?}", + ft_transfer_2, &test, post_condition_status_assert, post_condition_status, + ); + } + } + + if result != expected_result.clone() { eprintln!( "test failed:\nasset map: {:?}\nscenario: {:?}\n", &ft_transfer_2, &test @@ -6960,6 +7146,7 @@ pub mod test { vec![], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // one post-condition on origin in allow mode ( @@ -6972,6 +7159,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -6983,6 +7171,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // two post-conditions on origin in allow mode ( @@ -7003,6 +7192,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // post-condition on a non-sent asset ( @@ -7029,6 +7219,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // one post-condition on origin in allow mode, explicit origin ( @@ -7041,6 +7232,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -7052,6 +7244,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // two post-conditions on origin in allow mode, explicit origin ( @@ -7072,6 +7265,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // post-condition on a non-sent asset, explicit origin ( @@ -7098,6 +7292,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // no post-conditions in deny mode ( @@ -7105,6 +7300,7 @@ pub mod test { vec![], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedNonFungibleAsset), ), // one post-condition on origin in deny mode ( @@ -7117,6 +7313,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedNonFungibleAsset), ), ( false, @@ -7128,6 +7325,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedNonFungibleAsset), ), // two post-conditions on origin in allow mode ( @@ -7148,6 +7346,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // post-condition on a non-sent asset ( @@ -7174,6 +7373,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // one post-condition on origin in deny mode, explicit origin ( @@ -7186,6 +7386,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedNonFungibleAsset), ), ( false, @@ -7197,6 +7398,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedNonFungibleAsset), ), // two post-conditions on origin in allow mode, explicit origin ( @@ -7217,6 +7419,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // post-condition on a non-sent asset, explicit origin ( @@ -7243,25 +7446,48 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ]; for test in tests.iter() { - let expected_result = test.0; - let post_conditions = &test.1; - let mode = &test.2; - let origin = &test.3; - let mut failed_post_conditions = vec![]; + let (expected_result, post_conditions, mode, origin, post_condition_status_assert) = + &test; + + let mut post_condition_statuses = vec![]; let result = StacksChainState::check_transaction_postconditions( post_conditions, mode, origin, &nft_transfer_2, - &mut failed_post_conditions, // TODO: check failed post-conditions + &mut post_condition_statuses, ) .unwrap(); - if result != expected_result { + + let post_condition_status = post_condition_statuses.first(); + + match post_condition_status_assert { + None => { + assert!( + post_condition_status.is_some_and(|post_condition| post_condition.eq(&TransactionPostConditionStatus::Success)), + "transaction contains failed post conditions but test did not specify any expectations.\nasset map: {:?}\nscenario: {:?}\nmatcher: {:?}\nstatus: {:?}", + nft_transfer_2, &test, post_condition_status_assert, post_condition_status, + ); + } + Some(post_condition_status_assert) => { + let post_condition_status = + post_condition_status.unwrap_or(&TransactionPostConditionStatus::Success); + + assert!( + post_condition_status_assert.validate(post_condition_status), + "transaction contains failed post conditions but did not match expectations.\nasset map: {:?}\nscenario: {:?}\nmatcher: {:?}\nstatus: {:?}", + nft_transfer_2, &test, post_condition_status_assert, post_condition_status, + ); + } + } + + if result != expected_result.clone() { eprintln!( "test failed:\nasset map: {:?}\nscenario: {:?}\n", &nft_transfer_2, &test @@ -7309,6 +7535,7 @@ pub mod test { vec![], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should pass // post-conditions on origin in allow mode ( @@ -7320,6 +7547,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7330,6 +7558,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7340,6 +7569,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7350,6 +7580,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7360,6 +7591,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should pass // post-conditions with an explicitly-set address in allow mode ( @@ -7371,6 +7603,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7381,6 +7614,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7391,6 +7625,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7401,6 +7636,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7411,6 +7647,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should pass // post-conditions with an unrelated contract address in allow mode ( @@ -7425,6 +7662,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7438,6 +7676,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7451,6 +7690,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7464,6 +7704,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should pass // post-conditions with both the origin and an unrelated contract address in allow mode ( @@ -7485,6 +7726,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7505,6 +7747,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7525,6 +7768,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7545,6 +7789,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should pass // post-conditions that fail since the amount is wrong ( @@ -7556,6 +7801,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UnmetPostCondition), ), // should fail ( false, @@ -7566,6 +7812,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UnmetPostCondition), ), // should fail ( false, @@ -7576,6 +7823,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UnmetPostCondition), ), // should fail ( false, @@ -7586,6 +7834,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UnmetPostCondition), ), // should fail ( false, @@ -7596,6 +7845,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UnmetPostCondition), ), // should fail // no post-conditions in deny mode (should fail) ( @@ -7603,6 +7853,7 @@ pub mod test { vec![], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedFungibleAsset), ), // should fail // post-conditions on origin in deny mode (should all pass since origin is specified ( @@ -7614,6 +7865,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7624,6 +7876,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7634,6 +7887,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7644,6 +7898,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7654,6 +7909,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // should pass // post-conditions with an explicitly-set address in deny mode (should all pass since // address matches the address in the asset map) @@ -7666,6 +7922,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7676,6 +7933,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7686,6 +7944,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7696,6 +7955,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7706,6 +7966,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // should pass // post-conditions with an unrelated contract address in allow mode, with check on // origin (should all pass) @@ -7728,6 +7989,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should fail ( true, @@ -7748,6 +8010,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should fail ( true, @@ -7768,6 +8031,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should fail ( true, @@ -7788,6 +8052,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should fail // post-conditions with an unrelated contract address in deny mode (should all fail // since stx-transfer isn't covered) @@ -7803,6 +8068,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedFungibleAsset), ), // should fail ( false, @@ -7816,6 +8082,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedFungibleAsset), ), // should fail ( false, @@ -7829,6 +8096,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedFungibleAsset), ), // should fail ( false, @@ -7842,6 +8110,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedFungibleAsset), ), // should fail // post-conditions with an unrelated contract address in deny mode, with check on // origin (should all pass) @@ -7864,6 +8133,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // should fail ( true, @@ -7884,6 +8154,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // should fail ( true, @@ -7904,6 +8175,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // should fail ( true, @@ -7924,6 +8196,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // should fail // post-conditions with both the origin and an unrelated contract address in deny mode (should all pass) ( @@ -7945,6 +8218,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7965,6 +8239,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7985,6 +8260,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -8005,6 +8281,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // should pass // post-conditions that fail since the amount is wrong, even though all principals are // covered @@ -8017,6 +8294,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UnmetPostCondition), ), // should fail ( false, @@ -8027,6 +8305,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UnmetPostCondition), ), // should fail ( false, @@ -8037,6 +8316,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UnmetPostCondition), ), // should fail ( false, @@ -8047,6 +8327,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UnmetPostCondition), ), // should fail ( false, @@ -8057,6 +8338,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UnmetPostCondition), ), // should fail ]; @@ -8066,22 +8348,48 @@ pub mod test { &stx_transfer_burn_asset_map, ] { for test in tests.iter() { - let expected_result = test.0; - let post_conditions = &test.1; - let post_condition_mode = &test.2; - let origin_account = &test.3; + let ( + expected_result, + post_conditions, + post_condition_mode, + origin_account, + post_condition_status_assert, + ) = &test; - let mut failed_post_conditions = vec![]; + let mut post_condition_statuses = vec![]; let result = StacksChainState::check_transaction_postconditions( post_conditions, post_condition_mode, origin_account, asset_map, - &mut failed_post_conditions, // TODO: check failed post-conditions + &mut post_condition_statuses, ) .unwrap(); - if result != expected_result { + + let post_condition_status = post_condition_statuses.first(); + + match post_condition_status_assert { + None => { + assert!( + post_condition_status.is_some_and(|post_condition| post_condition.eq(&TransactionPostConditionStatus::Success)), + "transaction contains failed post conditions but test did not specify any expectations.\nasset map: {:?}\nscenario: {:?}\nmatcher: {:?}\nstatus: {:?}", + asset_map, &test, post_condition_status_assert, post_condition_status, + ); + } + Some(post_condition_status_assert) => { + let post_condition_status = post_condition_status + .unwrap_or(&TransactionPostConditionStatus::Success); + + assert!( + post_condition_status_assert.validate(post_condition_status), + "transaction contains failed post conditions but did not match expectations.\nasset map: {:?}\nscenario: {:?}\nmatcher: {:?}\nstatus: {:?}", + asset_map, &test, post_condition_status_assert, post_condition_status, + ); + } + } + + if result != expected_result.clone() { eprintln!( "test failed:\nasset map: {:?}\nscenario: {:?}\n", asset_map, &test diff --git a/stackslib/src/chainstate/stacks/events.rs b/stackslib/src/chainstate/stacks/events.rs index f6fb7108d0..5b14f62a66 100644 --- a/stackslib/src/chainstate/stacks/events.rs +++ b/stackslib/src/chainstate/stacks/events.rs @@ -9,6 +9,7 @@ use stacks_common::codec::StacksMessageCodec; use stacks_common::types::chainstate::{BlockHeaderHash, StacksAddress}; use stacks_common::util::hash::to_hex; +use super::TransactionPostConditionStatus; use crate::burnchains::Txid; use crate::chainstate::burn::operations::BlockstackOperationType; use crate::chainstate::nakamoto::NakamotoBlock; @@ -56,7 +57,7 @@ pub struct StacksTransactionReceipt { pub tx_index: u32, /// This is really a string-formatted CheckError (which can't be clone()'ed) pub vm_error: Option, - pub failed_post_conditions: Option>, + pub post_condition_status: Option, } #[derive(Clone)] diff --git a/stackslib/src/chainstate/stacks/mod.rs b/stackslib/src/chainstate/stacks/mod.rs index 47faca681e..36e16fbed1 100644 --- a/stackslib/src/chainstate/stacks/mod.rs +++ b/stackslib/src/chainstate/stacks/mod.rs @@ -24,7 +24,7 @@ use clarity::vm::costs::{CostErrors, ExecutionCost}; use clarity::vm::errors::Error as clarity_interpreter_error; use clarity::vm::representations::{ClarityName, ContractName}; use clarity::vm::types::{ - PrincipalData, QualifiedContractIdentifier, StandardPrincipalData, Value, + AssetIdentifier, PrincipalData, QualifiedContractIdentifier, StandardPrincipalData, Value, }; use clarity::vm::ClarityVersion; use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef}; @@ -1059,6 +1059,31 @@ impl fmt::Display for TransactionPostCondition { } } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum TransactionPostConditionStatus { + Success, + UnmetPostCondition(TransactionPostCondition), + UncheckedFungibleAsset((AssetIdentifier, PrincipalData, Option)), + UncheckedNonFungibleAsset((AssetIdentifier, PrincipalData, Option)), +} + +impl fmt::Display for TransactionPostConditionStatus { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + TransactionPostConditionStatus::Success => write!(f, "Success"), + TransactionPostConditionStatus::UnmetPostCondition(condition) => { + write!(f, "Unmet post condition: {:?}", condition) + } + TransactionPostConditionStatus::UncheckedFungibleAsset(asset) => { + write!(f, "Unchecked fungible asset: {:?}", asset) + } + TransactionPostConditionStatus::UncheckedNonFungibleAsset(asset) => { + write!(f, "Unchecked non-fungible asset: {:?}", asset) + } + } + } +} + /// Post-condition modes for unspecified assets #[repr(u8)] #[derive(Debug, Clone, PartialEq, Copy, Serialize, Deserialize)] From 8323f0d46db622780c673788efa756fd25aab29b Mon Sep 17 00:00:00 2001 From: Luis Aguilar Date: Thu, 30 May 2024 17:15:41 -0400 Subject: [PATCH 3/4] chore(post-conditions): add post-condition status to event dispatcher payload --- testnet/stacks-node/src/event_dispatcher.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/testnet/stacks-node/src/event_dispatcher.rs b/testnet/stacks-node/src/event_dispatcher.rs index 9badfda567..db2925119c 100644 --- a/testnet/stacks-node/src/event_dispatcher.rs +++ b/testnet/stacks-node/src/event_dispatcher.rs @@ -61,6 +61,7 @@ struct ReceiptPayloadInfo<'a> { raw_tx: String, contract_interface_json: serde_json::Value, burnchain_op_json: serde_json::Value, + post_condition_status_json: serde_json::Value, } const STATUS_RESP_TRUE: &str = "success"; @@ -444,6 +445,7 @@ impl EventObserver { .expect("FATAL: failed to serialize transaction receipt"); bytes_to_hex(&bytes) }; + let contract_interface_json = { match &receipt.contract_analysis { Some(analysis) => json!(build_contract_interface(analysis) @@ -451,6 +453,14 @@ impl EventObserver { None => json!(null), } }; + + let post_condition_status_json = { + match &receipt.post_condition_status { + Some(status) => json!(status), + None => json!(null), + } + }; + ReceiptPayloadInfo { txid, success, @@ -458,6 +468,7 @@ impl EventObserver { raw_tx, contract_interface_json, burnchain_op_json, + post_condition_status_json, } } @@ -476,6 +487,7 @@ impl EventObserver { "raw_tx": format!("0x{}", &receipt_payload_info.raw_tx), "contract_abi": receipt_payload_info.contract_interface_json, "burnchain_op": receipt_payload_info.burnchain_op_json, + "post_condition_status": receipt_payload_info.post_condition_status_json, "execution_cost": receipt.execution_cost, "microblock_sequence": receipt.microblock_header.as_ref().map(|x| x.sequence), "microblock_hash": receipt.microblock_header.as_ref().map(|x| format!("0x{}", x.block_hash())), From be118f7092c8e298bc350052142f553f3d78ad08 Mon Sep 17 00:00:00 2001 From: Luis Aguilar Date: Thu, 27 Jun 2024 10:02:20 -0600 Subject: [PATCH 4/4] chore: add more data to failed post-conditions --- .../src/chainstate/stacks/db/transactions.rs | 20 +++++++++++++++---- stackslib/src/chainstate/stacks/mod.rs | 10 +++++++--- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/stackslib/src/chainstate/stacks/db/transactions.rs b/stackslib/src/chainstate/stacks/db/transactions.rs index 8cd5799875..3ac08bd264 100644 --- a/stackslib/src/chainstate/stacks/db/transactions.rs +++ b/stackslib/src/chainstate/stacks/db/transactions.rs @@ -624,7 +624,10 @@ impl StacksChainState { ); post_condition_statuses.push( - TransactionPostConditionStatus::UnmetPostCondition(postcond.clone()), + TransactionPostConditionStatus::FailedFungibleAssetPostCondition(( + postcond.clone(), + amount_sent, + )), ); return Ok(false); @@ -671,7 +674,10 @@ impl StacksChainState { if !condition_code.check(u128::from(*amount_sent_condition), amount_sent) { info!("Post-condition check failure on fungible asset {} owned by {}: {} {:?} {}", &asset_id, account_principal, amount_sent_condition, condition_code, amount_sent); post_condition_statuses.push( - TransactionPostConditionStatus::UnmetPostCondition(postcond.clone()), + TransactionPostConditionStatus::FailedFungibleAssetPostCondition(( + postcond.clone(), + amount_sent, + )), ); return Ok(false); } @@ -708,7 +714,10 @@ impl StacksChainState { if !condition_code.check(asset_value, assets_sent) { info!("Post-condition check failure on non-fungible asset {} owned by {}: {:?} {:?}", &asset_id, account_principal, &asset_value, condition_code); post_condition_statuses.push( - TransactionPostConditionStatus::UnmetPostCondition(postcond.clone()), + TransactionPostConditionStatus::FailedNonFungibleAssetPostCondition(( + postcond.clone(), + asset_value.clone(), + )), ); return Ok(false); } @@ -1706,7 +1715,10 @@ pub mod test { TransactionPostConditionStatusAssert::UnmetPostCondition => { matches!( status, - TransactionPostConditionStatus::UnmetPostCondition(_) + TransactionPostConditionStatus::FailedFungibleAssetPostCondition(_) + ) | matches!( + status, + TransactionPostConditionStatus::FailedNonFungibleAssetPostCondition(_) ) } TransactionPostConditionStatusAssert::UncheckedFungibleAsset => { diff --git a/stackslib/src/chainstate/stacks/mod.rs b/stackslib/src/chainstate/stacks/mod.rs index 36e16fbed1..bde322aa09 100644 --- a/stackslib/src/chainstate/stacks/mod.rs +++ b/stackslib/src/chainstate/stacks/mod.rs @@ -1062,7 +1062,8 @@ impl fmt::Display for TransactionPostCondition { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum TransactionPostConditionStatus { Success, - UnmetPostCondition(TransactionPostCondition), + FailedFungibleAssetPostCondition((TransactionPostCondition, u128)), + FailedNonFungibleAssetPostCondition((TransactionPostCondition, Value)), UncheckedFungibleAsset((AssetIdentifier, PrincipalData, Option)), UncheckedNonFungibleAsset((AssetIdentifier, PrincipalData, Option)), } @@ -1071,8 +1072,11 @@ impl fmt::Display for TransactionPostConditionStatus { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { TransactionPostConditionStatus::Success => write!(f, "Success"), - TransactionPostConditionStatus::UnmetPostCondition(condition) => { - write!(f, "Unmet post condition: {:?}", condition) + TransactionPostConditionStatus::FailedFungibleAssetPostCondition(data) => { + write!(f, "Failed fungible asset post condition: {:?}", data) + } + TransactionPostConditionStatus::FailedNonFungibleAssetPostCondition(data) => { + write!(f, "Failed non-fungible asset post condition: {:?}", data) } TransactionPostConditionStatus::UncheckedFungibleAsset(asset) => { write!(f, "Unchecked fungible asset: {:?}", asset)