soroban_rs/mock/
transaction.rs

1use std::convert::TryInto;
2use stellar_rpc_client::{GetTransactionResponse, SimulateTransactionResponse};
3use stellar_xdr::curr::{
4    AccountEntry, AccountId, ContractEvent, ContractEventBody, ContractEventType, ContractEventV0,
5    ExtensionPoint, Hash, LedgerEntry, LedgerEntryChange, LedgerEntryData, LedgerEntryExt, Memo,
6    MuxedAccount, Operation, OperationBody, OperationMeta, OperationResult, Preconditions,
7    ScAddress, ScVal, SequenceNumber, SetOptionsOp, SorobanTransactionMeta,
8    SorobanTransactionMetaExt, Transaction, TransactionEnvelope, TransactionExt, TransactionMeta,
9    TransactionMetaV3, TransactionResult, TransactionResultExt, TransactionResultResult,
10    TransactionV1Envelope, Uint256, VecM,
11};
12
13use crate::SorobanTransactionResponse;
14
15pub struct MockTransactionResult {
16    pub success: bool,
17}
18
19pub struct MockTransactionMeta {
20    pub return_value: Option<ScVal>,
21    pub account_entry: Option<AccountEntry>,
22}
23
24pub struct MockGetTransactionResponse {
25    pub tx_result: Option<MockTransactionResult>,
26    pub tx_meta: Option<MockTransactionMeta>,
27    pub tx_envelope: Option<TransactionEnvelope>,
28}
29
30enum MockResponseType {
31    Basic,
32    WithReturnValue(ScVal),
33    WithAccountEntry(AccountEntry),
34}
35
36#[allow(dead_code)]
37pub fn mock_transaction(account_id: AccountId, operations: Vec<Operation>) -> Transaction {
38    Transaction {
39        fee: 100,
40        seq_num: SequenceNumber::from(1),
41        source_account: account_id.into(),
42        cond: Preconditions::None,
43        memo: Memo::None,
44        operations: operations.try_into().unwrap_or_default(),
45        ext: TransactionExt::V0,
46    }
47}
48
49#[allow(dead_code)]
50pub fn mock_transaction_envelope(account_id: AccountId) -> TransactionEnvelope {
51    TransactionEnvelope::Tx(TransactionV1Envelope {
52        tx: mock_transaction(account_id, vec![]),
53        signatures: Default::default(),
54    })
55}
56
57#[allow(dead_code)]
58pub fn create_contract_id_val() -> ScVal {
59    let contract_hash = Hash([1; 32]);
60    ScVal::Address(ScAddress::Contract(contract_hash))
61}
62
63#[allow(dead_code)]
64pub fn create_mock_set_options_tx_envelope() -> TransactionEnvelope {
65    // Create a mock account source
66    let source_account = MuxedAccount::Ed25519(Uint256([0; 32]));
67
68    // Create a SetOptions operation
69    let set_options_op = Operation {
70        source_account: None,
71        body: OperationBody::SetOptions(SetOptionsOp {
72            inflation_dest: None,
73            clear_flags: None,
74            set_flags: None,
75            master_weight: None,
76            low_threshold: None,
77            med_threshold: None,
78            high_threshold: None,
79            home_domain: None,
80            signer: None,
81        }),
82    };
83
84    // Create the operations vector
85    let operations = vec![set_options_op].try_into().unwrap_or_default();
86
87    // Create and return the transaction envelope
88    TransactionEnvelope::Tx(TransactionV1Envelope {
89        tx: Transaction {
90            source_account,
91            fee: 100,
92            seq_num: 1.into(),
93            cond: Preconditions::None,
94            memo: Memo::None,
95            operations,
96            ext: TransactionExt::V0,
97        },
98        signatures: Default::default(),
99    })
100}
101
102#[allow(dead_code)]
103pub fn mock_simulate_tx_response(min_resource_fee: Option<u64>) -> SimulateTransactionResponse {
104    SimulateTransactionResponse {
105        min_resource_fee: min_resource_fee.unwrap_or(100),
106        transaction_data: "test".to_string(),
107        ..Default::default()
108    }
109}
110
111#[allow(dead_code)]
112fn mock_transaction_response_impl(response_type: MockResponseType) -> GetTransactionResponse {
113    let mut response = GetTransactionResponse {
114        status: "SUCCESS".to_string(),
115        envelope: None,
116        result: Some(create_success_tx_result()),
117        result_meta: None,
118    };
119
120    match response_type {
121        MockResponseType::Basic => {}
122        MockResponseType::WithReturnValue(val) => {
123            response.result_meta = Some(create_soroban_tx_meta_with_return_value(val));
124        }
125        MockResponseType::WithAccountEntry(account) => {
126            let ledger_entry = LedgerEntry {
127                last_modified_ledger_seq: 1,
128                data: LedgerEntryData::Account(account),
129                ext: LedgerEntryExt::V0,
130            };
131
132            let change = LedgerEntryChange::Updated(ledger_entry);
133            let changes = VecM::try_from(vec![change]).unwrap_or_default();
134            let op_meta = OperationMeta {
135                changes: stellar_xdr::curr::LedgerEntryChanges(changes),
136            };
137
138            let operations = VecM::try_from(vec![op_meta]).unwrap_or_default();
139            let meta = TransactionMeta::V3(TransactionMetaV3 {
140                ext: ExtensionPoint::V0,
141                soroban_meta: None,
142                tx_changes_before: Default::default(),
143                tx_changes_after: Default::default(),
144                operations,
145            });
146
147            response.result_meta = Some(meta);
148        }
149    }
150
151    response
152}
153
154#[allow(dead_code)]
155pub fn mock_transaction_response() -> SorobanTransactionResponse {
156    SorobanTransactionResponse::from(mock_transaction_response_impl(MockResponseType::Basic))
157}
158
159#[allow(dead_code)]
160pub fn mock_transaction_response_with_return_value(
161    return_val: ScVal,
162) -> SorobanTransactionResponse {
163    SorobanTransactionResponse::from(mock_transaction_response_impl(
164        MockResponseType::WithReturnValue(return_val),
165    ))
166}
167
168#[allow(dead_code)]
169pub fn mock_transaction_response_with_account_entry(
170    account: AccountEntry,
171) -> GetTransactionResponse {
172    mock_transaction_response_impl(MockResponseType::WithAccountEntry(account))
173}
174
175#[allow(dead_code)]
176fn create_success_tx_result() -> TransactionResult {
177    // Create empty operation results
178    let empty_vec: Vec<OperationResult> = Vec::new();
179    let op_results = empty_vec.try_into().unwrap_or_default();
180
181    TransactionResult {
182        fee_charged: 100,
183        result: TransactionResultResult::TxSuccess(op_results),
184        ext: TransactionResultExt::V0,
185    }
186}
187
188#[allow(dead_code)]
189fn create_soroban_tx_meta_with_return_value(return_val: ScVal) -> TransactionMeta {
190    TransactionMeta::V3(TransactionMetaV3 {
191        ext: ExtensionPoint::V0,
192        soroban_meta: Some(SorobanTransactionMeta {
193            ext: SorobanTransactionMetaExt::V0,
194            events: Default::default(),
195            return_value: return_val,
196            diagnostic_events: Default::default(),
197        }),
198        tx_changes_before: Default::default(),
199        tx_changes_after: Default::default(),
200        operations: Default::default(),
201    })
202}
203
204#[allow(dead_code)]
205fn create_tx_meta_from_mock(mock: &MockTransactionMeta) -> TransactionMeta {
206    // Check if we have a return value
207    if let Some(return_val) = &mock.return_value {
208        return create_soroban_tx_meta_with_return_value(return_val.clone());
209    }
210
211    // Check if we have an account entry
212    if let Some(account) = &mock.account_entry {
213        // Create a ledger entry for the account
214        let ledger_entry = LedgerEntry {
215            last_modified_ledger_seq: 1,
216            data: LedgerEntryData::Account(account.clone()),
217            ext: LedgerEntryExt::V0,
218        };
219
220        let change = LedgerEntryChange::Updated(ledger_entry);
221        let changes = VecM::try_from(vec![change]).unwrap_or_default();
222        let op_meta = OperationMeta {
223            changes: stellar_xdr::curr::LedgerEntryChanges(changes),
224        };
225
226        let operations = VecM::try_from(vec![op_meta]).unwrap_or_default();
227
228        return TransactionMeta::V3(TransactionMetaV3 {
229            ext: ExtensionPoint::V0,
230            soroban_meta: None,
231            tx_changes_before: Default::default(),
232            tx_changes_after: Default::default(),
233            operations,
234        });
235    }
236
237    // Default empty meta if neither return value nor account entry is present
238    TransactionMeta::V3(TransactionMetaV3 {
239        ext: ExtensionPoint::V0,
240        soroban_meta: None,
241        tx_changes_before: Default::default(),
242        tx_changes_after: Default::default(),
243        operations: Default::default(),
244    })
245}
246
247#[allow(dead_code)]
248pub fn create_mock_contract_event() -> ContractEvent {
249    ContractEvent {
250        body: ContractEventBody::V0(ContractEventV0 {
251            data: ScVal::I32(0),
252            topics: VecM::default(),
253        }),
254        ext: ExtensionPoint::V0,
255        contract_id: Some(Hash([1; 32])),
256        type_: ContractEventType::Contract,
257    }
258}