drive_abci/abci/app/
execution_result.rs

1use crate::abci::handler::error::consensus::AbciResponseInfoGetter;
2use crate::abci::handler::error::HandlerError;
3use crate::error::Error;
4use crate::platform_types::state_transitions_processing_result::StateTransitionExecutionResult;
5use dpp::fee::SignedCredits;
6use dpp::version::PlatformVersion;
7use dpp::version::TryIntoPlatformVersioned;
8use tenderdash_abci::proto::abci::ExecTxResult;
9
10impl TryIntoPlatformVersioned<Option<ExecTxResult>> for StateTransitionExecutionResult {
11    type Error = Error;
12
13    fn try_into_platform_versioned(
14        self,
15        platform_version: &PlatformVersion,
16    ) -> Result<Option<ExecTxResult>, Self::Error> {
17        let response = match self {
18            StateTransitionExecutionResult::SuccessfulExecution {
19                fee_result: actual_fees,
20                ..
21            } => Some(ExecTxResult {
22                code: 0,
23                gas_used: actual_fees.total_base_fee() as SignedCredits,
24                ..Default::default()
25            }),
26            StateTransitionExecutionResult::UnpaidConsensusError(error) => Some(ExecTxResult {
27                code: HandlerError::from(&error).code(),
28                info: error.response_info_for_version(platform_version)?,
29                gas_used: 0,
30                ..Default::default()
31            }),
32            StateTransitionExecutionResult::PaidConsensusError { error, actual_fees } => {
33                Some(ExecTxResult {
34                    code: HandlerError::from(&error).code(),
35                    info: error.response_info_for_version(platform_version)?,
36                    gas_used: actual_fees.total_base_fee() as SignedCredits,
37                    ..Default::default()
38                })
39            }
40            StateTransitionExecutionResult::InternalError(message) => Some(ExecTxResult {
41                code: HandlerError::Internal(message).code(),
42                // TODO: That would be nice to provide more information about the error for debugging
43                info: String::default(),
44                ..Default::default()
45            }),
46            StateTransitionExecutionResult::NotExecuted(_) => None,
47        };
48
49        Ok(response)
50    }
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56    use crate::platform_types::state_transitions_processing_result::NotExecutedReason;
57    use dpp::consensus::ConsensusError;
58    use dpp::fee::fee_result::FeeResult;
59    use std::collections::BTreeMap;
60
61    #[test]
62    fn successful_execution_produces_result_with_zero_code() {
63        let platform_version = PlatformVersion::latest();
64
65        let fee_result = FeeResult {
66            storage_fee: 100,
67            processing_fee: 50,
68            ..Default::default()
69        };
70
71        let execution_result = StateTransitionExecutionResult::SuccessfulExecution {
72            estimated_fees: None,
73            fee_result: fee_result.clone(),
74            address_balance_changes: BTreeMap::new(),
75        };
76
77        let result: Option<ExecTxResult> = execution_result
78            .try_into_platform_versioned(platform_version)
79            .expect("conversion should succeed");
80
81        let tx_result = result.expect("should produce Some result");
82        assert_eq!(tx_result.code, 0);
83        assert_eq!(
84            tx_result.gas_used,
85            fee_result.total_base_fee() as SignedCredits
86        );
87    }
88
89    #[test]
90    fn unpaid_consensus_error_produces_result_with_error_code() {
91        let platform_version = PlatformVersion::latest();
92
93        let consensus_error = ConsensusError::DefaultError;
94        let execution_result =
95            StateTransitionExecutionResult::UnpaidConsensusError(consensus_error);
96
97        let result: Option<ExecTxResult> = execution_result
98            .try_into_platform_versioned(platform_version)
99            .expect("conversion should succeed");
100
101        let tx_result = result.expect("should produce Some result");
102        assert_ne!(tx_result.code, 0);
103        assert_eq!(tx_result.gas_used, 0);
104        assert!(!tx_result.info.is_empty());
105    }
106
107    #[test]
108    fn paid_consensus_error_produces_result_with_fees_and_error_code() {
109        let platform_version = PlatformVersion::latest();
110
111        let consensus_error = ConsensusError::DefaultError;
112        let fee_result = FeeResult {
113            storage_fee: 0,
114            processing_fee: 200,
115            ..Default::default()
116        };
117
118        let execution_result = StateTransitionExecutionResult::PaidConsensusError {
119            error: consensus_error,
120            actual_fees: fee_result.clone(),
121        };
122
123        let result: Option<ExecTxResult> = execution_result
124            .try_into_platform_versioned(platform_version)
125            .expect("conversion should succeed");
126
127        let tx_result = result.expect("should produce Some result");
128        assert_ne!(tx_result.code, 0);
129        assert_eq!(
130            tx_result.gas_used,
131            fee_result.total_base_fee() as SignedCredits
132        );
133        assert!(!tx_result.info.is_empty());
134    }
135
136    #[test]
137    fn internal_error_produces_result_with_internal_code() {
138        let platform_version = PlatformVersion::latest();
139
140        let execution_result =
141            StateTransitionExecutionResult::InternalError("something broke".to_string());
142
143        let result: Option<ExecTxResult> = execution_result
144            .try_into_platform_versioned(platform_version)
145            .expect("conversion should succeed");
146
147        let tx_result = result.expect("should produce Some result");
148        // Internal error code is 13
149        assert_eq!(tx_result.code, 13);
150        assert!(tx_result.info.is_empty());
151    }
152
153    #[test]
154    fn not_executed_produces_none() {
155        let platform_version = PlatformVersion::latest();
156
157        let execution_result =
158            StateTransitionExecutionResult::NotExecuted(NotExecutedReason::ProposerRanOutOfTime);
159
160        let result: Option<ExecTxResult> = execution_result
161            .try_into_platform_versioned(platform_version)
162            .expect("conversion should succeed");
163
164        assert!(result.is_none());
165    }
166
167    #[test]
168    fn successful_execution_with_zero_fees() {
169        let platform_version = PlatformVersion::latest();
170
171        let fee_result = FeeResult::default();
172
173        let execution_result = StateTransitionExecutionResult::SuccessfulExecution {
174            estimated_fees: None,
175            fee_result,
176            address_balance_changes: BTreeMap::new(),
177        };
178
179        let result: Option<ExecTxResult> = execution_result
180            .try_into_platform_versioned(platform_version)
181            .expect("conversion should succeed");
182
183        let tx_result = result.expect("should produce Some result");
184        assert_eq!(tx_result.code, 0);
185        assert_eq!(tx_result.gas_used, 0);
186    }
187}