neo_solidity/ir/expressions/calls/storage_array/
state_var.rs

1fn try_lower_state_array_helpers(
2    func: &Expression,
3    args: &[Expression],
4    ctx: &mut LoweringContext,
5    instructions: &mut Vec<Instruction>,
6) -> Option<bool> {
7    let Expression::MemberAccess(_, inner, member) = func else {
8        return None;
9    };
10
11    if !matches!(member.name.as_str(), "push" | "pop") {
12        return None;
13    }
14
15    let Expression::Variable(base) = inner.as_ref() else {
16        return None;
17    };
18
19    let state_index = ctx.state_index_map.get(&base.name).copied()?;
20
21    let Some(ValueType::Array(element_type)) = ctx.state_type(state_index).cloned() else {
22        return None;
23    };
24
25    match member.name.as_str() {
26        "push" => Some(lower_state_array_push(state_index, args, ctx, instructions)),
27        "pop" => Some(lower_state_array_pop(
28            state_index,
29            element_type.as_ref(),
30            args,
31            ctx,
32            instructions,
33        )),
34        _ => None,
35    }
36}
37
38fn lower_state_array_push(
39    state_index: usize,
40    args: &[Expression],
41    ctx: &mut LoweringContext,
42    instructions: &mut Vec<Instruction>,
43) -> bool {
44    if args.len() != 1 {
45        ctx.record_error("array push expects exactly one argument");
46        return false;
47    }
48
49    if !lower_expression(&args[0], ctx, instructions) {
50        return false;
51    }
52    let value_local = ctx.allocate_local("__array_push_value".to_string(), None);
53    instructions.push(Instruction::StoreLocal(value_local));
54
55    let len_local = ctx.allocate_local("__array_len".to_string(), None);
56    instructions.push(Instruction::LoadState(state_index));
57    instructions.push(Instruction::StoreLocal(len_local));
58
59    // Store element at index `len`.
60    instructions.push(Instruction::LoadLocal(value_local));
61    instructions.push(Instruction::LoadLocal(len_local));
62    instructions.push(Instruction::StoreMappingElement {
63        state_index,
64        key_types: vec![ValueType::Integer {
65            signed: false,
66            bits: 256,
67        }],
68    });
69
70    // Increment length.
71    instructions.push(Instruction::LoadLocal(len_local));
72    instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::one())));
73    instructions.push(Instruction::BinaryOp(BinaryOperator::Add));
74    instructions.push(Instruction::StoreState(state_index));
75
76    instructions.push(Instruction::PushLiteral(LiteralValue::Boolean(true)));
77    true
78}
79
80fn lower_state_array_pop(
81    state_index: usize,
82    element_type: &ValueType,
83    args: &[Expression],
84    ctx: &mut LoweringContext,
85    instructions: &mut Vec<Instruction>,
86) -> bool {
87    if !args.is_empty() {
88        ctx.record_error("array pop expects no arguments");
89        return false;
90    }
91
92    let len_local = ctx.allocate_local("__array_len".to_string(), None);
93    instructions.push(Instruction::LoadState(state_index));
94    instructions.push(Instruction::StoreLocal(len_local));
95
96    let empty_label = ctx.next_label();
97    let end_label = ctx.next_label();
98
99    // Abort on empty array.
100    instructions.push(Instruction::LoadLocal(len_local));
101    instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::zero())));
102    instructions.push(Instruction::BinaryOp(BinaryOperator::Ne));
103    instructions.push(Instruction::JumpIf { target: empty_label });
104
105    let new_len_local = ctx.allocate_local("__array_new_len".to_string(), None);
106    instructions.push(Instruction::LoadLocal(len_local));
107    instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::one())));
108    instructions.push(Instruction::BinaryOp(BinaryOperator::Sub));
109    instructions.push(Instruction::StoreLocal(new_len_local));
110
111    // Update length before returning element.
112    instructions.push(Instruction::LoadLocal(new_len_local));
113    instructions.push(Instruction::StoreState(state_index));
114
115    // Load element at new_len.
116    instructions.push(Instruction::LoadLocal(new_len_local));
117    instructions.push(Instruction::LoadMappingElement {
118        state_index,
119        key_types: vec![ValueType::Integer {
120            signed: false,
121            bits: 256,
122        }],
123    });
124
125    let popped_local = ctx.allocate_local("__array_popped".to_string(), Some(element_type.clone()));
126    instructions.push(Instruction::StoreLocal(popped_local));
127
128    // Overwrite removed slot with default value.
129    push_default_for_value_type(element_type, ctx, instructions);
130    instructions.push(Instruction::LoadLocal(new_len_local));
131    instructions.push(Instruction::StoreMappingElement {
132        state_index,
133        key_types: vec![ValueType::Integer {
134            signed: false,
135            bits: 256,
136        }],
137    });
138
139    instructions.push(Instruction::LoadLocal(popped_local));
140    instructions.push(Instruction::Jump { target: end_label });
141
142    instructions.push(Instruction::Label(empty_label));
143    instructions.push(Instruction::PushLiteral(LiteralValue::Null));
144    instructions.push(Instruction::Throw);
145
146    instructions.push(Instruction::Label(end_label));
147    true
148}