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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
fn emit_new_array(bytecode: &mut Vec<u8>) {
bytecode.push(0xC3); // NEWARRAY
}
fn emit_array_get(bytecode: &mut Vec<u8>) {
bytecode.push(0xCE); // PICKITEM
}
fn emit_array_set(bytecode: &mut Vec<u8>) {
bytecode.push(0xD0); // SETITEM
}
fn emit_load_runtime_value(
bytecode: &mut Vec<u8>,
value: &ir::RuntimeValue,
use_callt: bool,
token_patches: &mut Vec<MethodTokenPatch>,
) {
match value {
ir::RuntimeValue::MsgSender => {
// Solidity `msg.sender` is the immediate caller:
// - entry contract: Transaction.Sender (first signer)
// - internal contract call: CallingScriptHash (caller contract)
// - constructor (called from ContractManagement during deploy):
// ContractManagement.Hash would be a useless answer; the user
// actually wants the deploying signer, so we route to
// Transaction.Sender for that case too.
//
// Detection: check if CallingScriptHash matches EntryScriptHash
// (top-level user invocation) OR ContractManagement.Hash (running
// inside _deploy during a contract deploy). Either way, the
// semantically correct `msg.sender` is the transaction's first
// signer (Transaction.Sender). Otherwise we're in an internal
// contract-to-contract call and CallingScriptHash is correct.
//
// ContractManagement native hash on Neo N3:
// 0xfffdc93764dbaddd97c48f252a53ea4643faa3fd (big-endian)
// Stored on-chain in little-endian byte order, which is how
// CallingScriptHash compares it.
// Push CallingScriptHash, then test against EntryScriptHash and
// ContractManagement.Hash. The result is whether either match.
emit_syscall(bytecode, "System.Runtime.GetCallingScriptHash");
bytecode.push(0x4A); // DUP — keep one copy for the actual return path
// First test: == EntryScriptHash
emit_syscall(bytecode, "System.Runtime.GetEntryScriptHash");
bytecode.push(0x97); // EQUAL
// Second test: == ContractManagement.Hash (LE bytes)
// Stack: [calling_dup, calling_eq_entry?]
bytecode.push(0x50); // SWAP — bring calling_dup back to top
// PUSHDATA1 0x14 (20-byte hash, LE):
bytecode.push(0x0C); // PUSHDATA1
bytecode.push(0x14); // length 20
bytecode.extend_from_slice(&[
0xFD, 0xA3, 0xFA, 0x43, 0x46, 0xEA, 0x53, 0x2A,
0x25, 0x8F, 0xC4, 0x97, 0xDD, 0xAD, 0xDB, 0x64,
0x37, 0xC9, 0xFD, 0xFF,
]);
bytecode.push(0x97); // EQUAL — calling == ContractManagement?
// OR the two boolean results.
bytecode.push(0xAC); // BOOLOR
// if !(use_tx_sender) jump to "else" (return CallingScriptHash directly).
let jmp_if_not_pos = bytecode.len();
bytecode.push(0x27); // JMPIFNOT_L
let jmp_if_not_operand = bytecode.len();
bytecode.extend_from_slice(&[0, 0, 0, 0]);
// then: Transaction.Sender
emit_syscall(bytecode, "System.Runtime.GetScriptContainer");
// Neo N3 Transaction stack item layout (devpack order):
// [Hash, Version, Nonce, Sender, ...]
push_integer_bigint(bytecode, &BigInt::from(3u8));
bytecode.push(0xCE); // PICKITEM (Transaction.Sender field)
// jump to end
let jmp_end_pos = bytecode.len();
bytecode.push(0x23); // JMP_L
let jmp_end_operand = bytecode.len();
bytecode.extend_from_slice(&[0, 0, 0, 0]);
// else: re-fetch CallingScriptHash (we DUP'd and consumed it via the OR).
let else_pos = bytecode.len();
emit_syscall(bytecode, "System.Runtime.GetCallingScriptHash");
// end:
let end_pos = bytecode.len();
let rel_else = (else_pos as i32)
.checked_sub(jmp_if_not_pos as i32)
.unwrap_or(0);
bytecode[jmp_if_not_operand..jmp_if_not_operand + 4]
.copy_from_slice(&rel_else.to_le_bytes());
let rel_end = (end_pos as i32).checked_sub(jmp_end_pos as i32).unwrap_or(0);
bytecode[jmp_end_operand..jmp_end_operand + 4].copy_from_slice(&rel_end.to_le_bytes());
}
ir::RuntimeValue::MsgValue => {
// Neo N3 has NO EVM-style attached call value, and no
// `System.Runtime.GetMsgValue` interop exists — emitting a SYSCALL for
// it FAULTS on a real node (unknown interop service). Value transfer
// on Neo arrives as the `amount` argument of `onNEP17Payment` /
// `onNEP11Payment`, not as an ambient `msg.value`. So `msg.value`
// lowers to the conformant constant 0 (PUSH0); contracts that need a
// received amount must read their payment-callback argument.
bytecode.push(0x10); // PUSH0
}
ir::RuntimeValue::MsgData => {
// Solidity `msg.data` == the raw calldata bytes the runtime received at
// `execute(bytecode, input)`. Neo N3 exposes those bytes through the
// `Script` slot (index 7) of the Transaction-shaped array returned by
// `System.Runtime.GetScriptContainer`:
// [Hash, Version, Nonce, Sender, SystemFee, NetworkFee, ValidUntilBlock, Script]
// (see src/runtime/execution/syscalls/runtime.rs "GetScriptContainer").
emit_syscall(bytecode, "System.Runtime.GetScriptContainer");
push_integer_bigint(bytecode, &BigInt::from(7u8));
bytecode.push(0xCE); // PICKITEM — Transaction.Script (input_data)
}
ir::RuntimeValue::TxOrigin => {
emit_syscall(bytecode, "System.Runtime.GetScriptContainer");
// Neo N3 Transaction stack item layout (devpack order):
// [Hash, Version, Nonce, Sender, ...]
push_integer_bigint(bytecode, &BigInt::from(3u8));
bytecode.push(0xCE); // PICKITEM (Transaction.Sender field)
}
// Neo's System.Runtime.GetTime returns milliseconds since epoch, while Solidity's
// block.timestamp is seconds. Normalize here to preserve Solidity semantics.
ir::RuntimeValue::BlockTimestamp => {
emit_syscall(bytecode, "System.Runtime.GetTime");
push_integer_bigint(bytecode, &BigInt::from(1000u64));
bytecode.push(0xA1); // DIV
}
ir::RuntimeValue::BlockNumber => {
emit_native_contract_call(
bytecode,
ir::NativeContract::Ledger,
"currentIndex",
0,
use_callt,
token_patches,
);
}
}
}