neo_solidity/ir/context/
storage.rs

1fn resolve_storage_reference(
2    expression: &Expression,
3    ctx: &LoweringContext,
4) -> Option<StorageReference> {
5    if let Some(mapping) = resolve_mapping_access(expression, ctx) {
6        return Some(mapping.to_storage_reference());
7    }
8
9    match expression {
10        Expression::Variable(identifier) => {
11            if let Some(alias) = ctx.storage_alias(&identifier.name).cloned() {
12                return Some(alias);
13            }
14
15            // Treat struct-typed state variables as storage references so that:
16            // - field accesses (`stateStruct.field`) load from storage slots; and
17            // - whole-struct assignments (`stateStruct = StructName({...})`) can be lowered
18            //   into per-field stores.
19            let state_index = *ctx.state_index_map.get(&identifier.name)?;
20            let state_type = ctx.state_type(state_index)?;
21            if matches!(state_type, ValueType::Struct { .. }) {
22                Some(StorageReference {
23                    state_index,
24                    key_expressions: Vec::new(),
25                    key_types: Vec::new(),
26                    value_type: state_type.clone(),
27                    field_path: Vec::new(),
28                })
29            } else {
30                None
31            }
32        }
33        Expression::MemberAccess(_, inner, member) => {
34            let mut base = resolve_storage_reference(inner, ctx)?;
35            let field = find_struct_field(&base.value_type, &member.name)?;
36            base.field_path.push(StorageReferenceField {
37                key: field.key,
38                ty: field.ty.clone(),
39            });
40            base.value_type = field.ty.clone();
41            Some(base)
42        }
43        _ => None,
44    }
45}
46
47fn emit_storage_load(
48    reference: &StorageReference,
49    ctx: &mut LoweringContext,
50    instructions: &mut Vec<Instruction>,
51) -> bool {
52    let tmp_id = ctx.next_label();
53
54    // Evaluate mapping/array keys left-to-right (Solidity semantics) and store them so we can
55    // push them in the stack order required by the storage slot hashing routine.
56    let mut key_locals: Vec<usize> = Vec::new();
57    for (index, expr) in reference.key_expressions.iter().enumerate() {
58        let local = ctx.allocate_local(
59            format!("__storage_key_{tmp_id}_{index}"),
60            reference.key_types.get(index).cloned(),
61        );
62        if !lower_expression(expr, ctx, instructions) {
63            return false;
64        }
65        instructions.push(Instruction::StoreLocal(local));
66        key_locals.push(local);
67    }
68
69    let push_keys_for_slot = |instructions: &mut Vec<Instruction>| {
70        // The bytecode emission expects keys in reverse order (deepest key first), with the
71        // outer-most key closest to the top of the stack when the base slot is pushed.
72        for local in key_locals.iter().rev() {
73            instructions.push(Instruction::LoadLocal(*local));
74        }
75    };
76
77    if let Some(field) = reference.field_path.last() {
78        let field_keys: Vec<[u8; 32]> = reference.field_path.iter().map(|field| field.key).collect();
79        push_keys_for_slot(instructions);
80        instructions.push(Instruction::LoadStructField {
81            state_index: reference.state_index,
82            key_types: reference.key_types.clone(),
83            field_keys,
84            field_type: field.ty.clone(),
85        });
86        return true;
87    }
88
89    // Loading a struct value from storage requires fetching each field individually.
90    if let ValueType::Struct { fields, .. } = &reference.value_type {
91        let out_local = ctx.allocate_local(
92            format!("__storage_struct_{tmp_id}"),
93            Some(reference.value_type.clone()),
94        );
95
96        instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
97            BigInt::from(fields.len() as u64),
98        )));
99        instructions.push(Instruction::NewArray {
100            element_type: ValueType::Any,
101        });
102        instructions.push(Instruction::StoreLocal(out_local));
103
104        for (index, field) in fields.iter().enumerate() {
105            instructions.push(Instruction::LoadLocal(out_local));
106            instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::from(
107                index as u64,
108            ))));
109            push_keys_for_slot(instructions);
110            instructions.push(Instruction::LoadStructField {
111                state_index: reference.state_index,
112                key_types: reference.key_types.clone(),
113                field_keys: vec![field.key],
114                field_type: field.ty.clone(),
115            });
116            instructions.push(Instruction::ArraySet);
117        }
118
119        instructions.push(Instruction::LoadLocal(out_local));
120        return true;
121    }
122
123    push_keys_for_slot(instructions);
124    instructions.push(Instruction::LoadMappingElement {
125        state_index: reference.state_index,
126        key_types: reference.key_types.clone(),
127    });
128    true
129}
130
131fn emit_storage_store(
132    reference: &StorageReference,
133    ctx: &mut LoweringContext,
134    instructions: &mut Vec<Instruction>,
135) -> bool {
136    let tmp_id = ctx.next_label();
137
138    // Evaluate mapping/array keys left-to-right and store them in locals so we can re-push them
139    // in the correct order for slot hashing.
140    let mut key_locals: Vec<usize> = Vec::new();
141    for (index, expr) in reference.key_expressions.iter().enumerate() {
142        let local = ctx.allocate_local(
143            format!("__storage_key_{tmp_id}_{index}"),
144            reference.key_types.get(index).cloned(),
145        );
146        if !lower_expression(expr, ctx, instructions) {
147            return false;
148        }
149        instructions.push(Instruction::StoreLocal(local));
150        key_locals.push(local);
151    }
152
153    let push_keys_for_slot = |instructions: &mut Vec<Instruction>| {
154        for local in key_locals.iter().rev() {
155            instructions.push(Instruction::LoadLocal(*local));
156        }
157    };
158
159    if let Some(field) = reference.field_path.last() {
160        let field_keys: Vec<[u8; 32]> = reference.field_path.iter().map(|field| field.key).collect();
161        push_keys_for_slot(instructions);
162        instructions.push(Instruction::StoreStructField {
163            state_index: reference.state_index,
164            key_types: reference.key_types.clone(),
165            field_keys,
166            field_type: field.ty.clone(),
167        });
168        return true;
169    }
170
171    // Storing an entire struct value writes each field into its own derived slot.
172    if let ValueType::Struct { fields, .. } = &reference.value_type {
173        let value_local = ctx.allocate_local(
174            format!("__storage_struct_value_{tmp_id}"),
175            Some(reference.value_type.clone()),
176        );
177        instructions.push(Instruction::StoreLocal(value_local));
178
179        for (index, field) in fields.iter().enumerate() {
180            instructions.push(Instruction::LoadLocal(value_local));
181            instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::from(
182                index as u64,
183            ))));
184            instructions.push(Instruction::ArrayGet);
185            push_keys_for_slot(instructions);
186            instructions.push(Instruction::StoreStructField {
187                state_index: reference.state_index,
188                key_types: reference.key_types.clone(),
189                field_keys: vec![field.key],
190                field_type: field.ty.clone(),
191            });
192        }
193
194        return true;
195    }
196
197    push_keys_for_slot(instructions);
198    instructions.push(Instruction::StoreMappingElement {
199        state_index: reference.state_index,
200        key_types: reference.key_types.clone(),
201    });
202    true
203}
204
205fn resolve_mapping_access<'a>(
206    expression: &'a Expression,
207    ctx: &LoweringContext,
208) -> Option<MappingAccess<'a>> {
209    let mut keys: Vec<&'a Expression> = Vec::new();
210    let mut current = expression;
211
212    loop {
213        match current {
214            Expression::ArraySubscript(_, inner, maybe_index) => {
215                let index_expr = maybe_index.as_ref()?.as_ref();
216                keys.insert(0, index_expr);
217                current = inner;
218            }
219            Expression::Variable(identifier) => {
220                // A bare variable without subscript keys is NOT a mapping access;
221                // it should be handled as a plain state-variable load so that
222                // `emit_load_state` (with proper null-coercion) is used.
223                if keys.is_empty() {
224                    return None;
225                }
226
227                let state_index = *ctx.state_index_map.get(&identifier.name)?;
228                let mut current_type = ctx.state_type(state_index)?.clone();
229                let mut key_types = Vec::with_capacity(keys.len());
230
231                for _key_expr in &keys {
232                    match current_type {
233                        ValueType::Mapping { ref key, ref value } => {
234                            key_types.push((**key).clone());
235                            current_type = (**value).clone();
236                        }
237                        ValueType::Array(ref element) => {
238                            // Storage arrays are lowered like mappings keyed by uint256 index.
239                            key_types.push(ValueType::Integer {
240                                signed: false,
241                                bits: 256,
242                            });
243                            current_type = (**element).clone();
244                        }
245                        _ => return None,
246                    }
247                }
248
249                return Some(MappingAccess {
250                    state_index,
251                    key_expressions: keys,
252                    key_types,
253                    value_type: current_type,
254                });
255            }
256            _ => return None,
257        }
258    }
259}