solscript_codegen/
ir.rs

1//! Solana Intermediate Representation
2//!
3//! This module defines an IR that's closer to Solana's execution model,
4//! making it easier to generate Anchor Rust code.
5
6use crate::error::CodegenError;
7use solscript_ast::{self as ast, StateMutability, Visibility};
8
9/// A Solana program (corresponds to a SolScript contract)
10#[derive(Debug, Clone)]
11pub struct SolanaProgram {
12    pub name: String,
13    pub state: ProgramState,
14    pub mappings: Vec<MappingDef>,
15    pub modifiers: Vec<ModifierDefinition>,
16    pub instructions: Vec<Instruction>,
17    pub events: Vec<Event>,
18    pub errors: Vec<ProgramError>,
19    pub structs: Vec<StructDef>,
20    pub enums: Vec<EnumDef>,
21    /// Test functions marked with #[test]
22    pub tests: Vec<TestFunction>,
23}
24
25/// A test function
26#[derive(Debug, Clone)]
27pub struct TestFunction {
28    pub name: String,
29    pub body: Vec<Statement>,
30    /// Expected error message for #[should_fail("...")] tests
31    pub should_fail: Option<String>,
32}
33
34/// An enum definition
35#[derive(Debug, Clone)]
36pub struct EnumDef {
37    pub name: String,
38    pub variants: Vec<String>,
39}
40
41/// A struct definition
42#[derive(Debug, Clone)]
43pub struct StructDef {
44    pub name: String,
45    pub fields: Vec<StructField>,
46}
47
48/// A field in a struct
49#[derive(Debug, Clone)]
50pub struct StructField {
51    pub name: String,
52    pub ty: SolanaType,
53}
54
55/// A modifier definition (gets inlined into functions)
56#[derive(Debug, Clone)]
57pub struct ModifierDefinition {
58    pub name: String,
59    pub params: Vec<InstructionParam>,
60    pub body: Vec<Statement>,
61}
62
63/// A mapping definition (becomes PDA-based storage)
64#[derive(Debug, Clone)]
65pub struct MappingDef {
66    pub name: String,
67    pub key_ty: SolanaType,
68    pub value_ty: SolanaType,
69    pub is_public: bool,
70}
71
72/// Program state account
73#[derive(Debug, Clone)]
74pub struct ProgramState {
75    pub fields: Vec<StateField>,
76}
77
78/// A field in the state account
79#[derive(Debug, Clone)]
80pub struct StateField {
81    pub name: String,
82    pub ty: SolanaType,
83    pub is_public: bool,
84}
85
86/// An instruction (function) in the program
87#[derive(Debug, Clone)]
88pub struct Instruction {
89    pub name: String,
90    pub params: Vec<InstructionParam>,
91    pub returns: Option<SolanaType>,
92    pub body: Vec<Statement>,
93    pub is_public: bool,
94    pub is_view: bool,
95    pub is_payable: bool,
96    pub uses_token_program: bool,
97    pub uses_sol_transfer: bool,
98    pub modifiers: Vec<ModifierCall>,
99    /// Mapping accesses needed for this instruction
100    pub mapping_accesses: Vec<MappingAccess>,
101    /// If true, this instruction closes the state account (selfdestruct)
102    pub closes_state: bool,
103}
104
105/// A mapping access within an instruction
106#[derive(Debug, Clone)]
107pub struct MappingAccess {
108    /// Name of the mapping being accessed
109    pub mapping_name: String,
110    /// The key expression(s) used to access the mapping (multiple for nested mappings)
111    pub key_exprs: Vec<Expression>,
112    /// Whether this is a write access (needs init_if_needed)
113    pub is_write: bool,
114    /// Whether this access should close the PDA (delete operation)
115    pub should_close: bool,
116    /// Generated account name for this access
117    pub account_name: String,
118}
119
120/// A parameter for an instruction
121#[derive(Debug, Clone)]
122pub struct InstructionParam {
123    pub name: String,
124    pub ty: SolanaType,
125}
126
127/// A modifier invocation
128#[derive(Debug, Clone)]
129pub struct ModifierCall {
130    pub name: String,
131    pub args: Vec<Expression>,
132}
133
134/// An event
135#[derive(Debug, Clone)]
136pub struct Event {
137    pub name: String,
138    pub fields: Vec<EventField>,
139}
140
141/// An event field
142#[derive(Debug, Clone)]
143pub struct EventField {
144    pub name: String,
145    pub ty: SolanaType,
146    pub indexed: bool,
147}
148
149/// A custom error
150#[derive(Debug, Clone)]
151pub struct ProgramError {
152    pub name: String,
153    pub fields: Vec<ErrorField>,
154}
155
156/// An error field
157#[derive(Debug, Clone)]
158pub struct ErrorField {
159    pub name: String,
160    pub ty: SolanaType,
161}
162
163/// Types in Solana IR
164#[derive(Debug, Clone)]
165pub enum SolanaType {
166    U8,
167    U16,
168    U32,
169    U64,
170    U128,
171    I8,
172    I16,
173    I32,
174    I64,
175    I128,
176    Bool,
177    Pubkey, // Solana's address type
178    Signer, // A required signer account
179    String,
180    Bytes,
181    FixedBytes(usize), // bytes1 through bytes32 -> [u8; N]
182    Array(Box<SolanaType>, usize),
183    Vec(Box<SolanaType>),
184    Option(Box<SolanaType>),
185    // For mappings, we use PDA-based storage which requires special handling
186    Mapping(Box<SolanaType>, Box<SolanaType>),
187    // User-defined types
188    Custom(String),
189}
190
191/// Statements in IR
192#[derive(Debug, Clone)]
193pub enum Statement {
194    VarDecl {
195        name: String,
196        ty: SolanaType,
197        value: Option<Expression>,
198    },
199    Assign {
200        target: Expression,
201        value: Expression,
202    },
203    If {
204        condition: Expression,
205        then_block: Vec<Statement>,
206        else_block: Option<Vec<Statement>>,
207    },
208    While {
209        condition: Expression,
210        body: Vec<Statement>,
211    },
212    For {
213        init: Option<Box<Statement>>,
214        condition: Option<Expression>,
215        update: Option<Expression>,
216        body: Vec<Statement>,
217    },
218    Return(Option<Expression>),
219    Emit {
220        event: String,
221        args: Vec<Expression>,
222    },
223    Require {
224        condition: Expression,
225        message: Option<String>,
226    },
227    /// Revert with a custom error: revert ErrorName(args)
228    RevertWithError {
229        error_name: String,
230        args: Vec<Expression>,
231    },
232    /// Delete statement: reset target to default value
233    Delete(Expression),
234    /// Selfdestruct: close the state account and send rent to recipient
235    Selfdestruct {
236        recipient: Expression,
237    },
238    Expr(Expression),
239    /// Placeholder for modifier body insertion (`_` in Solidity)
240    Placeholder,
241}
242
243/// Expressions in IR
244#[derive(Debug, Clone)]
245pub enum Expression {
246    Literal(Literal),
247    Var(String),
248    StateAccess(String), // Access to state field
249    /// Mapping access: `mapping_name[key1][key2]...` → `ctx.accounts.{account_name}.value`
250    MappingAccess {
251        mapping_name: String,
252        /// All keys in order (outer to inner)
253        keys: Vec<Expression>,
254        /// Generated account name for this access point
255        account_name: String,
256    },
257    MsgSender,      // msg.sender → ctx.accounts.signer
258    MsgValue,       // msg.value (not directly supported in Solana)
259    BlockTimestamp, // block.timestamp → Clock::get()
260    // Solana Clock sysvar fields
261    ClockSlot,          // clock.slot → Clock::get()?.slot
262    ClockEpoch,         // clock.epoch → Clock::get()?.epoch
263    ClockUnixTimestamp, // clock.unix_timestamp → Clock::get()?.unix_timestamp
264    // Solana Rent sysvar methods
265    RentMinimumBalance {
266        data_len: Box<Expression>,
267    },
268    RentIsExempt {
269        lamports: Box<Expression>,
270        data_len: Box<Expression>,
271    },
272    Binary {
273        op: BinaryOp,
274        left: Box<Expression>,
275        right: Box<Expression>,
276    },
277    Unary {
278        op: UnaryOp,
279        expr: Box<Expression>,
280    },
281    Call {
282        func: String,
283        args: Vec<Expression>,
284    },
285    MethodCall {
286        receiver: Box<Expression>,
287        method: String,
288        args: Vec<Expression>,
289    },
290    /// Interface cast for CPI: IERC20(programId) -> allows calling methods on external programs
291    InterfaceCast {
292        /// The interface name (e.g., "IERC20")
293        interface_name: String,
294        /// The program ID to call
295        program_id: Box<Expression>,
296    },
297    /// Cross-Program Invocation call
298    CpiCall {
299        /// The program to call (expression evaluating to program ID)
300        program: Box<Expression>,
301        /// Interface/program name for generating the right instruction
302        interface_name: String,
303        /// Method name to call
304        method: String,
305        /// Arguments to the CPI call
306        args: Vec<Expression>,
307    },
308    /// SPL Token transfer CPI
309    TokenTransfer {
310        /// from account
311        from: Box<Expression>,
312        /// to account
313        to: Box<Expression>,
314        /// authority
315        authority: Box<Expression>,
316        /// amount
317        amount: Box<Expression>,
318    },
319    /// SPL Token mint CPI
320    TokenMint {
321        /// mint account
322        mint: Box<Expression>,
323        /// to account
324        to: Box<Expression>,
325        /// authority
326        authority: Box<Expression>,
327        /// amount
328        amount: Box<Expression>,
329    },
330    /// SPL Token burn CPI
331    TokenBurn {
332        /// from account
333        from: Box<Expression>,
334        /// mint account
335        mint: Box<Expression>,
336        /// authority
337        authority: Box<Expression>,
338        /// amount
339        amount: Box<Expression>,
340    },
341    /// Direct SOL transfer via system_program::transfer
342    SolTransfer {
343        /// to account (Pubkey)
344        to: Box<Expression>,
345        /// amount in lamports
346        amount: Box<Expression>,
347    },
348    /// Get Associated Token Address
349    GetATA {
350        /// owner/wallet address
351        owner: Box<Expression>,
352        /// token mint address
353        mint: Box<Expression>,
354    },
355    Index {
356        expr: Box<Expression>,
357        index: Box<Expression>,
358    },
359    Field {
360        expr: Box<Expression>,
361        field: String,
362    },
363    Ternary {
364        condition: Box<Expression>,
365        then_expr: Box<Expression>,
366        else_expr: Box<Expression>,
367    },
368    /// Assert expression: assert(condition, "message")
369    Assert {
370        condition: Box<Expression>,
371        message: Option<String>,
372    },
373    /// Assert equality: assertEq(left, right, "message")
374    AssertEq {
375        left: Box<Expression>,
376        right: Box<Expression>,
377        message: Option<String>,
378    },
379    /// Assert not equal: assertNe(left, right, "message")
380    AssertNe {
381        left: Box<Expression>,
382        right: Box<Expression>,
383        message: Option<String>,
384    },
385    /// Assert greater than: assertGt(left, right, "message")
386    AssertGt {
387        left: Box<Expression>,
388        right: Box<Expression>,
389        message: Option<String>,
390    },
391    /// Assert greater or equal: assertGe(left, right, "message")
392    AssertGe {
393        left: Box<Expression>,
394        right: Box<Expression>,
395        message: Option<String>,
396    },
397    /// Assert less than: assertLt(left, right, "message")
398    AssertLt {
399        left: Box<Expression>,
400        right: Box<Expression>,
401        message: Option<String>,
402    },
403    /// Assert less or equal: assertLe(left, right, "message")
404    AssertLe {
405        left: Box<Expression>,
406        right: Box<Expression>,
407        message: Option<String>,
408    },
409}
410
411/// Literal values
412#[derive(Debug, Clone)]
413pub enum Literal {
414    Bool(bool),
415    Int(i128),
416    Uint(u128),
417    String(String),
418    Pubkey(String),   // Base58 encoded
419    ZeroAddress,      // address(0) - the default/null address
420    ZeroBytes(usize), // bytes32(0) etc. - zero-filled fixed bytes
421}
422
423/// Binary operators
424#[derive(Debug, Clone, Copy)]
425pub enum BinaryOp {
426    Add,
427    Sub,
428    Mul,
429    Div,
430    Rem,
431    Eq,
432    Ne,
433    Lt,
434    Le,
435    Gt,
436    Ge,
437    And,
438    Or,
439    BitAnd,
440    BitOr,
441    BitXor,
442    Shl,
443    Shr,
444}
445
446/// Unary operators
447#[derive(Debug, Clone, Copy)]
448pub enum UnaryOp {
449    Neg,
450    Not,
451    BitNot,
452}
453
454/// Lower the AST to Solana IR
455pub fn lower_to_ir(program: &ast::Program) -> Result<Vec<SolanaProgram>, CodegenError> {
456    let mut programs = Vec::new();
457    let mut events = Vec::new();
458    let mut errors = Vec::new();
459    let mut structs = Vec::new();
460    let mut enums = Vec::new();
461
462    // First pass: collect events, errors, structs, enums, and interfaces
463    let mut interface_names: std::collections::HashSet<String> = std::collections::HashSet::new();
464    for item in &program.items {
465        match item {
466            ast::Item::Event(e) => {
467                events.push(lower_event(e)?);
468            }
469            ast::Item::Error(e) => {
470                errors.push(lower_error(e)?);
471            }
472            ast::Item::Struct(s) => {
473                structs.push(lower_struct(s)?);
474            }
475            ast::Item::Enum(e) => {
476                enums.push(lower_enum(e));
477            }
478            ast::Item::Interface(i) => {
479                interface_names.insert(i.name.name.to_string());
480            }
481            ast::Item::Contract(c) => {
482                // Also collect events, errors, structs, and enums defined inside contracts
483                for member in &c.members {
484                    match member {
485                        ast::ContractMember::Event(e) => {
486                            events.push(lower_event(e)?);
487                        }
488                        ast::ContractMember::Error(e) => {
489                            errors.push(lower_error(e)?);
490                        }
491                        ast::ContractMember::Struct(s) => {
492                            structs.push(lower_struct(s)?);
493                        }
494                        ast::ContractMember::Enum(e) => {
495                            enums.push(lower_enum(e));
496                        }
497                        _ => {}
498                    }
499                }
500            }
501            _ => {}
502        }
503    }
504
505    // Collect all contracts for inheritance resolution
506    let contracts: std::collections::HashMap<String, &ast::ContractDef> = program
507        .items
508        .iter()
509        .filter_map(|item| {
510            if let ast::Item::Contract(c) = item {
511                Some((c.name.name.to_string(), c))
512            } else {
513                None
514            }
515        })
516        .collect();
517
518    // Second pass: process contracts (skip abstract contracts)
519    for item in &program.items {
520        if let ast::Item::Contract(contract) = item {
521            // Skip abstract contracts - they can't be deployed
522            if contract.is_abstract {
523                continue;
524            }
525            let prog = lower_contract(
526                contract,
527                &events,
528                &errors,
529                &structs,
530                &enums,
531                &contracts,
532                &interface_names,
533            )?;
534            programs.push(prog);
535        }
536    }
537
538    Ok(programs)
539}
540
541/// Context for lowering expressions, tracking state fields and mappings
542struct LoweringContext {
543    state_fields: std::collections::HashSet<String>,
544    mapping_names: std::collections::HashSet<String>,
545    mappings: Vec<MappingDef>,
546    interface_names: std::collections::HashSet<String>,
547}
548
549impl LoweringContext {
550    fn new() -> Self {
551        Self {
552            state_fields: std::collections::HashSet::new(),
553            mapping_names: std::collections::HashSet::new(),
554            mappings: Vec::new(),
555            interface_names: std::collections::HashSet::new(),
556        }
557    }
558
559    fn is_state_field(&self, name: &str) -> bool {
560        self.state_fields.contains(name)
561    }
562
563    fn is_interface(&self, name: &str) -> bool {
564        self.interface_names.contains(name)
565    }
566
567    fn is_mapping(&self, name: &str) -> bool {
568        self.mapping_names.contains(name)
569    }
570}
571
572/// Collector for mapping accesses within a function
573struct MappingAccessCollector {
574    accesses: Vec<MappingAccess>,
575    counter: usize,
576    uses_token_program: bool,
577    uses_sol_transfer: bool,
578}
579
580impl MappingAccessCollector {
581    fn new() -> Self {
582        Self {
583            accesses: Vec::new(),
584            counter: 0,
585            uses_token_program: false,
586            uses_sol_transfer: false,
587        }
588    }
589
590    fn mark_uses_token_program(&mut self) {
591        self.uses_token_program = true;
592    }
593
594    fn mark_uses_sol_transfer(&mut self) {
595        self.uses_sol_transfer = true;
596    }
597
598    /// Record a mapping access and return a unique account name
599    fn record_access(
600        &mut self,
601        mapping_name: &str,
602        keys: Vec<Expression>,
603        is_write: bool,
604        should_close: bool,
605    ) -> String {
606        // Generate unique account name based on mapping name and counter
607        let account_name = format!("{}_entry_{}", to_snake_case(mapping_name), self.counter);
608        self.counter += 1;
609
610        self.accesses.push(MappingAccess {
611            mapping_name: mapping_name.to_string(),
612            key_exprs: keys,
613            is_write,
614            should_close,
615            account_name: account_name.clone(),
616        });
617
618        account_name
619    }
620}
621
622fn to_snake_case(s: &str) -> String {
623    let mut result = String::new();
624    let mut prev_upper = false;
625
626    for (i, c) in s.chars().enumerate() {
627        if c.is_uppercase() {
628            if i > 0 && !prev_upper {
629                result.push('_');
630            }
631            result.push(c.to_lowercase().next().unwrap());
632            prev_upper = true;
633        } else {
634            result.push(c);
635            prev_upper = false;
636        }
637    }
638
639    result
640}
641
642fn lower_contract(
643    contract: &ast::ContractDef,
644    events: &[Event],
645    errors: &[ProgramError],
646    structs: &[StructDef],
647    enums: &[EnumDef],
648    all_contracts: &std::collections::HashMap<String, &ast::ContractDef>,
649    interface_names: &std::collections::HashSet<String>,
650) -> Result<SolanaProgram, CodegenError> {
651    let name = contract.name.name.to_string();
652
653    // Collect all members including inherited ones
654    // Order: base contracts first (in declaration order), then this contract
655    let mut all_members: Vec<&ast::ContractMember> = Vec::new();
656
657    // Process base contracts (inheritance)
658    for base in &contract.bases {
659        let base_name = base.segments.first().map(|s| s.name.as_str()).unwrap_or("");
660        if let Some(base_contract) = all_contracts.get(base_name) {
661            // Add base contract members (excluding constructors - those are handled separately)
662            for member in &base_contract.members {
663                if !matches!(member, ast::ContractMember::Constructor(_)) {
664                    all_members.push(member);
665                }
666            }
667        }
668    }
669
670    // Add this contract's members (may override inherited ones)
671    for member in &contract.members {
672        all_members.push(member);
673    }
674
675    // First pass: collect state fields and mappings from all members
676    let mut fields = Vec::new();
677    let mut ctx = LoweringContext::new();
678    ctx.interface_names = interface_names.clone();
679    let mut seen_fields = std::collections::HashSet::new();
680
681    for member in &all_members {
682        if let ast::ContractMember::StateVar(var) = member {
683            let field_name = var.name.name.to_string();
684
685            // Skip if already defined (child overrides parent)
686            if seen_fields.contains(&field_name) {
687                continue;
688            }
689            seen_fields.insert(field_name.clone());
690
691            let field_ty = lower_type(&var.ty)?;
692            let is_public = matches!(var.visibility, Some(Visibility::Public));
693
694            // Check if this is a mapping type
695            if let SolanaType::Mapping(key_ty, value_ty) = field_ty {
696                ctx.mapping_names.insert(field_name.clone());
697                ctx.mappings.push(MappingDef {
698                    name: field_name,
699                    key_ty: (*key_ty).clone(),
700                    value_ty: (*value_ty).clone(),
701                    is_public,
702                });
703            } else {
704                ctx.state_fields.insert(field_name.clone());
705                fields.push(StateField {
706                    name: field_name,
707                    ty: field_ty,
708                    is_public,
709                });
710            }
711        }
712    }
713
714    // Second pass: collect modifiers and lower functions
715    let mut modifiers = Vec::new();
716    let mut instructions = Vec::new();
717    let mut seen_modifiers = std::collections::HashSet::new();
718    let mut seen_functions = std::collections::HashSet::new();
719
720    // First, collect all modifiers (child overrides parent)
721    for member in all_members.iter().rev() {
722        if let ast::ContractMember::Modifier(modifier) = member {
723            let mod_name = modifier.name.name.to_string();
724            if !seen_modifiers.contains(&mod_name) {
725                seen_modifiers.insert(mod_name);
726                modifiers.push(lower_modifier(modifier, &ctx)?);
727            }
728        }
729    }
730
731    // Then lower functions (child overrides parent)
732    for member in all_members.iter().rev() {
733        match member {
734            ast::ContractMember::StateVar(_) => {
735                // Already handled
736            }
737            ast::ContractMember::Function(func) => {
738                let func_name = func.name.name.to_string();
739                // Skip abstract functions (those without a body)
740                if func.body.is_some() && !seen_functions.contains(&func_name) {
741                    seen_functions.insert(func_name);
742                    instructions.push(lower_function(func, &ctx)?);
743                }
744            }
745            ast::ContractMember::Constructor(_) => {
746                // Handle constructors separately - only use the child's constructor
747            }
748            ast::ContractMember::Modifier(_) => {
749                // Already handled above
750            }
751            ast::ContractMember::Event(_) | ast::ContractMember::Error(_) => {
752                // Events and errors are handled at the top level during lowering
753            }
754            ast::ContractMember::Struct(_) | ast::ContractMember::Enum(_) => {
755                // Structs and enums are handled at the top level during lowering
756            }
757        }
758    }
759
760    // Handle constructor from this contract only
761    for member in &contract.members {
762        if let ast::ContractMember::Constructor(ctor) = member {
763            instructions.insert(0, lower_constructor(ctor, &ctx)?);
764            break;
765        }
766    }
767
768    // Extract test functions (functions with #[test] attribute)
769    let mut tests = Vec::new();
770    for member in all_members.iter() {
771        if let ast::ContractMember::Function(func) = member {
772            if has_test_attribute(&func.attributes) {
773                tests.push(lower_test_function(func, &ctx)?);
774            }
775        }
776    }
777
778    Ok(SolanaProgram {
779        name,
780        state: ProgramState { fields },
781        mappings: ctx.mappings,
782        modifiers,
783        instructions,
784        events: events.to_vec(),
785        errors: errors.to_vec(),
786        structs: structs.to_vec(),
787        enums: enums.to_vec(),
788        tests,
789    })
790}
791
792/// Check if a function has the #[test] attribute
793fn has_test_attribute(attrs: &[ast::Attribute]) -> bool {
794    attrs.iter().any(|a| a.name.name.as_str() == "test")
795}
796
797/// Get the expected failure message from #[should_fail("...")] attribute
798fn get_should_fail_message(attrs: &[ast::Attribute]) -> Option<String> {
799    for attr in attrs {
800        if attr.name.name.as_str() == "should_fail" {
801            if let Some(arg) = attr.args.first() {
802                if let ast::AttributeValue::Literal(ast::Literal::String(s, _)) = &arg.value {
803                    return Some(s.to_string());
804                }
805            }
806            // #[should_fail] without message
807            return Some(String::new());
808        }
809    }
810    None
811}
812
813/// Lower a test function
814fn lower_test_function(
815    func: &ast::FnDef,
816    ctx: &LoweringContext,
817) -> Result<TestFunction, CodegenError> {
818    let name = func.name.name.to_string();
819    let mut collector = MappingAccessCollector::new();
820
821    let body = if let Some(block) = &func.body {
822        lower_block(block, ctx, &mut collector)?
823    } else {
824        Vec::new()
825    };
826
827    let should_fail = get_should_fail_message(&func.attributes);
828
829    Ok(TestFunction {
830        name,
831        body,
832        should_fail,
833    })
834}
835
836fn lower_function(func: &ast::FnDef, ctx: &LoweringContext) -> Result<Instruction, CodegenError> {
837    let name = func.name.name.to_string();
838    let mut collector = MappingAccessCollector::new();
839
840    let params: Vec<InstructionParam> = func
841        .params
842        .iter()
843        .map(|p| {
844            Ok(InstructionParam {
845                name: p.name.name.to_string(),
846                ty: lower_type(&p.ty)?,
847            })
848        })
849        .collect::<Result<Vec<_>, CodegenError>>()?;
850
851    let returns = if func.return_params.is_empty() {
852        None
853    } else if func.return_params.len() == 1 {
854        Some(lower_type(&func.return_params[0].ty)?)
855    } else {
856        return Err(CodegenError::UnsupportedFeature(
857            "Multiple return values".to_string(),
858        ));
859    };
860
861    let is_public = matches!(
862        func.visibility,
863        Some(Visibility::Public) | Some(Visibility::External)
864    );
865    let is_view = func
866        .state_mutability
867        .iter()
868        .any(|m| matches!(m, StateMutability::View | StateMutability::Pure));
869    let is_payable = func
870        .state_mutability
871        .iter()
872        .any(|m| matches!(m, StateMutability::Payable));
873
874    let mut modifiers = Vec::new();
875    for m in &func.modifiers {
876        let args: Vec<Expression> = m
877            .args
878            .iter()
879            .map(|a| lower_expr(&a.value, ctx, &mut collector))
880            .collect::<Result<Vec<_>, _>>()?;
881        modifiers.push(ModifierCall {
882            name: m.name.name.to_string(),
883            args,
884        });
885    }
886
887    // Safe to unwrap since we only call lower_function for functions with bodies
888    let body = lower_block(func.body.as_ref().unwrap(), ctx, &mut collector)?;
889
890    // Check if body contains selfdestruct
891    let closes_state = body_contains_selfdestruct(&body);
892
893    Ok(Instruction {
894        name,
895        params,
896        returns,
897        body,
898        is_public,
899        is_view,
900        is_payable,
901        uses_token_program: collector.uses_token_program,
902        uses_sol_transfer: collector.uses_sol_transfer,
903        modifiers,
904        mapping_accesses: collector.accesses,
905        closes_state,
906    })
907}
908
909/// Check if a statement list contains a Selfdestruct statement
910fn body_contains_selfdestruct(stmts: &[Statement]) -> bool {
911    for stmt in stmts {
912        match stmt {
913            Statement::Selfdestruct { .. } => return true,
914            Statement::If {
915                then_block,
916                else_block,
917                ..
918            } => {
919                if body_contains_selfdestruct(then_block) {
920                    return true;
921                }
922                if let Some(else_stmts) = else_block {
923                    if body_contains_selfdestruct(else_stmts) {
924                        return true;
925                    }
926                }
927            }
928            Statement::While { body, .. } => {
929                if body_contains_selfdestruct(body) {
930                    return true;
931                }
932            }
933            Statement::For { body, .. } => {
934                if body_contains_selfdestruct(body) {
935                    return true;
936                }
937            }
938            _ => {}
939        }
940    }
941    false
942}
943
944fn lower_constructor(
945    ctor: &ast::ConstructorDef,
946    ctx: &LoweringContext,
947) -> Result<Instruction, CodegenError> {
948    let mut collector = MappingAccessCollector::new();
949
950    let params: Vec<InstructionParam> = ctor
951        .params
952        .iter()
953        .map(|p| {
954            Ok(InstructionParam {
955                name: p.name.name.to_string(),
956                ty: lower_type(&p.ty)?,
957            })
958        })
959        .collect::<Result<Vec<_>, CodegenError>>()?;
960
961    let body = lower_block(&ctor.body, ctx, &mut collector)?;
962
963    Ok(Instruction {
964        name: "initialize".to_string(),
965        params,
966        returns: None,
967        body,
968        is_public: true,
969        is_view: false,
970        is_payable: ctor.modifiers.iter().any(|m| m.name.name == "payable"),
971        uses_token_program: collector.uses_token_program,
972        uses_sol_transfer: collector.uses_sol_transfer,
973        modifiers: Vec::new(),
974        mapping_accesses: collector.accesses,
975        closes_state: false, // Constructor never closes state
976    })
977}
978
979fn lower_modifier(
980    modifier: &ast::ModifierDef,
981    ctx: &LoweringContext,
982) -> Result<ModifierDefinition, CodegenError> {
983    let mut collector = MappingAccessCollector::new();
984
985    let params: Vec<InstructionParam> = modifier
986        .params
987        .iter()
988        .map(|p| {
989            Ok(InstructionParam {
990                name: p.name.name.to_string(),
991                ty: lower_type(&p.ty)?,
992            })
993        })
994        .collect::<Result<Vec<_>, CodegenError>>()?;
995
996    let body = lower_block(&modifier.body, ctx, &mut collector)?;
997
998    Ok(ModifierDefinition {
999        name: modifier.name.name.to_string(),
1000        params,
1001        body,
1002    })
1003}
1004
1005fn lower_event(event: &ast::EventDef) -> Result<Event, CodegenError> {
1006    let fields: Vec<EventField> = event
1007        .params
1008        .iter()
1009        .map(|p| {
1010            Ok(EventField {
1011                name: p.name.name.to_string(),
1012                ty: lower_type(&p.ty)?,
1013                indexed: p.indexed,
1014            })
1015        })
1016        .collect::<Result<Vec<_>, CodegenError>>()?;
1017
1018    Ok(Event {
1019        name: event.name.name.to_string(),
1020        fields,
1021    })
1022}
1023
1024fn lower_error(error: &ast::ErrorDef) -> Result<ProgramError, CodegenError> {
1025    let fields: Vec<ErrorField> = error
1026        .params
1027        .iter()
1028        .map(|p| {
1029            Ok(ErrorField {
1030                name: p.name.name.to_string(),
1031                ty: lower_type(&p.ty)?,
1032            })
1033        })
1034        .collect::<Result<Vec<_>, CodegenError>>()?;
1035
1036    Ok(ProgramError {
1037        name: error.name.name.to_string(),
1038        fields,
1039    })
1040}
1041
1042fn lower_struct(s: &ast::StructDef) -> Result<StructDef, CodegenError> {
1043    let fields: Vec<StructField> = s
1044        .fields
1045        .iter()
1046        .map(|f| {
1047            Ok(StructField {
1048                name: f.name.name.to_string(),
1049                ty: lower_type(&f.ty)?,
1050            })
1051        })
1052        .collect::<Result<Vec<_>, CodegenError>>()?;
1053
1054    Ok(StructDef {
1055        name: s.name.name.to_string(),
1056        fields,
1057    })
1058}
1059
1060fn lower_enum(e: &ast::EnumDef) -> EnumDef {
1061    EnumDef {
1062        name: e.name.name.to_string(),
1063        variants: e.variants.iter().map(|v| v.name.name.to_string()).collect(),
1064    }
1065}
1066
1067fn lower_type(ty: &ast::TypeExpr) -> Result<SolanaType, CodegenError> {
1068    match ty {
1069        ast::TypeExpr::Path(path) => {
1070            let name = path.name();
1071            match name.as_str() {
1072                "uint8" | "u8" => Ok(SolanaType::U8),
1073                "uint16" | "u16" => Ok(SolanaType::U16),
1074                "uint32" | "u32" => Ok(SolanaType::U32),
1075                "uint64" | "u64" => Ok(SolanaType::U64),
1076                "uint128" | "u128" => Ok(SolanaType::U128),
1077                "uint256" | "uint" => Ok(SolanaType::U128), // Solana doesn't have u256 natively
1078                "int8" | "i8" => Ok(SolanaType::I8),
1079                "int16" | "i16" => Ok(SolanaType::I16),
1080                "int32" | "i32" => Ok(SolanaType::I32),
1081                "int64" | "i64" => Ok(SolanaType::I64),
1082                "int128" | "i128" => Ok(SolanaType::I128),
1083                "int256" | "int" => Ok(SolanaType::I128),
1084                "bool" => Ok(SolanaType::Bool),
1085                "address" => Ok(SolanaType::Pubkey),
1086                "signer" => Ok(SolanaType::Signer),
1087                "string" => Ok(SolanaType::String),
1088                "bytes" => Ok(SolanaType::Bytes),
1089                // Fixed-size bytes: bytes1 through bytes32
1090                s if s.starts_with("bytes") => {
1091                    if let Ok(n) = s[5..].parse::<usize>() {
1092                        if (1..=32).contains(&n) {
1093                            Ok(SolanaType::FixedBytes(n))
1094                        } else {
1095                            Err(CodegenError::UnsupportedFeature(format!(
1096                                "Invalid bytes size: {}",
1097                                n
1098                            )))
1099                        }
1100                    } else {
1101                        Ok(SolanaType::Custom(s.to_string()))
1102                    }
1103                }
1104                other => Ok(SolanaType::Custom(other.to_string())),
1105            }
1106        }
1107        ast::TypeExpr::Array(arr) => {
1108            let elem = lower_type(&ast::TypeExpr::Path(arr.element.clone()))?;
1109            if arr.sizes.len() != 1 {
1110                return Err(CodegenError::UnsupportedFeature(
1111                    "Multi-dimensional arrays".to_string(),
1112                ));
1113            }
1114            match &arr.sizes[0] {
1115                Some(size) => Ok(SolanaType::Array(Box::new(elem), *size as usize)),
1116                None => Ok(SolanaType::Vec(Box::new(elem))),
1117            }
1118        }
1119        ast::TypeExpr::Mapping(mapping) => {
1120            let key = lower_type(&mapping.key)?;
1121            let value = lower_type(&mapping.value)?;
1122            Ok(SolanaType::Mapping(Box::new(key), Box::new(value)))
1123        }
1124        ast::TypeExpr::Tuple(_) => Err(CodegenError::UnsupportedFeature("Tuple types".to_string())),
1125    }
1126}
1127
1128fn lower_block(
1129    block: &ast::Block,
1130    ctx: &LoweringContext,
1131    collector: &mut MappingAccessCollector,
1132) -> Result<Vec<Statement>, CodegenError> {
1133    block
1134        .stmts
1135        .iter()
1136        .map(|s| lower_stmt(s, ctx, collector))
1137        .collect()
1138}
1139
1140fn lower_stmt(
1141    stmt: &ast::Stmt,
1142    ctx: &LoweringContext,
1143    collector: &mut MappingAccessCollector,
1144) -> Result<Statement, CodegenError> {
1145    match stmt {
1146        ast::Stmt::VarDecl(v) => Ok(Statement::VarDecl {
1147            name: v.name.name.to_string(),
1148            ty: lower_type(&v.ty)?,
1149            value: v
1150                .initializer
1151                .as_ref()
1152                .map(|e| lower_expr(e, ctx, collector))
1153                .transpose()?,
1154        }),
1155        ast::Stmt::Return(r) => Ok(Statement::Return(
1156            r.value
1157                .as_ref()
1158                .map(|e| lower_expr(e, ctx, collector))
1159                .transpose()?,
1160        )),
1161        ast::Stmt::If(i) => lower_if_stmt(i, ctx, collector),
1162        ast::Stmt::While(w) => Ok(Statement::While {
1163            condition: lower_expr(&w.condition, ctx, collector)?,
1164            body: lower_block(&w.body, ctx, collector)?,
1165        }),
1166        ast::Stmt::For(f) => lower_for_stmt(f, ctx, collector),
1167        ast::Stmt::Emit(e) => Ok(Statement::Emit {
1168            event: e.event.name.to_string(),
1169            args: e
1170                .args
1171                .iter()
1172                .map(|a| lower_expr(&a.value, ctx, collector))
1173                .collect::<Result<Vec<_>, _>>()?,
1174        }),
1175        ast::Stmt::Require(r) => Ok(Statement::Require {
1176            condition: lower_expr(&r.condition, ctx, collector)?,
1177            message: r.message.as_ref().map(|s| s.to_string()),
1178        }),
1179        ast::Stmt::Revert(r) => match &r.kind {
1180            ast::RevertKind::Message(msg) => Ok(Statement::Require {
1181                condition: Expression::Literal(Literal::Bool(false)),
1182                message: msg
1183                    .as_ref()
1184                    .map(|s| s.to_string())
1185                    .or_else(|| Some("Reverted".to_string())),
1186            }),
1187            ast::RevertKind::Error { name, args } => {
1188                let lowered_args: Vec<Expression> = args
1189                    .iter()
1190                    .map(|a| lower_expr(&a.value, ctx, collector))
1191                    .collect::<Result<Vec<_>, _>>()?;
1192                Ok(Statement::RevertWithError {
1193                    error_name: name.name.to_string(),
1194                    args: lowered_args,
1195                })
1196            }
1197        },
1198        ast::Stmt::Delete(d) => {
1199            let target = lower_expr(&d.target, ctx, collector)?;
1200            // If we're deleting a mapping access, mark it as should_close
1201            if let Expression::MappingAccess { account_name, .. } = &target {
1202                // Find and update the mapping access to mark it for closing
1203                for access in &mut collector.accesses {
1204                    if &access.account_name == account_name {
1205                        access.should_close = true;
1206                        break;
1207                    }
1208                }
1209            }
1210            Ok(Statement::Delete(target))
1211        }
1212        ast::Stmt::Selfdestruct(s) => Ok(Statement::Selfdestruct {
1213            recipient: lower_expr(&s.recipient, ctx, collector)?,
1214        }),
1215        ast::Stmt::Expr(e) => Ok(Statement::Expr(lower_expr(&e.expr, ctx, collector)?)),
1216        ast::Stmt::Placeholder(_) => Ok(Statement::Placeholder),
1217    }
1218}
1219
1220fn lower_if_stmt(
1221    i: &ast::IfStmt,
1222    ctx: &LoweringContext,
1223    collector: &mut MappingAccessCollector,
1224) -> Result<Statement, CodegenError> {
1225    let condition = lower_expr(&i.condition, ctx, collector)?;
1226    let then_block = lower_block(&i.then_block, ctx, collector)?;
1227    let else_block = match &i.else_branch {
1228        Some(ast::ElseBranch::Else(block)) => Some(lower_block(block, ctx, collector)?),
1229        Some(ast::ElseBranch::ElseIf(elif)) => Some(vec![lower_if_stmt(elif, ctx, collector)?]),
1230        None => None,
1231    };
1232
1233    Ok(Statement::If {
1234        condition,
1235        then_block,
1236        else_block,
1237    })
1238}
1239
1240fn lower_for_stmt(
1241    f: &ast::ForStmt,
1242    ctx: &LoweringContext,
1243    collector: &mut MappingAccessCollector,
1244) -> Result<Statement, CodegenError> {
1245    let init = match &f.init {
1246        Some(ast::ForInit::VarDecl(v)) => Some(Box::new(Statement::VarDecl {
1247            name: v.name.name.to_string(),
1248            ty: lower_type(&v.ty)?,
1249            value: v
1250                .initializer
1251                .as_ref()
1252                .map(|e| lower_expr(e, ctx, collector))
1253                .transpose()?,
1254        })),
1255        Some(ast::ForInit::Expr(e)) => {
1256            Some(Box::new(Statement::Expr(lower_expr(e, ctx, collector)?)))
1257        }
1258        None => None,
1259    };
1260
1261    Ok(Statement::For {
1262        init,
1263        condition: f
1264            .condition
1265            .as_ref()
1266            .map(|e| lower_expr(e, ctx, collector))
1267            .transpose()?,
1268        update: f
1269            .update
1270            .as_ref()
1271            .map(|e| lower_expr(e, ctx, collector))
1272            .transpose()?,
1273        body: lower_block(&f.body, ctx, collector)?,
1274    })
1275}
1276
1277/// Extract mapping access including nested mappings
1278/// Returns (mapping_name, keys) if this is a mapping access, None otherwise
1279fn extract_mapping_access<'a>(
1280    base: &'a ast::Expr,
1281    index: &'a ast::Expr,
1282    ctx: &LoweringContext,
1283) -> Result<Option<(String, Vec<&'a ast::Expr>)>, CodegenError> {
1284    // Check if base is a simple mapping identifier
1285    if let ast::Expr::Ident(ident) = base {
1286        let name = ident.name.to_string();
1287        if ctx.is_mapping(&name) {
1288            return Ok(Some((name, vec![index])));
1289        }
1290    }
1291
1292    // Check if base is another index expression (nested mapping)
1293    if let ast::Expr::Index(inner) = base {
1294        if let Some((mapping_name, mut keys)) =
1295            extract_mapping_access(&inner.expr, &inner.index, ctx)?
1296        {
1297            // Add the outer key
1298            keys.push(index);
1299            return Ok(Some((mapping_name, keys)));
1300        }
1301    }
1302
1303    Ok(None)
1304}
1305
1306fn lower_expr(
1307    expr: &ast::Expr,
1308    ctx: &LoweringContext,
1309    collector: &mut MappingAccessCollector,
1310) -> Result<Expression, CodegenError> {
1311    match expr {
1312        ast::Expr::Literal(lit) => lower_literal(lit),
1313        ast::Expr::Ident(ident) => {
1314            let name = ident.name.to_string();
1315            match name.as_str() {
1316                "msg" => Ok(Expression::Var("msg".to_string())),
1317                "block" => Ok(Expression::Var("block".to_string())),
1318                "tx" => Ok(Expression::Var("tx".to_string())),
1319                _ => {
1320                    // Check if this is a state field
1321                    if ctx.is_state_field(&name) {
1322                        Ok(Expression::StateAccess(name))
1323                    } else {
1324                        Ok(Expression::Var(name))
1325                    }
1326                }
1327            }
1328        }
1329        ast::Expr::Binary(b) => Ok(Expression::Binary {
1330            op: lower_binary_op(&b.op),
1331            left: Box::new(lower_expr(&b.left, ctx, collector)?),
1332            right: Box::new(lower_expr(&b.right, ctx, collector)?),
1333        }),
1334        ast::Expr::Unary(u) => Ok(Expression::Unary {
1335            op: lower_unary_op(&u.op),
1336            expr: Box::new(lower_expr(&u.expr, ctx, collector)?),
1337        }),
1338        ast::Expr::Call(c) => {
1339            if let ast::Expr::Ident(ident) = &c.callee {
1340                let func_name = ident.name.to_string();
1341
1342                // Handle assert functions
1343                match func_name.as_str() {
1344                    "assert" => {
1345                        let condition = lower_expr(&c.args[0].value, ctx, collector)?;
1346                        let message = if c.args.len() > 1 {
1347                            if let ast::Expr::Literal(ast::Literal::String(s, _)) = &c.args[1].value
1348                            {
1349                                Some(s.to_string())
1350                            } else {
1351                                None
1352                            }
1353                        } else {
1354                            None
1355                        };
1356                        return Ok(Expression::Assert {
1357                            condition: Box::new(condition),
1358                            message,
1359                        });
1360                    }
1361                    "assertEq" => {
1362                        let left = lower_expr(&c.args[0].value, ctx, collector)?;
1363                        let right = lower_expr(&c.args[1].value, ctx, collector)?;
1364                        let message = if c.args.len() > 2 {
1365                            if let ast::Expr::Literal(ast::Literal::String(s, _)) = &c.args[2].value
1366                            {
1367                                Some(s.to_string())
1368                            } else {
1369                                None
1370                            }
1371                        } else {
1372                            None
1373                        };
1374                        return Ok(Expression::AssertEq {
1375                            left: Box::new(left),
1376                            right: Box::new(right),
1377                            message,
1378                        });
1379                    }
1380                    "assertNe" => {
1381                        let left = lower_expr(&c.args[0].value, ctx, collector)?;
1382                        let right = lower_expr(&c.args[1].value, ctx, collector)?;
1383                        let message = if c.args.len() > 2 {
1384                            if let ast::Expr::Literal(ast::Literal::String(s, _)) = &c.args[2].value
1385                            {
1386                                Some(s.to_string())
1387                            } else {
1388                                None
1389                            }
1390                        } else {
1391                            None
1392                        };
1393                        return Ok(Expression::AssertNe {
1394                            left: Box::new(left),
1395                            right: Box::new(right),
1396                            message,
1397                        });
1398                    }
1399                    "assertGt" => {
1400                        let left = lower_expr(&c.args[0].value, ctx, collector)?;
1401                        let right = lower_expr(&c.args[1].value, ctx, collector)?;
1402                        let message = if c.args.len() > 2 {
1403                            if let ast::Expr::Literal(ast::Literal::String(s, _)) = &c.args[2].value
1404                            {
1405                                Some(s.to_string())
1406                            } else {
1407                                None
1408                            }
1409                        } else {
1410                            None
1411                        };
1412                        return Ok(Expression::AssertGt {
1413                            left: Box::new(left),
1414                            right: Box::new(right),
1415                            message,
1416                        });
1417                    }
1418                    "assertGe" => {
1419                        let left = lower_expr(&c.args[0].value, ctx, collector)?;
1420                        let right = lower_expr(&c.args[1].value, ctx, collector)?;
1421                        let message = if c.args.len() > 2 {
1422                            if let ast::Expr::Literal(ast::Literal::String(s, _)) = &c.args[2].value
1423                            {
1424                                Some(s.to_string())
1425                            } else {
1426                                None
1427                            }
1428                        } else {
1429                            None
1430                        };
1431                        return Ok(Expression::AssertGe {
1432                            left: Box::new(left),
1433                            right: Box::new(right),
1434                            message,
1435                        });
1436                    }
1437                    "assertLt" => {
1438                        let left = lower_expr(&c.args[0].value, ctx, collector)?;
1439                        let right = lower_expr(&c.args[1].value, ctx, collector)?;
1440                        let message = if c.args.len() > 2 {
1441                            if let ast::Expr::Literal(ast::Literal::String(s, _)) = &c.args[2].value
1442                            {
1443                                Some(s.to_string())
1444                            } else {
1445                                None
1446                            }
1447                        } else {
1448                            None
1449                        };
1450                        return Ok(Expression::AssertLt {
1451                            left: Box::new(left),
1452                            right: Box::new(right),
1453                            message,
1454                        });
1455                    }
1456                    "assertLe" => {
1457                        let left = lower_expr(&c.args[0].value, ctx, collector)?;
1458                        let right = lower_expr(&c.args[1].value, ctx, collector)?;
1459                        let message = if c.args.len() > 2 {
1460                            if let ast::Expr::Literal(ast::Literal::String(s, _)) = &c.args[2].value
1461                            {
1462                                Some(s.to_string())
1463                            } else {
1464                                None
1465                            }
1466                        } else {
1467                            None
1468                        };
1469                        return Ok(Expression::AssertLe {
1470                            left: Box::new(left),
1471                            right: Box::new(right),
1472                            message,
1473                        });
1474                    }
1475                    _ => {}
1476                }
1477
1478                // Handle address(0) - the zero/null address pattern
1479                if func_name == "address" && c.args.len() == 1 {
1480                    if let ast::Expr::Literal(ast::Literal::Int(0, _)) = &c.args[0].value {
1481                        return Ok(Expression::Literal(Literal::ZeroAddress));
1482                    }
1483                }
1484
1485                // Handle bytes32(0), bytes4(0), etc. - zero-filled fixed bytes
1486                if func_name.starts_with("bytes") && c.args.len() == 1 {
1487                    if let Ok(n) = func_name[5..].parse::<usize>() {
1488                        if (1..=32).contains(&n) {
1489                            if let ast::Expr::Literal(ast::Literal::Int(0, _)) = &c.args[0].value {
1490                                return Ok(Expression::Literal(Literal::ZeroBytes(n)));
1491                            }
1492                        }
1493                    }
1494                }
1495
1496                // Handle interface type cast: IERC20(address) -> InterfaceCast for CPI
1497                if ctx.is_interface(&func_name) && c.args.len() == 1 {
1498                    let program_id = lower_expr(&c.args[0].value, ctx, collector)?;
1499                    return Ok(Expression::InterfaceCast {
1500                        interface_name: func_name,
1501                        program_id: Box::new(program_id),
1502                    });
1503                }
1504
1505                // Handle transfer(to, amount) - direct SOL transfer
1506                if func_name == "transfer" && c.args.len() == 2 {
1507                    collector.mark_uses_sol_transfer();
1508                    let to = lower_expr(&c.args[0].value, ctx, collector)?;
1509                    let amount = lower_expr(&c.args[1].value, ctx, collector)?;
1510                    return Ok(Expression::SolTransfer {
1511                        to: Box::new(to),
1512                        amount: Box::new(amount),
1513                    });
1514                }
1515
1516                Ok(Expression::Call {
1517                    func: func_name,
1518                    args: c
1519                        .args
1520                        .iter()
1521                        .map(|a| lower_expr(&a.value, ctx, collector))
1522                        .collect::<Result<Vec<_>, _>>()?,
1523                })
1524            } else {
1525                Err(CodegenError::UnsupportedFeature(
1526                    "Complex call expressions".to_string(),
1527                ))
1528            }
1529        }
1530        ast::Expr::MethodCall(m) => {
1531            let receiver = lower_expr(&m.receiver, ctx, collector)?;
1532            let method = m.method.name.to_string();
1533            let args: Vec<Expression> = m
1534                .args
1535                .iter()
1536                .map(|a| lower_expr(&a.value, ctx, collector))
1537                .collect::<Result<Vec<_>, _>>()?;
1538
1539            // Handle CPI calls: IERC20(programId).transfer(...) -> CpiCall
1540            if let Expression::InterfaceCast {
1541                interface_name,
1542                program_id,
1543            } = receiver
1544            {
1545                return Ok(Expression::CpiCall {
1546                    program: program_id,
1547                    interface_name,
1548                    method,
1549                    args,
1550                });
1551            }
1552
1553            // Handle built-in objects
1554            if let Expression::Var(ref name) = receiver {
1555                match (name.as_str(), method.as_str()) {
1556                    ("msg", "sender") => return Ok(Expression::MsgSender),
1557                    ("msg", "value") => return Ok(Expression::MsgValue),
1558                    ("block", "timestamp") => return Ok(Expression::BlockTimestamp),
1559                    // Solana Rent sysvar methods
1560                    ("rent", "minimumBalance") if args.len() == 1 => {
1561                        return Ok(Expression::RentMinimumBalance {
1562                            data_len: Box::new(args[0].clone()),
1563                        });
1564                    }
1565                    ("rent", "isExempt") if args.len() == 2 => {
1566                        return Ok(Expression::RentIsExempt {
1567                            lamports: Box::new(args[0].clone()),
1568                            data_len: Box::new(args[1].clone()),
1569                        });
1570                    }
1571                    // SPL Token operations: token.transfer(from, to, authority, amount)
1572                    ("token", "transfer") if args.len() == 4 => {
1573                        collector.mark_uses_token_program();
1574                        return Ok(Expression::TokenTransfer {
1575                            from: Box::new(args[0].clone()),
1576                            to: Box::new(args[1].clone()),
1577                            authority: Box::new(args[2].clone()),
1578                            amount: Box::new(args[3].clone()),
1579                        });
1580                    }
1581                    // SPL Token mint: token.mint(mint, to, authority, amount)
1582                    ("token", "mint") if args.len() == 4 => {
1583                        collector.mark_uses_token_program();
1584                        return Ok(Expression::TokenMint {
1585                            mint: Box::new(args[0].clone()),
1586                            to: Box::new(args[1].clone()),
1587                            authority: Box::new(args[2].clone()),
1588                            amount: Box::new(args[3].clone()),
1589                        });
1590                    }
1591                    // SPL Token burn: token.burn(from, mint, authority, amount)
1592                    ("token", "burn") if args.len() == 4 => {
1593                        collector.mark_uses_token_program();
1594                        return Ok(Expression::TokenBurn {
1595                            from: Box::new(args[0].clone()),
1596                            mint: Box::new(args[1].clone()),
1597                            authority: Box::new(args[2].clone()),
1598                            amount: Box::new(args[3].clone()),
1599                        });
1600                    }
1601                    // Get Associated Token Address: token.getATA(owner, mint)
1602                    ("token", "getATA") if args.len() == 2 => {
1603                        collector.mark_uses_token_program();
1604                        return Ok(Expression::GetATA {
1605                            owner: Box::new(args[0].clone()),
1606                            mint: Box::new(args[1].clone()),
1607                        });
1608                    }
1609                    _ => {}
1610                }
1611            }
1612
1613            Ok(Expression::MethodCall {
1614                receiver: Box::new(receiver),
1615                method,
1616                args,
1617            })
1618        }
1619        ast::Expr::FieldAccess(f) => {
1620            let lowered_expr = lower_expr(&f.expr, ctx, collector)?;
1621            let field = f.field.name.to_string();
1622
1623            // Handle built-in objects
1624            if let Expression::Var(ref name) = lowered_expr {
1625                match (name.as_str(), field.as_str()) {
1626                    ("msg", "sender") => return Ok(Expression::MsgSender),
1627                    ("msg", "value") => return Ok(Expression::MsgValue),
1628                    ("block", "timestamp") => return Ok(Expression::BlockTimestamp),
1629                    ("block", "number") => return Ok(Expression::BlockTimestamp), // Solana uses slots
1630                    // Solana Clock sysvar fields
1631                    ("clock", "timestamp") => return Ok(Expression::ClockUnixTimestamp),
1632                    ("clock", "unix_timestamp") => return Ok(Expression::ClockUnixTimestamp),
1633                    ("clock", "slot") => return Ok(Expression::ClockSlot),
1634                    ("clock", "epoch") => return Ok(Expression::ClockEpoch),
1635                    _ => {}
1636                }
1637            }
1638
1639            Ok(Expression::Field {
1640                expr: Box::new(lowered_expr),
1641                field,
1642            })
1643        }
1644        ast::Expr::Index(i) => {
1645            // Try to extract mapping access (including nested mappings)
1646            if let Some((mapping_name, keys)) = extract_mapping_access(&i.expr, &i.index, ctx)? {
1647                let lowered_keys: Vec<Expression> = keys
1648                    .into_iter()
1649                    .map(|k| lower_expr(k, ctx, collector))
1650                    .collect::<Result<Vec<_>, _>>()?;
1651
1652                // Record the mapping access (not closing)
1653                let account_name =
1654                    collector.record_access(&mapping_name, lowered_keys.clone(), true, false);
1655                return Ok(Expression::MappingAccess {
1656                    mapping_name,
1657                    keys: lowered_keys,
1658                    account_name,
1659                });
1660            }
1661
1662            // Regular index access
1663            Ok(Expression::Index {
1664                expr: Box::new(lower_expr(&i.expr, ctx, collector)?),
1665                index: Box::new(lower_expr(&i.index, ctx, collector)?),
1666            })
1667        }
1668        ast::Expr::Ternary(t) => Ok(Expression::Ternary {
1669            condition: Box::new(lower_expr(&t.condition, ctx, collector)?),
1670            then_expr: Box::new(lower_expr(&t.then_expr, ctx, collector)?),
1671            else_expr: Box::new(lower_expr(&t.else_expr, ctx, collector)?),
1672        }),
1673        ast::Expr::Assign(a) => {
1674            let target = lower_expr(&a.target, ctx, collector)?;
1675            let value = lower_expr(&a.value, ctx, collector)?;
1676
1677            // Handle compound assignment
1678            let final_value = match a.op {
1679                ast::AssignOp::Assign => value,
1680                ast::AssignOp::AddAssign => Expression::Binary {
1681                    op: BinaryOp::Add,
1682                    left: Box::new(target.clone()),
1683                    right: Box::new(value),
1684                },
1685                ast::AssignOp::SubAssign => Expression::Binary {
1686                    op: BinaryOp::Sub,
1687                    left: Box::new(target.clone()),
1688                    right: Box::new(value),
1689                },
1690                ast::AssignOp::MulAssign => Expression::Binary {
1691                    op: BinaryOp::Mul,
1692                    left: Box::new(target.clone()),
1693                    right: Box::new(value),
1694                },
1695                ast::AssignOp::DivAssign => Expression::Binary {
1696                    op: BinaryOp::Div,
1697                    left: Box::new(target.clone()),
1698                    right: Box::new(value),
1699                },
1700                ast::AssignOp::RemAssign => Expression::Binary {
1701                    op: BinaryOp::Rem,
1702                    left: Box::new(target.clone()),
1703                    right: Box::new(value),
1704                },
1705                ast::AssignOp::BitAndAssign => Expression::Binary {
1706                    op: BinaryOp::BitAnd,
1707                    left: Box::new(target.clone()),
1708                    right: Box::new(value),
1709                },
1710                ast::AssignOp::BitOrAssign => Expression::Binary {
1711                    op: BinaryOp::BitOr,
1712                    left: Box::new(target.clone()),
1713                    right: Box::new(value),
1714                },
1715                ast::AssignOp::BitXorAssign => Expression::Binary {
1716                    op: BinaryOp::BitXor,
1717                    left: Box::new(target.clone()),
1718                    right: Box::new(value),
1719                },
1720            };
1721
1722            // Convert assignment to Statement::Assign which will be properly handled
1723            // For now, return as a special marker that rust_gen will recognize
1724            Ok(Expression::MethodCall {
1725                receiver: Box::new(target),
1726                method: "__assign__".to_string(),
1727                args: vec![final_value],
1728            })
1729        }
1730        ast::Expr::Array(a) => {
1731            // Array literals become Vec construction
1732            if a.elements.is_empty() {
1733                Ok(Expression::Call {
1734                    func: "Vec::new".to_string(),
1735                    args: vec![],
1736                })
1737            } else {
1738                Ok(Expression::Call {
1739                    func: "vec!".to_string(),
1740                    args: a
1741                        .elements
1742                        .iter()
1743                        .map(|e| lower_expr(e, ctx, collector))
1744                        .collect::<Result<Vec<_>, _>>()?,
1745                })
1746            }
1747        }
1748        ast::Expr::Paren(e) => lower_expr(e, ctx, collector),
1749        ast::Expr::If(_) => Err(CodegenError::UnsupportedFeature(
1750            "If expressions".to_string(),
1751        )),
1752        ast::Expr::Tuple(_) => Err(CodegenError::UnsupportedFeature(
1753            "Tuple expressions".to_string(),
1754        )),
1755        ast::Expr::New(_) => Err(CodegenError::UnsupportedFeature(
1756            "New expressions (use CPI instead)".to_string(),
1757        )),
1758    }
1759}
1760
1761fn lower_literal(lit: &ast::Literal) -> Result<Expression, CodegenError> {
1762    match lit {
1763        ast::Literal::Bool(b, _) => Ok(Expression::Literal(Literal::Bool(*b))),
1764        ast::Literal::Int(n, _) => Ok(Expression::Literal(Literal::Uint(*n))),
1765        ast::Literal::HexInt(s, _) => {
1766            let n = u128::from_str_radix(s.trim_start_matches("0x"), 16)
1767                .map_err(|_| CodegenError::TypeConversion(format!("Invalid hex: {}", s)))?;
1768            Ok(Expression::Literal(Literal::Uint(n)))
1769        }
1770        ast::Literal::String(s, _) => Ok(Expression::Literal(Literal::String(s.to_string()))),
1771        ast::Literal::HexString(s, _) => Ok(Expression::Literal(Literal::String(s.to_string()))),
1772        ast::Literal::Address(s, _) => Ok(Expression::Literal(Literal::Pubkey(s.to_string()))),
1773    }
1774}
1775
1776fn lower_binary_op(op: &ast::BinaryOp) -> BinaryOp {
1777    match op {
1778        ast::BinaryOp::Add => BinaryOp::Add,
1779        ast::BinaryOp::Sub => BinaryOp::Sub,
1780        ast::BinaryOp::Mul => BinaryOp::Mul,
1781        ast::BinaryOp::Div => BinaryOp::Div,
1782        ast::BinaryOp::Rem => BinaryOp::Rem,
1783        ast::BinaryOp::Exp => BinaryOp::Mul, // No native exp, would need custom impl
1784        ast::BinaryOp::Eq => BinaryOp::Eq,
1785        ast::BinaryOp::Ne => BinaryOp::Ne,
1786        ast::BinaryOp::Lt => BinaryOp::Lt,
1787        ast::BinaryOp::Le => BinaryOp::Le,
1788        ast::BinaryOp::Gt => BinaryOp::Gt,
1789        ast::BinaryOp::Ge => BinaryOp::Ge,
1790        ast::BinaryOp::And => BinaryOp::And,
1791        ast::BinaryOp::Or => BinaryOp::Or,
1792        ast::BinaryOp::BitAnd => BinaryOp::BitAnd,
1793        ast::BinaryOp::BitOr => BinaryOp::BitOr,
1794        ast::BinaryOp::BitXor => BinaryOp::BitXor,
1795        ast::BinaryOp::Shl => BinaryOp::Shl,
1796        ast::BinaryOp::Shr => BinaryOp::Shr,
1797    }
1798}
1799
1800fn lower_unary_op(op: &ast::UnaryOp) -> UnaryOp {
1801    match op {
1802        ast::UnaryOp::Neg => UnaryOp::Neg,
1803        ast::UnaryOp::Not => UnaryOp::Not,
1804        ast::UnaryOp::BitNot => UnaryOp::BitNot,
1805        ast::UnaryOp::PreInc | ast::UnaryOp::PostInc => UnaryOp::Neg, // Placeholder
1806        ast::UnaryOp::PreDec | ast::UnaryOp::PostDec => UnaryOp::Neg, // Placeholder
1807    }
1808}