neo_solidity/ir/expressions/member_access/
runtime_values.rs

1fn try_lower_runtime_member_access(
2    inner: &Expression,
3    member: &Identifier,
4    ctx: &mut LoweringContext,
5    instructions: &mut Vec<Instruction>,
6) -> Option<bool> {
7    match member.name.as_str() {
8        "sender" => {
9            if let Expression::Variable(base) = inner {
10                if base.name == "msg" {
11                    if ctx.function_name == "onNEP17Payment" {
12                        instructions.push(Instruction::LoadParameter(0));
13                    } else {
14                        instructions
15                            .push(Instruction::LoadRuntimeValue(RuntimeValue::MsgSender));
16                    }
17                    return Some(true);
18                }
19            }
20            None
21        }
22        "value" => {
23            if let Expression::Variable(base) = inner {
24                if base.name == "msg" {
25                    if ctx.function_name == "onNEP17Payment" {
26                        instructions.push(Instruction::LoadParameter(1));
27                    } else {
28                        instructions.push(Instruction::LoadRuntimeValue(RuntimeValue::MsgValue));
29                    }
30                    return Some(true);
31                }
32            }
33            None
34        }
35        "data" => {
36            if let Expression::Variable(base) = inner {
37                if base.name == "msg" {
38                    if ctx.function_name == "onNEP17Payment" {
39                        instructions.push(Instruction::LoadParameter(2));
40                    } else {
41                        instructions.push(Instruction::LoadRuntimeValue(RuntimeValue::MsgData));
42                    }
43                    return Some(true);
44                }
45            }
46            None
47        }
48        "sig" => {
49            if let Expression::Variable(base) = inner {
50                if base.name == "msg" {
51                    ctx.record_error_with_suggestion(
52                        "msg.sig is not available on Neo N3. Neo dispatches by method name, not 4-byte function selectors",
53                        "use string-based method identification instead",
54                    );
55                    return Some(false);
56                }
57            }
58            None
59        }
60        "origin" => {
61            if let Expression::Variable(base) = inner {
62                if base.name == "tx" {
63                    // Non-fatal warning: tx.origin compiles but has different semantics on Neo.
64                    eprintln!(
65                        "warning: tx.origin has different semantics on Neo N3. \
66                         Neo uses multi-signature witnesses instead of a single origin. \
67                         Consider using msg.sender or Runtime.CheckWitness() instead."
68                    );
69                    instructions.push(Instruction::LoadRuntimeValue(RuntimeValue::TxOrigin));
70                    return Some(true);
71                }
72            }
73            None
74        }
75        "gasprice" => {
76            if let Expression::Variable(base) = inner {
77                if base.name == "tx" {
78                    // Neo N3 auto-compat: tx.gasprice → Policy.getFeePerByte()
79                    eprintln!(
80                        "warning: tx.gasprice auto-mapped to Policy.getFeePerByte() \
81                         on Neo N3. Neo fees are determined by script size and syscall costs."
82                    );
83                    instructions.push(Instruction::CallBuiltin {
84                        builtin: BuiltinCall::NativeCall {
85                            contract: NativeContract::Policy,
86                            method: "getFeePerByte".to_string(),
87                        },
88                        arg_count: 0,
89                    });
90                    return Some(true);
91                }
92            }
93            None
94        }
95        "timestamp" => {
96            if let Expression::Variable(base) = inner {
97                if base.name == "block" {
98                    instructions.push(Instruction::LoadRuntimeValue(RuntimeValue::BlockTimestamp));
99                    return Some(true);
100                }
101            }
102            None
103        }
104        "number" => {
105            if let Expression::Variable(base) = inner {
106                if base.name == "block" {
107                    instructions.push(Instruction::LoadRuntimeValue(RuntimeValue::BlockNumber));
108                    return Some(true);
109                }
110            }
111            None
112        }
113        "chainid" => {
114            if let Expression::Variable(base) = inner {
115                if base.name == "block" {
116                    // Solidity `block.chainid` is a uint256 chain identifier. Neo N3 exposes a
117                    // network "magic" number via `System.Runtime.GetNetwork`; use that as the
118                    // closest equivalent.
119                    instructions.push(Instruction::CallBuiltin {
120                        builtin: BuiltinCall::Syscall("System.Runtime.GetNetwork".to_string()),
121                        arg_count: 0,
122                    });
123                    return Some(true);
124                }
125            }
126            None
127        }
128        "coinbase" => {
129            if let Expression::Variable(base) = inner {
130                if base.name == "block" {
131                    // Neo N3 auto-compat: block.coinbase → address(0)
132                    // dBFT has no miner; return zero address as safe default.
133                    eprintln!(
134                        "warning: block.coinbase auto-mapped to address(0) on Neo N3 \
135                         (dBFT consensus has no miner). Use NativeCalls.getNextBlockValidators() \
136                         for validator info."
137                    );
138                    instructions.push(Instruction::PushLiteral(LiteralValue::Address(
139                        [0u8; 20].to_vec(),
140                    )));
141                    return Some(true);
142                }
143            }
144            None
145        }
146        "difficulty" | "prevrandao" => {
147            if let Expression::Variable(base) = inner {
148                if base.name == "block" {
149                    // Neo N3 auto-compat: block.difficulty/prevrandao → Runtime.getRandom()
150                    eprintln!(
151                        "warning: block.{} auto-mapped to Runtime.getRandom() on Neo N3 \
152                         (dBFT consensus has no PoW difficulty).",
153                        member.name
154                    );
155                    instructions.push(Instruction::CallBuiltin {
156                        builtin: BuiltinCall::Syscall(
157                            "System.Runtime.GetRandom".to_string(),
158                        ),
159                        arg_count: 0,
160                    });
161                    return Some(true);
162                }
163            }
164            None
165        }
166        "gaslimit" => {
167            if let Expression::Variable(base) = inner {
168                if base.name == "block" {
169                    // Neo N3 auto-compat: block.gaslimit → Policy.getExecFeeFactor()
170                    eprintln!(
171                        "warning: block.gaslimit auto-mapped to Policy.getExecFeeFactor() \
172                         on Neo N3. Neo uses GAS token for fees, not per-block gas limits."
173                    );
174                    instructions.push(Instruction::CallBuiltin {
175                        builtin: BuiltinCall::NativeCall {
176                            contract: NativeContract::Policy,
177                            method: "getExecFeeFactor".to_string(),
178                        },
179                        arg_count: 0,
180                    });
181                    return Some(true);
182                }
183            }
184            None
185        }
186        "basefee" => {
187            if let Expression::Variable(base) = inner {
188                if base.name == "block" {
189                    // Neo N3 auto-compat: block.basefee → Policy.getFeePerByte()
190                    eprintln!(
191                        "warning: block.basefee auto-mapped to Policy.getFeePerByte() \
192                         on Neo N3. Neo uses a fixed fee structure, not EIP-1559 base fees."
193                    );
194                    instructions.push(Instruction::CallBuiltin {
195                        builtin: BuiltinCall::NativeCall {
196                            contract: NativeContract::Policy,
197                            method: "getFeePerByte".to_string(),
198                        },
199                        arg_count: 0,
200                    });
201                    return Some(true);
202                }
203            }
204            None
205        }
206        _ => None,
207    }
208}