Skip to main content

avm_rs/state/
mod.rs

1//! State management interfaces and implementations
2
3use crate::error::AvmResult;
4use crate::types::{GlobalField, TealValue, TxnField};
5use std::collections::HashMap;
6
7/// Account address type
8pub type Address = Vec<u8>;
9
10/// Application ID type
11pub type AppId = u64;
12
13/// Asset ID type
14pub type AssetId = u64;
15
16/// Micro Algos type
17pub type MicroAlgos = u64;
18
19/// Transaction type
20#[derive(Debug, Clone, PartialEq)]
21pub enum TransactionType {
22    Payment,
23    KeyRegistration,
24    AssetConfig,
25    AssetTransfer,
26    AssetFreeze,
27    ApplicationCall,
28    StateProof,
29}
30
31/// Transaction data
32#[derive(Debug, Clone)]
33pub struct Transaction {
34    pub sender: Address,
35    pub fee: MicroAlgos,
36    pub first_valid: u64,
37    pub first_valid_time: u64,
38    pub last_valid: u64,
39    pub note: Vec<u8>,
40    pub lease: Vec<u8>,
41    pub receiver: Option<Address>,
42    pub amount: Option<MicroAlgos>,
43    pub close_remainder_to: Option<Address>,
44    pub vote_pk: Option<Vec<u8>>,
45    pub selection_pk: Option<Vec<u8>>,
46    pub vote_first: Option<u64>,
47    pub vote_last: Option<u64>,
48    pub vote_key_dilution: Option<u64>,
49    pub tx_type: TransactionType,
50    pub type_enum: u64,
51    pub xfer_asset: Option<AssetId>,
52    pub asset_amount: Option<u64>,
53    pub asset_sender: Option<Address>,
54    pub asset_receiver: Option<Address>,
55    pub asset_close_to: Option<Address>,
56    pub group_index: u64,
57    pub tx_id: Vec<u8>,
58    pub application_id: Option<AppId>,
59    pub on_completion: Option<u64>,
60    pub application_args: Vec<Vec<u8>>,
61    pub accounts: Vec<Address>,
62    pub approval_program: Option<Vec<u8>>,
63    pub clear_state_program: Option<Vec<u8>>,
64    pub rekey_to: Option<Address>,
65    pub config_asset: Option<AssetId>,
66    pub config_asset_total: Option<u64>,
67    pub config_asset_decimals: Option<u8>,
68    pub config_asset_default_frozen: Option<bool>,
69    pub config_asset_unit_name: Option<String>,
70    pub config_asset_name: Option<String>,
71    pub config_asset_url: Option<String>,
72    pub config_asset_metadata_hash: Option<Vec<u8>>,
73    pub config_asset_manager: Option<Address>,
74    pub config_asset_reserve: Option<Address>,
75    pub config_asset_freeze: Option<Address>,
76    pub config_asset_clawback: Option<Address>,
77    pub freeze_asset: Option<AssetId>,
78    pub freeze_asset_account: Option<Address>,
79    pub freeze_asset_frozen: Option<bool>,
80    pub assets: Vec<AssetId>,
81    pub applications: Vec<AppId>,
82    pub global_num_uint: Option<u64>,
83    pub global_num_byte_slice: Option<u64>,
84    pub local_num_uint: Option<u64>,
85    pub local_num_byte_slice: Option<u64>,
86    pub extra_program_pages: Option<u32>,
87    pub nonparticipation: Option<bool>,
88    pub logs: Vec<Vec<u8>>,
89    pub created_asset_id: Option<AssetId>,
90    pub created_application_id: Option<AppId>,
91    pub last_log: Option<Vec<u8>>,
92    pub state_proof_pk: Option<Vec<u8>>,
93    pub approval_program_pages: Vec<Vec<u8>>,
94    pub clear_state_program_pages: Vec<Vec<u8>>,
95}
96
97impl Default for Transaction {
98    fn default() -> Self {
99        Self::new()
100    }
101}
102
103impl Transaction {
104    /// Create a new default transaction
105    pub fn new() -> Self {
106        Self {
107            sender: vec![0; 32],
108            fee: 1000,
109            first_valid: 1,
110            first_valid_time: 0,
111            last_valid: 1000,
112            note: Vec::new(),
113            lease: vec![0; 32],
114            receiver: None,
115            amount: None,
116            close_remainder_to: None,
117            vote_pk: None,
118            selection_pk: None,
119            vote_first: None,
120            vote_last: None,
121            vote_key_dilution: None,
122            tx_type: TransactionType::Payment,
123            type_enum: 1,
124            xfer_asset: None,
125            asset_amount: None,
126            asset_sender: None,
127            asset_receiver: None,
128            asset_close_to: None,
129            group_index: 0,
130            tx_id: vec![0; 32],
131            application_id: None,
132            on_completion: None,
133            application_args: Vec::new(),
134            accounts: Vec::new(),
135            approval_program: None,
136            clear_state_program: None,
137            rekey_to: None,
138            config_asset: None,
139            config_asset_total: None,
140            config_asset_decimals: None,
141            config_asset_default_frozen: None,
142            config_asset_unit_name: None,
143            config_asset_name: None,
144            config_asset_url: None,
145            config_asset_metadata_hash: None,
146            config_asset_manager: None,
147            config_asset_reserve: None,
148            config_asset_freeze: None,
149            config_asset_clawback: None,
150            freeze_asset: None,
151            freeze_asset_account: None,
152            freeze_asset_frozen: None,
153            assets: Vec::new(),
154            applications: Vec::new(),
155            global_num_uint: None,
156            global_num_byte_slice: None,
157            local_num_uint: None,
158            local_num_byte_slice: None,
159            extra_program_pages: None,
160            nonparticipation: None,
161            logs: Vec::new(),
162            created_asset_id: None,
163            created_application_id: None,
164            last_log: None,
165            state_proof_pk: None,
166            approval_program_pages: Vec::new(),
167            clear_state_program_pages: Vec::new(),
168        }
169    }
170
171    /// Create a payment transaction
172    pub fn payment(sender: Address, receiver: Address, amount: MicroAlgos) -> Self {
173        let mut tx = Self::new();
174        tx.sender = sender;
175        tx.receiver = Some(receiver);
176        tx.amount = Some(amount);
177        tx.tx_type = TransactionType::Payment;
178        tx.type_enum = 1;
179        tx
180    }
181
182    /// Create an asset transfer transaction
183    pub fn asset_transfer(
184        sender: Address,
185        receiver: Address,
186        asset_id: AssetId,
187        amount: u64,
188    ) -> Self {
189        let mut tx = Self::new();
190        tx.sender = sender;
191        tx.asset_receiver = Some(receiver);
192        tx.xfer_asset = Some(asset_id);
193        tx.asset_amount = Some(amount);
194        tx.tx_type = TransactionType::AssetTransfer;
195        tx.type_enum = 4;
196        tx
197    }
198}
199
200/// Asset holding information
201#[derive(Debug, Clone)]
202pub struct AssetHolding {
203    pub amount: u64,
204    pub frozen: bool,
205}
206
207/// Asset parameters
208#[derive(Debug, Clone)]
209pub struct AssetParams {
210    pub total: u64,
211    pub decimals: u8,
212    pub default_frozen: bool,
213    pub name: String,
214    pub unit_name: String,
215    pub url: String,
216    pub metadata_hash: Vec<u8>,
217    pub manager: Address,
218    pub reserve: Address,
219    pub freeze: Address,
220    pub clawback: Address,
221}
222
223/// Application parameters
224#[derive(Debug, Clone)]
225pub struct AppParams {
226    pub approval_program: Vec<u8>,
227    pub clear_state_program: Vec<u8>,
228    pub global_state_schema: StateSchema,
229    pub local_state_schema: StateSchema,
230    pub extra_program_pages: u32,
231    pub creator: Address,
232}
233
234/// State schema defining storage allocation
235#[derive(Debug, Clone)]
236pub struct StateSchema {
237    pub num_uint: u64,
238    pub num_byte_slice: u64,
239}
240
241/// Account parameters
242#[derive(Debug, Clone)]
243pub struct AccountParams {
244    pub micro_algos: MicroAlgos,
245    pub rewards_base: u64,
246    pub reward_algos: MicroAlgos,
247    pub status: String,
248    pub auth_addr: Option<Address>,
249    pub total_apps_schema: StateSchema,
250    pub total_apps_extra_pages: u32,
251    pub total_assets: u64,
252    pub total_created_assets: u64,
253    pub total_created_apps: u64,
254    pub total_boxes: u64,
255    pub total_box_bytes: u64,
256}
257
258/// Trait for accessing ledger state
259pub trait LedgerAccess: std::fmt::Debug {
260    /// Get account balance
261    fn balance(&self, addr: &Address) -> AvmResult<MicroAlgos>;
262
263    /// Get minimum balance for account
264    fn min_balance(&self, addr: &Address) -> AvmResult<MicroAlgos>;
265
266    /// Get global state value
267    fn app_global_get(&self, app_id: AppId, key: &str) -> AvmResult<Option<TealValue>>;
268
269    /// Set global state value
270    fn app_global_put(&mut self, app_id: AppId, key: &str, value: TealValue) -> AvmResult<()>;
271
272    /// Delete global state value
273    fn app_global_del(&mut self, app_id: AppId, key: &str) -> AvmResult<()>;
274
275    /// Get local state value
276    fn app_local_get(
277        &self,
278        addr: &Address,
279        app_id: AppId,
280        key: &str,
281    ) -> AvmResult<Option<TealValue>>;
282
283    /// Set local state value
284    fn app_local_put(
285        &mut self,
286        addr: &Address,
287        app_id: AppId,
288        key: &str,
289        value: TealValue,
290    ) -> AvmResult<()>;
291
292    /// Delete local state value
293    fn app_local_del(&mut self, addr: &Address, app_id: AppId, key: &str) -> AvmResult<()>;
294
295    /// Check if account has opted into application
296    fn app_opted_in(&self, addr: &Address, app_id: AppId) -> AvmResult<bool>;
297
298    /// Get asset holding information
299    fn asset_holding(&self, addr: &Address, asset_id: AssetId) -> AvmResult<Option<AssetHolding>>;
300
301    /// Get asset parameters
302    fn asset_params(&self, asset_id: AssetId) -> AvmResult<Option<AssetParams>>;
303
304    /// Get application parameters
305    fn app_params(&self, app_id: AppId) -> AvmResult<Option<AppParams>>;
306
307    /// Get account parameters
308    fn account_params(&self, addr: &Address) -> AvmResult<Option<AccountParams>>;
309
310    /// Get current round number
311    fn current_round(&self) -> AvmResult<u64>;
312
313    /// Get latest timestamp
314    fn latest_timestamp(&self) -> AvmResult<u64>;
315
316    /// Get genesis hash
317    fn genesis_hash(&self) -> AvmResult<Vec<u8>>;
318
319    /// Get current application ID (for application mode)
320    fn current_application_id(&self) -> AvmResult<AppId>;
321
322    /// Get creator address for current application
323    fn creator_address(&self) -> AvmResult<Address>;
324
325    /// Get current application address
326    fn current_application_address(&self) -> AvmResult<Address>;
327
328    /// Get group ID for current transaction group
329    fn group_id(&self) -> AvmResult<Vec<u8>>;
330
331    /// Get opcode budget for current execution
332    fn opcode_budget(&self) -> AvmResult<u64>;
333
334    /// Get caller application ID (for inner transactions)
335    fn caller_application_id(&self) -> AvmResult<Option<AppId>>;
336
337    /// Get caller application address (for inner transactions)
338    fn caller_application_address(&self) -> AvmResult<Option<Address>>;
339
340    /// Get transaction field value
341    fn get_txn_field(&self, txn_index: usize, field: TxnField) -> AvmResult<TealValue>;
342
343    /// Get global field value
344    fn get_global_field(&self, field: GlobalField) -> AvmResult<TealValue>;
345
346    /// Get current transaction (cloned)
347    fn current_transaction(&self) -> AvmResult<Transaction>;
348
349    /// Get transaction group (cloned)
350    fn transaction_group(&self) -> AvmResult<Vec<Transaction>>;
351
352    /// Get program arguments for current transaction (cloned)
353    fn program_args(&self) -> AvmResult<Vec<Vec<u8>>>;
354}
355
356/// Mock ledger implementation for testing
357#[derive(Debug)]
358pub struct MockLedger {
359    balances: HashMap<Address, MicroAlgos>,
360    min_balances: HashMap<Address, MicroAlgos>,
361    global_state: HashMap<(AppId, String), TealValue>,
362    local_state: HashMap<(Address, AppId, String), TealValue>,
363    opted_in: HashMap<(Address, AppId), bool>,
364    asset_holdings: HashMap<(Address, AssetId), AssetHolding>,
365    asset_params: HashMap<AssetId, AssetParams>,
366    app_params: HashMap<AppId, AppParams>,
367    account_params: HashMap<Address, AccountParams>,
368    current_round: u64,
369    latest_timestamp: u64,
370    genesis_hash: Vec<u8>,
371    current_app_id: AppId,
372    creator_addr: Address,
373    current_app_addr: Address,
374    group_id: Vec<u8>,
375    opcode_budget: u64,
376    caller_app_id: Option<AppId>,
377    caller_app_addr: Option<Address>,
378    transactions: Vec<Transaction>,
379    current_txn_index: usize,
380    program_args: Vec<Vec<u8>>,
381}
382
383impl MockLedger {
384    /// Create a new mock ledger
385    pub fn new() -> Self {
386        Self::default()
387    }
388
389    /// Set account balance
390    pub fn set_balance(&mut self, addr: Address, balance: MicroAlgos) {
391        self.balances.insert(addr, balance);
392    }
393
394    /// Set minimum balance
395    pub fn set_min_balance(&mut self, addr: Address, min_balance: MicroAlgos) {
396        self.min_balances.insert(addr, min_balance);
397    }
398
399    /// Set global state
400    pub fn set_global_state(&mut self, app_id: AppId, key: String, value: TealValue) {
401        self.global_state.insert((app_id, key), value);
402    }
403
404    /// Set local state
405    pub fn set_local_state(&mut self, addr: Address, app_id: AppId, key: String, value: TealValue) {
406        self.local_state.insert((addr, app_id, key), value);
407    }
408
409    /// Set application opt-in status
410    pub fn set_opted_in(&mut self, addr: Address, app_id: AppId, opted_in: bool) {
411        self.opted_in.insert((addr, app_id), opted_in);
412    }
413
414    /// Set asset holding
415    pub fn set_asset_holding(&mut self, addr: Address, asset_id: AssetId, holding: AssetHolding) {
416        self.asset_holdings.insert((addr, asset_id), holding);
417    }
418
419    /// Set asset parameters
420    pub fn set_asset_params(&mut self, asset_id: AssetId, params: AssetParams) {
421        self.asset_params.insert(asset_id, params);
422    }
423
424    /// Set application parameters
425    pub fn set_app_params(&mut self, app_id: AppId, params: AppParams) {
426        self.app_params.insert(app_id, params);
427    }
428
429    /// Set account parameters
430    pub fn set_account_params(&mut self, addr: Address, params: AccountParams) {
431        self.account_params.insert(addr, params);
432    }
433
434    /// Set current round
435    pub fn set_current_round(&mut self, round: u64) {
436        self.current_round = round;
437    }
438
439    /// Set latest timestamp
440    pub fn set_latest_timestamp(&mut self, timestamp: u64) {
441        self.latest_timestamp = timestamp;
442    }
443
444    /// Set genesis hash
445    pub fn set_genesis_hash(&mut self, hash: Vec<u8>) {
446        self.genesis_hash = hash;
447    }
448
449    /// Set current application ID
450    pub fn set_current_application_id(&mut self, app_id: AppId) {
451        self.current_app_id = app_id;
452    }
453
454    /// Set creator address
455    pub fn set_creator_address(&mut self, addr: Address) {
456        self.creator_addr = addr;
457    }
458
459    /// Set current application address
460    pub fn set_current_application_address(&mut self, addr: Address) {
461        self.current_app_addr = addr;
462    }
463
464    /// Set group ID
465    pub fn set_group_id(&mut self, group_id: Vec<u8>) {
466        self.group_id = group_id;
467    }
468
469    /// Set opcode budget
470    pub fn set_opcode_budget(&mut self, budget: u64) {
471        self.opcode_budget = budget;
472    }
473
474    /// Set caller application ID
475    pub fn set_caller_application_id(&mut self, app_id: Option<AppId>) {
476        self.caller_app_id = app_id;
477    }
478
479    /// Set caller application address
480    pub fn set_caller_application_address(&mut self, addr: Option<Address>) {
481        self.caller_app_addr = addr;
482    }
483
484    /// Add a transaction to the group
485    pub fn add_transaction(&mut self, transaction: Transaction) {
486        self.transactions.push(transaction);
487    }
488
489    /// Set the current transaction index
490    pub fn set_current_transaction_index(&mut self, index: usize) {
491        self.current_txn_index = index;
492    }
493
494    /// Set program arguments
495    pub fn set_program_args(&mut self, args: Vec<Vec<u8>>) {
496        self.program_args = args;
497    }
498
499    /// Set up a simple payment transaction group
500    pub fn setup_payment_transaction(
501        &mut self,
502        sender: Address,
503        receiver: Address,
504        amount: MicroAlgos,
505    ) {
506        let tx = Transaction::payment(sender, receiver, amount);
507        self.transactions.clear();
508        self.transactions.push(tx);
509        self.current_txn_index = 0;
510    }
511
512    /// Set up an asset transfer transaction group
513    pub fn setup_asset_transfer(
514        &mut self,
515        sender: Address,
516        receiver: Address,
517        asset_id: AssetId,
518        amount: u64,
519    ) {
520        let tx = Transaction::asset_transfer(sender, receiver, asset_id, amount);
521        self.transactions.clear();
522        self.transactions.push(tx);
523        self.current_txn_index = 0;
524    }
525
526    /// Clear all transactions
527    pub fn clear_transactions(&mut self) {
528        self.transactions.clear();
529        self.current_txn_index = 0;
530    }
531
532    /// Create a mock ledger with realistic defaults
533    pub fn with_defaults() -> Self {
534        let mut ledger = Self {
535            balances: HashMap::new(),
536            min_balances: HashMap::new(),
537            global_state: HashMap::new(),
538            local_state: HashMap::new(),
539            opted_in: HashMap::new(),
540            asset_holdings: HashMap::new(),
541            asset_params: HashMap::new(),
542            app_params: HashMap::new(),
543            account_params: HashMap::new(),
544            current_round: 1000,
545            latest_timestamp: 1640995200, // 2022-01-01
546            genesis_hash: vec![0; 32],
547            current_app_id: 0,
548            creator_addr: Vec::new(),
549            current_app_addr: Vec::new(),
550            group_id: Vec::new(),
551            opcode_budget: 700,
552            caller_app_id: None,
553            caller_app_addr: None,
554            transactions: Vec::new(),
555            current_txn_index: 0,
556            program_args: Vec::new(),
557        };
558
559        // Add a default payment transaction
560        let sender = vec![1; 32];
561        let receiver = vec![2; 32];
562        ledger.setup_payment_transaction(sender, receiver, 1_000_000);
563
564        ledger
565    }
566}
567
568impl Default for MockLedger {
569    fn default() -> Self {
570        Self::with_defaults()
571    }
572}
573
574impl LedgerAccess for MockLedger {
575    fn balance(&self, addr: &Address) -> AvmResult<MicroAlgos> {
576        Ok(self.balances.get(addr).copied().unwrap_or(0))
577    }
578
579    fn min_balance(&self, addr: &Address) -> AvmResult<MicroAlgos> {
580        Ok(self.min_balances.get(addr).copied().unwrap_or(100000))
581    }
582
583    fn app_global_get(&self, app_id: AppId, key: &str) -> AvmResult<Option<TealValue>> {
584        Ok(self.global_state.get(&(app_id, key.to_string())).cloned())
585    }
586
587    fn app_global_put(&mut self, app_id: AppId, key: &str, value: TealValue) -> AvmResult<()> {
588        self.global_state.insert((app_id, key.to_string()), value);
589        Ok(())
590    }
591
592    fn app_global_del(&mut self, app_id: AppId, key: &str) -> AvmResult<()> {
593        self.global_state.remove(&(app_id, key.to_string()));
594        Ok(())
595    }
596
597    fn app_local_get(
598        &self,
599        addr: &Address,
600        app_id: AppId,
601        key: &str,
602    ) -> AvmResult<Option<TealValue>> {
603        Ok(self
604            .local_state
605            .get(&(addr.clone(), app_id, key.to_string()))
606            .cloned())
607    }
608
609    fn app_local_put(
610        &mut self,
611        addr: &Address,
612        app_id: AppId,
613        key: &str,
614        value: TealValue,
615    ) -> AvmResult<()> {
616        self.local_state
617            .insert((addr.clone(), app_id, key.to_string()), value);
618        Ok(())
619    }
620
621    fn app_local_del(&mut self, addr: &Address, app_id: AppId, key: &str) -> AvmResult<()> {
622        self.local_state
623            .remove(&(addr.clone(), app_id, key.to_string()));
624        Ok(())
625    }
626
627    fn app_opted_in(&self, addr: &Address, app_id: AppId) -> AvmResult<bool> {
628        Ok(self
629            .opted_in
630            .get(&(addr.clone(), app_id))
631            .copied()
632            .unwrap_or(false))
633    }
634
635    fn asset_holding(&self, addr: &Address, asset_id: AssetId) -> AvmResult<Option<AssetHolding>> {
636        Ok(self.asset_holdings.get(&(addr.clone(), asset_id)).cloned())
637    }
638
639    fn asset_params(&self, asset_id: AssetId) -> AvmResult<Option<AssetParams>> {
640        Ok(self.asset_params.get(&asset_id).cloned())
641    }
642
643    fn app_params(&self, app_id: AppId) -> AvmResult<Option<AppParams>> {
644        Ok(self.app_params.get(&app_id).cloned())
645    }
646
647    fn account_params(&self, addr: &Address) -> AvmResult<Option<AccountParams>> {
648        Ok(self.account_params.get(addr).cloned())
649    }
650
651    fn current_round(&self) -> AvmResult<u64> {
652        Ok(self.current_round)
653    }
654
655    fn latest_timestamp(&self) -> AvmResult<u64> {
656        Ok(self.latest_timestamp)
657    }
658
659    fn genesis_hash(&self) -> AvmResult<Vec<u8>> {
660        Ok(self.genesis_hash.clone())
661    }
662
663    fn current_application_id(&self) -> AvmResult<AppId> {
664        Ok(self.current_app_id)
665    }
666
667    fn creator_address(&self) -> AvmResult<Address> {
668        Ok(self.creator_addr.clone())
669    }
670
671    fn current_application_address(&self) -> AvmResult<Address> {
672        Ok(self.current_app_addr.clone())
673    }
674
675    fn group_id(&self) -> AvmResult<Vec<u8>> {
676        Ok(self.group_id.clone())
677    }
678
679    fn opcode_budget(&self) -> AvmResult<u64> {
680        Ok(self.opcode_budget)
681    }
682
683    fn caller_application_id(&self) -> AvmResult<Option<AppId>> {
684        Ok(self.caller_app_id)
685    }
686
687    fn caller_application_address(&self) -> AvmResult<Option<Address>> {
688        Ok(self.caller_app_addr.clone())
689    }
690
691    fn get_txn_field(&self, txn_index: usize, field: TxnField) -> AvmResult<TealValue> {
692        let tx = self.transactions.get(txn_index).ok_or_else(|| {
693            crate::error::AvmError::InvalidTransactionField {
694                field: format!("transaction index {txn_index}"),
695            }
696        })?;
697
698        let value = match field {
699            TxnField::Sender => TealValue::Bytes(tx.sender.clone()),
700            TxnField::Fee => TealValue::Uint(tx.fee),
701            TxnField::FirstValid => TealValue::Uint(tx.first_valid),
702            TxnField::FirstValidTime => TealValue::Uint(tx.first_valid_time),
703            TxnField::LastValid => TealValue::Uint(tx.last_valid),
704            TxnField::Note => TealValue::Bytes(tx.note.clone()),
705            TxnField::Lease => TealValue::Bytes(tx.lease.clone()),
706            TxnField::Receiver => TealValue::Bytes(tx.receiver.clone().unwrap_or_default()),
707            TxnField::Amount => TealValue::Uint(tx.amount.unwrap_or(0)),
708            TxnField::CloseRemainderTo => {
709                TealValue::Bytes(tx.close_remainder_to.clone().unwrap_or_default())
710            }
711            TxnField::VotePK => TealValue::Bytes(tx.vote_pk.clone().unwrap_or_default()),
712            TxnField::SelectionPK => TealValue::Bytes(tx.selection_pk.clone().unwrap_or_default()),
713            TxnField::VoteFirst => TealValue::Uint(tx.vote_first.unwrap_or(0)),
714            TxnField::VoteLast => TealValue::Uint(tx.vote_last.unwrap_or(0)),
715            TxnField::VoteKeyDilution => TealValue::Uint(tx.vote_key_dilution.unwrap_or(0)),
716            TxnField::Type => TealValue::Bytes(match tx.tx_type {
717                TransactionType::Payment => b"pay".to_vec(),
718                TransactionType::KeyRegistration => b"keyreg".to_vec(),
719                TransactionType::AssetConfig => b"acfg".to_vec(),
720                TransactionType::AssetTransfer => b"axfer".to_vec(),
721                TransactionType::AssetFreeze => b"afrz".to_vec(),
722                TransactionType::ApplicationCall => b"appl".to_vec(),
723                TransactionType::StateProof => b"stpf".to_vec(),
724            }),
725            TxnField::TypeEnum => TealValue::Uint(tx.type_enum),
726            TxnField::XferAsset => TealValue::Uint(tx.xfer_asset.unwrap_or(0)),
727            TxnField::AssetAmount => TealValue::Uint(tx.asset_amount.unwrap_or(0)),
728            TxnField::AssetSender => TealValue::Bytes(tx.asset_sender.clone().unwrap_or_default()),
729            TxnField::AssetReceiver => {
730                TealValue::Bytes(tx.asset_receiver.clone().unwrap_or_default())
731            }
732            TxnField::AssetCloseTo => {
733                TealValue::Bytes(tx.asset_close_to.clone().unwrap_or_default())
734            }
735            TxnField::GroupIndex => TealValue::Uint(tx.group_index),
736            TxnField::TxID => TealValue::Bytes(tx.tx_id.clone()),
737            TxnField::ApplicationID => TealValue::Uint(tx.application_id.unwrap_or(0)),
738            TxnField::OnCompletion => TealValue::Uint(tx.on_completion.unwrap_or(0)),
739            TxnField::ApplicationArgs => {
740                // For now, return the first application arg if it exists
741                TealValue::Bytes(tx.application_args.first().cloned().unwrap_or_default())
742            }
743            TxnField::NumAppArgs => TealValue::Uint(tx.application_args.len() as u64),
744            TxnField::Accounts => {
745                // For now, return the first account if it exists
746                TealValue::Bytes(tx.accounts.first().cloned().unwrap_or_default())
747            }
748            TxnField::NumAccounts => TealValue::Uint(tx.accounts.len() as u64),
749            TxnField::ApprovalProgram => {
750                TealValue::Bytes(tx.approval_program.clone().unwrap_or_default())
751            }
752            TxnField::ClearStateProgram => {
753                TealValue::Bytes(tx.clear_state_program.clone().unwrap_or_default())
754            }
755            TxnField::RekeyTo => TealValue::Bytes(tx.rekey_to.clone().unwrap_or_default()),
756            _ => TealValue::Uint(0), // Default for other fields
757        };
758
759        Ok(value)
760    }
761
762    fn get_global_field(&self, field: GlobalField) -> AvmResult<TealValue> {
763        let value = match field {
764            GlobalField::MinTxnFee => TealValue::Uint(1000),
765            GlobalField::MinBalance => TealValue::Uint(100000),
766            GlobalField::MaxTxnLife => TealValue::Uint(1000),
767            GlobalField::ZeroAddress => TealValue::Bytes(vec![0; 32]),
768            GlobalField::GroupSize => TealValue::Uint(self.transactions.len() as u64),
769            GlobalField::LogicSigVersion => TealValue::Uint(8),
770            GlobalField::Round => TealValue::Uint(self.current_round),
771            GlobalField::LatestTimestamp => TealValue::Uint(self.latest_timestamp),
772            GlobalField::CurrentApplicationID => TealValue::Uint(self.current_app_id),
773            GlobalField::CreatorAddress => TealValue::Bytes(self.creator_addr.clone()),
774            GlobalField::CurrentApplicationAddress => {
775                TealValue::Bytes(self.current_app_addr.clone())
776            }
777            GlobalField::GroupID => TealValue::Bytes(self.group_id.clone()),
778            GlobalField::OpcodeBudget => TealValue::Uint(self.opcode_budget),
779            GlobalField::CallerApplicationID => TealValue::Uint(self.caller_app_id.unwrap_or(0)),
780            GlobalField::CallerApplicationAddress => {
781                TealValue::Bytes(self.caller_app_addr.clone().unwrap_or_default())
782            }
783            GlobalField::GenesisHash => TealValue::Bytes(self.genesis_hash.clone()),
784            _ => TealValue::Uint(0), // Default for other fields
785        };
786
787        Ok(value)
788    }
789
790    fn current_transaction(&self) -> AvmResult<Transaction> {
791        self.transactions
792            .get(self.current_txn_index)
793            .cloned()
794            .ok_or_else(|| crate::error::AvmError::InvalidTransactionField {
795                field: format!("current transaction index {}", self.current_txn_index),
796            })
797    }
798
799    fn transaction_group(&self) -> AvmResult<Vec<Transaction>> {
800        Ok(self.transactions.clone())
801    }
802
803    fn program_args(&self) -> AvmResult<Vec<Vec<u8>>> {
804        Ok(self.program_args.clone())
805    }
806}