diff --git a/stackslib/src/chainstate/stacks/db/blocks.rs b/stackslib/src/chainstate/stacks/db/blocks.rs index 971351b1d5..b87ee7c33d 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, + post_condition_status: None, }; all_receipts.push(receipt); @@ -4314,6 +4315,7 @@ impl StacksChainState { microblock_header: None, tx_index: 0, vm_error: None, + post_condition_status: None, }) } Err(e) => { @@ -4420,6 +4422,7 @@ impl StacksChainState { microblock_header: None, tx_index: 0, vm_error: None, + post_condition_status: None, }; all_receipts.push(receipt); @@ -4528,6 +4531,7 @@ impl StacksChainState { microblock_header: None, tx_index: 0, vm_error: 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 35ba532667..3ac08bd264 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, + post_condition_status: None, } } @@ -118,6 +119,7 @@ impl StacksTransactionReceipt { microblock_header: None, tx_index: 0, vm_error: None, + post_condition_status: None, } } @@ -127,6 +129,7 @@ impl StacksTransactionReceipt { result: Value, burned: u128, cost: ExecutionCost, + post_condition_status: TransactionPostConditionStatus, ) -> StacksTransactionReceipt { StacksTransactionReceipt { transaction: tx.into(), @@ -139,6 +142,7 @@ impl StacksTransactionReceipt { microblock_header: None, tx_index: 0, vm_error: None, + post_condition_status: Some(post_condition_status), } } @@ -160,6 +164,7 @@ impl StacksTransactionReceipt { microblock_header: None, tx_index: 0, vm_error: None, + post_condition_status: None, } } @@ -169,6 +174,7 @@ impl StacksTransactionReceipt { burned: u128, analysis: ContractAnalysis, cost: ExecutionCost, + post_condition_status: TransactionPostConditionStatus, ) -> StacksTransactionReceipt { StacksTransactionReceipt { transaction: tx.into(), @@ -181,6 +187,7 @@ impl StacksTransactionReceipt { microblock_header: None, tx_index: 0, vm_error: None, + post_condition_status: Some(post_condition_status), } } @@ -196,6 +203,7 @@ impl StacksTransactionReceipt { microblock_header: None, tx_index: 0, vm_error: None, + post_condition_status: None, } } @@ -238,6 +246,7 @@ impl StacksTransactionReceipt { microblock_header: None, tx_index: 0, vm_error: Some(error_string), + post_condition_status: None, } } @@ -257,6 +266,7 @@ impl StacksTransactionReceipt { microblock_header: None, tx_index: 0, vm_error: None, + post_condition_status: None, } } @@ -277,6 +287,7 @@ impl StacksTransactionReceipt { microblock_header: None, tx_index: 0, vm_error: Some(format!("{}", &error)), + post_condition_status: None, } } @@ -296,6 +307,7 @@ impl StacksTransactionReceipt { microblock_header: None, tx_index: 0, vm_error: Some(format!("{}", &error)), + post_condition_status: None, } } @@ -311,6 +323,7 @@ impl StacksTransactionReceipt { microblock_header: None, tx_index: 0, vm_error: None, + post_condition_status: None, } } @@ -578,6 +591,7 @@ impl StacksChainState { post_condition_mode: &TransactionPostConditionMode, origin_account: &StacksAccount, asset_map: &AssetMap, + post_condition_statuses: &mut Vec, ) -> Result { let mut checked_fungible_assets: HashMap> = HashMap::new(); @@ -608,6 +622,14 @@ impl StacksChainState { "Post-condition check failure on STX owned by {}: {:?} {:?} {}", account_principal, amount_sent_condition, condition_code, amount_sent ); + + post_condition_statuses.push( + TransactionPostConditionStatus::FailedFungibleAssetPostCondition(( + postcond.clone(), + amount_sent, + )), + ); + return Ok(false); } @@ -651,6 +673,12 @@ 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); + post_condition_statuses.push( + TransactionPostConditionStatus::FailedFungibleAssetPostCondition(( + postcond.clone(), + amount_sent, + )), + ); return Ok(false); } @@ -685,6 +713,12 @@ 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); + post_condition_statuses.push( + TransactionPostConditionStatus::FailedNonFungibleAssetPostCondition(( + postcond.clone(), + asset_value.clone(), + )), + ); return Ok(false); } @@ -727,17 +761,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); } } @@ -748,10 +795,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); } } @@ -759,6 +820,7 @@ impl StacksChainState { } } } + post_condition_statuses.push(TransactionPostConditionStatus::Success); return Ok(true); } @@ -1027,6 +1089,8 @@ impl StacksChainState { let sponsor = tx.sponsor_address().map(|a| a.to_account_principal()); let epoch_id = clarity_tx.get_epoch(); + let mut post_condition_statuses = vec![]; + let contract_call_resp = clarity_tx.run_contract_call( &origin_account.principal, sponsor.as_ref(), @@ -1039,6 +1103,7 @@ impl StacksChainState { &tx.post_condition_mode, origin_account, asset_map, + &mut post_condition_statuses, ) .expect("FATAL: error while evaluating post-conditions") }, @@ -1075,19 +1140,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)); + "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); + total_cost, + post_condition_status); return Ok(receipt); } ClarityRuntimeTxError::CostError(cost_after, budget) => { @@ -1189,6 +1262,9 @@ impl StacksChainState { &contract_code_str, ast_rules, ); + + let mut post_condition_statuses = vec![]; + let (contract_ast, contract_analysis) = match analysis_resp { Ok(x) => x, Err(e) => { @@ -1274,6 +1350,7 @@ impl StacksChainState { &tx.post_condition_mode, origin_account, asset_map, + &mut post_condition_statuses, ) .expect("FATAL: error while evaluating post-conditions") }, @@ -1314,10 +1391,16 @@ impl StacksChainState { microblock_header: None, tx_index: 0, vm_error: Some(error.to_string()), + 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(), @@ -1325,6 +1408,7 @@ impl StacksChainState { assets.get_stx_burned_total()?, contract_analysis, total_cost, + post_condition_status, ); return Ok(receipt); } @@ -1614,6 +1698,45 @@ 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::FailedFungibleAssetPostCondition(_) + ) | matches!( + status, + TransactionPostConditionStatus::FailedNonFungibleAssetPostCondition(_) + ) + } + 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(); @@ -5110,6 +5233,7 @@ pub mod test { vec![], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // one post-condition on origin in allow mode ( @@ -5122,6 +5246,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5133,6 +5258,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5144,6 +5270,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5155,6 +5282,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5166,6 +5294,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // two post-conditions on origin in allow mode ( @@ -5186,6 +5315,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5205,6 +5335,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5224,6 +5355,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5243,6 +5375,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5262,6 +5395,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 ( @@ -5288,6 +5422,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5313,6 +5448,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5338,6 +5474,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5363,6 +5500,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5388,6 +5526,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 @@ -5421,6 +5560,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5452,6 +5592,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5483,6 +5624,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5514,6 +5656,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5545,6 +5688,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // one post-condition on origin in allow mode, explicit origin ( @@ -5557,6 +5701,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5568,6 +5713,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5579,6 +5725,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5590,6 +5737,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5601,6 +5749,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // two post-conditions on origin in allow mode, explicit origin ( @@ -5621,6 +5770,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5640,6 +5790,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5659,6 +5810,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5678,6 +5830,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5697,6 +5850,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 @@ -5724,6 +5878,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5749,6 +5904,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5774,6 +5930,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5799,6 +5956,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5824,6 +5982,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 @@ -5857,6 +6016,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5888,6 +6048,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5919,6 +6080,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5950,6 +6112,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -5981,6 +6144,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // no-postconditions in deny mode ( @@ -5988,6 +6152,7 @@ pub mod test { vec![], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedFungibleAsset), ), // one post-condition on origin in allow mode ( @@ -6000,6 +6165,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedFungibleAsset), ), ( false, @@ -6011,6 +6177,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedFungibleAsset), ), ( false, @@ -6022,6 +6189,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedFungibleAsset), ), ( false, @@ -6033,6 +6201,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedFungibleAsset), ), ( false, @@ -6044,6 +6213,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedFungibleAsset), ), // two post-conditions on origin in allow mode ( @@ -6064,6 +6234,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6083,6 +6254,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6102,6 +6274,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6121,6 +6294,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6140,6 +6314,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 ( @@ -6166,6 +6341,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6191,6 +6367,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6216,6 +6393,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6241,6 +6419,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6266,6 +6445,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 @@ -6299,6 +6479,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6330,6 +6511,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6361,6 +6543,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6392,6 +6575,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6423,6 +6607,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // one post-condition on origin in allow mode, explicit origin ( @@ -6435,6 +6620,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedFungibleAsset), ), ( false, @@ -6446,6 +6632,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedFungibleAsset), ), ( false, @@ -6457,6 +6644,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedFungibleAsset), ), ( false, @@ -6468,6 +6656,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedFungibleAsset), ), ( false, @@ -6479,6 +6668,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedFungibleAsset), ), // two post-conditions on origin in allow mode, explicit origin ( @@ -6499,6 +6689,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6518,6 +6709,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6537,6 +6729,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6556,6 +6749,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6575,6 +6769,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 @@ -6602,6 +6797,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6627,6 +6823,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6652,6 +6849,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6677,6 +6875,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6702,6 +6901,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 @@ -6735,6 +6935,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6766,6 +6967,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6797,6 +6999,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6828,6 +7031,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), ( true, @@ -6859,23 +7063,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 (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 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 @@ -6929,6 +7158,7 @@ pub mod test { vec![], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // one post-condition on origin in allow mode ( @@ -6941,6 +7171,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -6952,6 +7183,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // two post-conditions on origin in allow mode ( @@ -6972,6 +7204,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // post-condition on a non-sent asset ( @@ -6998,6 +7231,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // one post-condition on origin in allow mode, explicit origin ( @@ -7010,6 +7244,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), ( true, @@ -7021,6 +7256,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // two post-conditions on origin in allow mode, explicit origin ( @@ -7041,6 +7277,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // post-condition on a non-sent asset, explicit origin ( @@ -7067,6 +7304,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // no post-conditions in deny mode ( @@ -7074,6 +7312,7 @@ pub mod test { vec![], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedNonFungibleAsset), ), // one post-condition on origin in deny mode ( @@ -7086,6 +7325,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedNonFungibleAsset), ), ( false, @@ -7097,6 +7337,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedNonFungibleAsset), ), // two post-conditions on origin in allow mode ( @@ -7117,6 +7358,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // post-condition on a non-sent asset ( @@ -7143,6 +7385,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // one post-condition on origin in deny mode, explicit origin ( @@ -7155,6 +7398,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedNonFungibleAsset), ), ( false, @@ -7166,6 +7410,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedNonFungibleAsset), ), // two post-conditions on origin in allow mode, explicit origin ( @@ -7186,6 +7431,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // post-condition on a non-sent asset, explicit origin ( @@ -7212,23 +7458,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 (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 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 @@ -7276,6 +7547,7 @@ pub mod test { vec![], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should pass // post-conditions on origin in allow mode ( @@ -7287,6 +7559,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7297,6 +7570,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7307,6 +7581,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7317,6 +7592,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7327,6 +7603,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should pass // post-conditions with an explicitly-set address in allow mode ( @@ -7338,6 +7615,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7348,6 +7626,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7358,6 +7637,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7368,6 +7648,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7378,6 +7659,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should pass // post-conditions with an unrelated contract address in allow mode ( @@ -7392,6 +7674,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7405,6 +7688,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7418,6 +7702,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7431,6 +7716,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 ( @@ -7452,6 +7738,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7472,6 +7759,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7492,6 +7780,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7512,6 +7801,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should pass // post-conditions that fail since the amount is wrong ( @@ -7523,6 +7813,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UnmetPostCondition), ), // should fail ( false, @@ -7533,6 +7824,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UnmetPostCondition), ), // should fail ( false, @@ -7543,6 +7835,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UnmetPostCondition), ), // should fail ( false, @@ -7553,6 +7846,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UnmetPostCondition), ), // should fail ( false, @@ -7563,6 +7857,7 @@ pub mod test { )], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UnmetPostCondition), ), // should fail // no post-conditions in deny mode (should fail) ( @@ -7570,6 +7865,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 ( @@ -7581,6 +7877,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7591,6 +7888,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7601,6 +7899,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7611,6 +7910,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7621,6 +7921,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) @@ -7633,6 +7934,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7643,6 +7945,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7653,6 +7956,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7663,6 +7967,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7673,6 +7978,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) @@ -7695,6 +8001,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should fail ( true, @@ -7715,6 +8022,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should fail ( true, @@ -7735,6 +8043,7 @@ pub mod test { ], TransactionPostConditionMode::Allow, make_account(&origin, 1, 123), + None, ), // should fail ( true, @@ -7755,6 +8064,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) @@ -7770,6 +8080,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedFungibleAsset), ), // should fail ( false, @@ -7783,6 +8094,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedFungibleAsset), ), // should fail ( false, @@ -7796,6 +8108,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UncheckedFungibleAsset), ), // should fail ( false, @@ -7809,6 +8122,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) @@ -7831,6 +8145,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // should fail ( true, @@ -7851,6 +8166,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // should fail ( true, @@ -7871,6 +8187,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // should fail ( true, @@ -7891,6 +8208,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) ( @@ -7912,6 +8230,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7932,6 +8251,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7952,6 +8272,7 @@ pub mod test { ], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + None, ), // should pass ( true, @@ -7972,6 +8293,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 @@ -7984,6 +8306,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UnmetPostCondition), ), // should fail ( false, @@ -7994,6 +8317,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UnmetPostCondition), ), // should fail ( false, @@ -8004,6 +8328,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UnmetPostCondition), ), // should fail ( false, @@ -8014,6 +8339,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UnmetPostCondition), ), // should fail ( false, @@ -8024,6 +8350,7 @@ pub mod test { )], TransactionPostConditionMode::Deny, make_account(&origin, 1, 123), + Some(TransactionPostConditionStatusAssert::UnmetPostCondition), ), // should fail ]; @@ -8033,19 +8360,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 post_condition_statuses = vec![]; let result = StacksChainState::check_transaction_postconditions( post_conditions, post_condition_mode, origin_account, asset_map, + &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 a744d126b4..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,6 +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 post_condition_status: Option, } #[derive(Clone)] diff --git a/stackslib/src/chainstate/stacks/mod.rs b/stackslib/src/chainstate/stacks/mod.rs index 92da8ac283..bde322aa09 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}; @@ -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,59 @@ 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 + ) + } + } + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum TransactionPostConditionStatus { + Success, + FailedFungibleAssetPostCondition((TransactionPostCondition, u128)), + FailedNonFungibleAssetPostCondition((TransactionPostCondition, Value)), + 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::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) + } + 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)] 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())),