neo_solidity/cli/bytecode/bytecode_builtins/
syscalls.rs

1fn is_void_syscall(name: &str) -> bool {
2    matches!(
3        name,
4        "System.Storage.Put"
5            | "System.Storage.Delete"
6            | "System.Storage.Local.Put"
7            | "System.Storage.Local.Delete"
8            | "System.Runtime.Notify"
9            | "System.Runtime.Log"
10            | "System.Runtime.BurnGas"
11            | "System.Runtime.LoadScript"
12    )
13}
14
15fn emit_native_contract_call(
16    bytecode: &mut Vec<u8>,
17    contract: ir::NativeContract,
18    method: &str,
19    arg_count: usize,
20    use_callt: bool,
21    token_patches: &mut Vec<MethodTokenPatch>,
22) {
23    // Backwards-compatible aliases for older devpack method names.
24    let method = match (contract, method) {
25        (ir::NativeContract::ContractManagement, "listContracts") => "getContractHashes",
26        _ => method,
27    };
28
29    let call_flags = native_method_call_flags(contract, method);
30
31    if use_callt {
32        if let Ok(params) = u16::try_from(arg_count) {
33            if !method.starts_with('_') && method.len() <= neo_solidity::neo::MAX_TOKEN_METHOD_LENGTH
34            {
35                if let Some(has_return_value) = native_method_has_return_value(contract, method) {
36                    bytecode.push(0x37); // CALLT
37                    let position = bytecode.len();
38                    bytecode.extend_from_slice(&[0, 0]); // token index placeholder
39
40                    token_patches.push(MethodTokenPatch {
41                        position,
42                        token: MethodTokenKey {
43                            hash: native_contract_hash(contract),
44                            method: method.to_string(),
45                            parameters_count: params,
46                            has_return_value,
47                            call_flags,
48                        },
49                    });
50
51                    if !has_return_value {
52                        // CALLT will not push a return value for void methods; keep stack behavior
53                        // consistent with other void builtins by pushing a placeholder item.
54                        bytecode.push(0x11); // PUSH1
55                    }
56                    return;
57                }
58            }
59        }
60    }
61
62    // Build argument array for System.Contract.Call.
63    let arg_count_bigint = BigInt::from(arg_count);
64    push_integer_bigint(bytecode, &arg_count_bigint);
65    bytecode.push(0xC0); // PACK
66    // PACK pops values from the stack and reverses their order. Native contract
67    // calls must preserve the original argument ordering.
68    if arg_count > 1 {
69        bytecode.push(0x4A); // DUP (keep a reference to the array on the stack)
70        bytecode.push(0xD1); // REVERSEITEMS (consumes one reference, reverses in-place)
71    }
72
73    // Push call flags, method name, and contract hash.
74    push_integer_bigint(bytecode, &BigInt::from(call_flags));
75    push_data(bytecode, method.as_bytes());
76    let hash = native_contract_hash(contract);
77    push_data(bytecode, &hash);
78
79    // `System.Contract.Call` expects stack order: [args, flags, method, hash]
80    // (top-of-stack is `hash`, popped first).
81    emit_syscall(bytecode, "System.Contract.Call");
82}
83
84fn native_method_call_flags(contract: ir::NativeContract, method: &str) -> u8 {
85    match native_method_is_mutating(contract, method) {
86        Some(true) => CALLFLAGS_ALL,
87        Some(false) => CALLFLAGS_READ_ONLY,
88        None => CALLFLAGS_ALL,
89    }
90}
91
92fn native_method_is_mutating(contract: ir::NativeContract, method: &str) -> Option<bool> {
93    use ir::NativeContract::*;
94
95    let is_mutating = match (contract, method) {
96        (Neo, "transfer" | "vote" | "registerCandidate" | "unregisterCandidate" | "setGasPerBlock"
97        | "setRegisterPrice") => true,
98        (Gas, "transfer") => true,
99        (ContractManagement, "deploy" | "update" | "destroy" | "setMinimumDeploymentFee") => true,
100        (Policy, "setFeePerByte" | "setExecFeeFactor" | "setStoragePrice" | "setAttributeFee"
101        | "setMillisecondsPerBlock" | "setMaxValidUntilBlockIncrement" | "setMaxTraceableBlocks"
102        | "blockAccount" | "unblockAccount" | "recoverFund"
103        | "setWhitelistFeeContract" | "removeWhitelistFeeContract") => true,
104        (Oracle, "request" | "setPrice" | "finish") => true,
105        (RoleManagement, "designateAsRole") => true,
106        (Notary, "lockDepositUntil" | "withdraw" | "setMaxNotValidBeforeDelta" | "onNEP17Payment") => true,
107        _ => {
108            if native_method_has_return_value(contract, method).is_some() {
109                return Some(false);
110            }
111            return None;
112        }
113    };
114
115    Some(is_mutating)
116}
117
118fn native_method_has_return_value(contract: ir::NativeContract, method: &str) -> Option<bool> {
119    use ir::NativeContract::*;
120
121    let has_return = match (contract, method) {
122        (StdLib, "serialize" | "deserialize" | "jsonSerialize" | "jsonDeserialize" | "base64Encode"
123        | "base64Decode" | "base64UrlEncode" | "base64UrlDecode" | "base58Encode" | "base58Decode"
124        | "base58CheckEncode" | "base58CheckDecode" | "hexEncode" | "hexDecode" | "itoa" | "atoi"
125        | "memoryCompare" | "memorySearch" | "stringSplit" | "strLen") => true,
126        (CryptoLib, "sha256" | "ripemd160" | "keccak256" | "murmur32" | "recoverSecp256K1"
127        | "verifyWithECDsa" | "verifyWithEd25519" | "bls12381Serialize" | "bls12381Deserialize"
128        | "bls12381Equal" | "bls12381Add" | "bls12381Mul" | "bls12381Pairing") => true,
129        (Ledger, "currentIndex" | "getBlock" | "getTransaction" | "getTransactionHeight"
130        | "getTransactionFromBlock") => true,
131        (Neo, "symbol" | "decimals" | "totalSupply" | "balanceOf" | "transfer" | "vote" | "getCandidates"
132        | "registerCandidate" | "unregisterCandidate" | "getGasPerBlock" | "getRegisterPrice"
133        | "getAccountState" | "unclaimedGas" | "getCandidateVote" | "getCommittee"
134        | "getCommitteeAddress" | "getNextBlockValidators" | "getAllCandidates") => true,
135        (Neo, "setGasPerBlock" | "setRegisterPrice") => false,
136        (Gas, "symbol" | "decimals" | "totalSupply" | "balanceOf" | "transfer") => true,
137        (ContractManagement, "deploy" | "getContract" | "getContractById" | "getContractHashes"
138        | "hasMethod" | "isContract" | "getMinimumDeploymentFee") => true,
139        (ContractManagement, "update" | "destroy" | "setMinimumDeploymentFee") => false,
140        (Policy, "getFeePerByte" | "getExecFeeFactor" | "getExecPicoFeeFactor" | "getStoragePrice"
141        | "getMillisecondsPerBlock" | "getMaxValidUntilBlockIncrement" | "getMaxTraceableBlocks"
142        | "getAttributeFee" | "blockAccount" | "unblockAccount" | "isBlocked"
143        | "getBlockedAccounts" | "recoverFund" | "getWhitelistFeeContracts") => true,
144        (Policy, "setFeePerByte" | "setExecFeeFactor" | "setStoragePrice" | "setAttributeFee"
145        | "setMillisecondsPerBlock" | "setMaxValidUntilBlockIncrement"
146        | "setMaxTraceableBlocks" | "setWhitelistFeeContract"
147        | "removeWhitelistFeeContract") => false,
148        (Oracle, "getPrice" | "verify") => true,
149        (Oracle, "request" | "setPrice" | "finish") => false,
150        (RoleManagement, "getDesignatedByRole") => true,
151        (RoleManagement, "designateAsRole") => false,
152        (Notary, "balanceOf" | "expirationOf" | "lockDepositUntil" | "withdraw"
153        | "getMaxNotValidBeforeDelta" | "verify") => true,
154        (Notary, "setMaxNotValidBeforeDelta" | "onNEP17Payment") => false,
155        (Treasury, "verify") => true,
156        (Treasury, "onNEP17Payment" | "onNEP11Payment") => false,
157        (Ledger, "currentHash" | "getTransactionSigners" | "getTransactionVMState") => true,
158        _ => return None,
159    };
160
161    Some(has_return)
162}
163
164fn emit_syscall(bytecode: &mut Vec<u8>, name: &str) {
165    bytecode.push(0x41);
166    bytecode.extend_from_slice(&crate::codegen::interop_id_bytes(name));
167}