1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
fn emit_load_struct_value_from_slot(
bytecode: &mut Vec<u8>,
fields: &[ir::StructField],
use_callt: bool,
token_patches: &mut Vec<MethodTokenPatch>,
) {
// Stack before: [base_slot]
// Stack after: [struct_array]
push_integer_bigint(bytecode, &BigInt::from(fields.len() as u64));
bytecode.push(0xC3); // NEWARRAY
// Stack: [base_slot, out_array]
for (field_index, field) in fields.iter().enumerate() {
// Mapping members have no loadable storage value (entries live at
// per-key derived slots). Push a Null placeholder — WITHOUT a
// `Storage.Get` — but keep the SETITEM below so the struct array
// stays at `fields.len()` entries and PICKITEM indices remain
// aligned with the in-memory struct layout.
if matches!(field.ty, ValueType::Mapping { .. }) {
bytecode.push(0x0B); // PUSHNULL
} else {
// Derive the field slot from the base slot:
// field_slot = keccak256(field_key || base_slot)
bytecode.push(0x4B); // OVER (duplicate base_slot)
push_data(bytecode, &field.key);
bytecode.push(0x50); // SWAP -> [base_slot, out_array, field_key, base_slot]
bytecode.push(0x8B); // CAT
emit_native_contract_call(
bytecode,
ir::NativeContract::CryptoLib,
"keccak256",
1,
use_callt,
token_patches,
);
// Stack: [base_slot, out_array, field_slot]
match &field.ty {
ValueType::Struct { fields, .. } => {
emit_load_struct_value_from_slot(bytecode, fields, use_callt, token_patches);
}
_ => {
emit_syscall(bytecode, "System.Storage.GetContext");
emit_syscall(bytecode, "System.Storage.Get");
emit_coerce_storage_value(bytecode, &field.ty);
}
}
}
// Set `out_array[field_index] = field_value`, preserving the array reference.
bytecode.push(0x4B); // OVER (duplicate out_array)
bytecode.push(0x50); // SWAP -> [base_slot, out_array, out_array, field_value]
push_integer_bigint(bytecode, &BigInt::from(field_index as u64));
bytecode.push(0x50); // SWAP -> [base_slot, out_array, out_array, idx, value]
bytecode.push(0xD0); // SETITEM
}
// Drop base_slot, leaving the struct array.
bytecode.push(0x50); // SWAP -> [out_array, base_slot]
bytecode.push(0x45); // DROP
}
fn emit_store_struct_value_to_slot(
bytecode: &mut Vec<u8>,
fields: &[ir::StructField],
use_callt: bool,
token_patches: &mut Vec<MethodTokenPatch>,
) {
// Stack before: [base_slot, struct_array]
// Stack after: []
for (field_index, field) in fields.iter().enumerate() {
// Skip Mapping members — Solidity `delete` / whole-struct writes
// leave mapping entries untouched, and the in-memory value at the
// mapping's field index is Null, which `Storage.Put` faults on for
// real Neo N3. Each iteration only consumes copies of
// `[base_slot, struct_array]`, so skipping is stack-safe.
if matches!(field.ty, ValueType::Mapping { .. }) {
continue;
}
// Derive field_slot = keccak256(field_key || base_slot)
bytecode.push(0x4B); // OVER (duplicate base_slot)
push_data(bytecode, &field.key);
bytecode.push(0x50); // SWAP
bytecode.push(0x8B); // CAT
emit_native_contract_call(
bytecode,
ir::NativeContract::CryptoLib,
"keccak256",
1,
use_callt,
token_patches,
);
// Stack: [base_slot, struct_array, field_slot]
// Extract field_value = struct_array[field_index]
bytecode.push(0x4B); // OVER (duplicate struct_array)
push_integer_bigint(bytecode, &BigInt::from(field_index as u64));
bytecode.push(0xCE); // PICKITEM -> [base_slot, struct_array, field_slot, field_value]
match &field.ty {
ValueType::Struct { fields, .. } => {
emit_store_struct_value_to_slot(bytecode, fields, use_callt, token_patches);
}
_ => {
// Store expects [value, key, context], so swap to put value on top of the slot.
bytecode.push(0x50); // SWAP -> [base_slot, struct_array, field_value, field_slot]
emit_syscall(bytecode, "System.Storage.GetContext");
emit_syscall(bytecode, "System.Storage.Put");
}
}
}
// Drop `[base_slot, struct_array]`.
bytecode.push(0x45); // DROP (struct_array)
bytecode.push(0x45); // DROP (base_slot)
}