fn resolve_builtin_call(expr: &Expression) -> Option<BuiltinCall> {
if let Expression::MemberAccess(_, inner, member) = expr {
if let Expression::Variable(base) = inner.as_ref() {
let member_name = member.name.as_str();
match base.name.as_str() {
"Runtime" => return resolve_runtime_member(member_name),
"abi" => return resolve_abi_member(member_name),
"Storage" => return resolve_storage_member(member_name),
"Syscalls" => return resolve_syscalls_member(member_name),
"NativeCalls" => return resolve_native_calls_member(member_name),
"Neo" => return resolve_neo_member(member_name),
"StdLib" => return resolve_stdlib_member(member_name),
"CryptoLib" => return resolve_cryptolib_member(member_name),
_ => {}
}
}
}
if let Expression::Variable(identifier) = expr {
if identifier.name == "ecrecover" {
return Some(BuiltinCall::Ecrecover);
}
if identifier.name == "keccak256" {
return Some(BuiltinCall::Keccak256);
}
if identifier.name == "sha256" {
return Some(BuiltinCall::NativeCall {
contract: NativeContract::CryptoLib,
method: "sha256".to_string(),
});
}
if identifier.name == "ripemd160" {
return Some(BuiltinCall::NativeCall {
contract: NativeContract::CryptoLib,
method: "ripemd160".to_string(),
});
}
if identifier.name == "type" {
return Some(BuiltinCall::TypeOf);
}
}
None
}
pub const BUILTIN_LIBRARY_BASES: &[&str] =
&["Runtime", "abi", "Storage", "Syscalls", "NativeCalls", "Neo"];
pub fn builtin_intrinsic_surface() -> Vec<(&'static str, &'static [&'static str])> {
BUILTIN_LIBRARY_BASES
.iter()
.map(|base| {
(
*base,
builtin_library_supported_members(base)
.expect("every builtin library base has a member whitelist"),
)
})
.collect()
}
fn builtin_library_supported_members(base: &str) -> Option<&'static [&'static str]> {
match base {
"Runtime" => Some(&[
"notify",
"notifyIndexed",
"checkWitness",
"requireWitness",
"checkAnyWitness",
"checkAllWitnesses",
"checkMultiSigWitness",
"gasLeft",
"burnGas",
"log",
"getTime",
"getTrigger",
"getInvocationCounter",
"getCurrentSigners",
"getCallFlags",
"getScriptContainer",
"loadScript",
"initializeServices",
"getNetwork",
"getPlatform",
"getAddressVersion",
"getRandom",
"getExecutingScriptHash",
"getCallingScriptHash",
"getEntryScriptHash",
]),
"abi" => Some(&[
"encode",
"encodePacked",
"encodeCall",
"encodeWithSignature",
"encodeWithSelector",
"decode",
]),
"Storage" => Some(&[
"find",
"put",
"get",
"remove",
"asReadOnly",
"initializeContext",
"getContext",
"getReadOnlyContext",
"putContractMetadata",
]),
"Syscalls" => Some(&[
"contractCall",
"contractCallWithFlags",
"getCallFlags",
"contractCreate",
"contractUpdate",
"contractDestroy",
"createStandardAccount",
"createMultisigAccount",
"notify",
"getCurrentIndex",
"getCurrentHash",
"getBlock",
"getTransaction",
"getTransactionHeight",
"getTransactionFromBlock",
"getTransactionSigners",
"getTransactionVMState",
"getExecutingScriptHash",
"getCallingScriptHash",
"getEntryScriptHash",
"getScriptContainer",
"loadScript",
"getStorageContext",
"getReadOnlyStorageContext",
"storageAsReadOnly",
"storageGet",
"storagePut",
"storageDelete",
"storageFind",
"checkWitness",
"getTime",
"gasLeft",
"getPlatform",
"getTrigger",
"getNotifications",
"log",
"getCurrentSigners",
"checkSig",
"checkMultisig",
"sha256",
"ripemd160",
"verifyWithECDsa",
"murmur32",
"keccak256",
"recoverSecp256K1",
"verifyWithEd25519",
"bls12381Serialize",
"bls12381Deserialize",
"bls12381Equal",
"bls12381Add",
"bls12381Mul",
"bls12381Pairing",
"bls12381G1Add",
"bls12381G1Mul",
"bls12381G1Neg",
"bls12381G2Add",
"bls12381G2Mul",
"bls12381G2Neg",
"neoKeccak256",
"serialize",
"deserialize",
"itoa",
"atoi",
"jsonSerialize",
"jsonDeserialize",
"base64Encode",
"base64Decode",
"base64UrlEncode",
"base64UrlDecode",
"base58Encode",
"base58Decode",
"base58CheckEncode",
"base58CheckDecode",
"hexEncode",
"hexDecode",
"memoryCompare",
"memorySearch",
"stringSplit",
"strLen",
"iteratorNext",
"iteratorValue",
"getCurrentRandom",
"getNetwork",
"getAddressVersion",
"burnGas",
"getInvocationCounter",
"getFeePerByte",
"getExecFeeFactor",
"getExecPicoFeeFactor",
"getStoragePrice",
"getMillisecondsPerBlock",
"getMaxValidUntilBlockIncrement",
"getMaxTraceableBlocks",
"getAttributeFee",
"isBlocked",
"oracleRequest",
"getOraclePrice",
"getDesignatedByRole",
"scriptHashToAddress",
"addressToScriptHash",
"isValidAddress",
"getContractScript",
"contractExists",
]),
"NativeCalls" => Some(&[
"neoTotalSupply",
"neoBalanceOf",
"neoTransfer",
"neoDecimals",
"neoSymbol",
"neoName",
"vote",
"getCandidates",
"registerCandidate",
"unregisterCandidate",
"getGasPerBlock",
"getRegisterPrice",
"setRegisterPrice",
"setGasPerBlock",
"getAccountState",
"unclaimedGas",
"getCandidateVote",
"getCommittee",
"getCommitteeAddress",
"isCommittee",
"getNextBlockValidators",
"getAllCandidates",
"isValidator",
"gasTotalSupply",
"gasBalanceOf",
"gasTransfer",
"gasDecimals",
"gasSymbol",
"gasName",
"deployContract",
"updateContract",
"destroyContract",
"getContract",
"listContracts",
"hasMethod",
"getMinimumDeploymentFee",
"setMinimumDeploymentFee",
"getContractById",
"isContract",
"getFeePerByte",
"setFeePerByte",
"getExecFeeFactor",
"getExecPicoFeeFactor",
"setExecFeeFactor",
"getStoragePrice",
"getMillisecondsPerBlock",
"setMillisecondsPerBlock",
"getMaxValidUntilBlockIncrement",
"setMaxValidUntilBlockIncrement",
"getMaxTraceableBlocks",
"setMaxTraceableBlocks",
"getAttributeFee",
"setAttributeFee",
"setStoragePrice",
"blockAccount",
"unblockAccount",
"isBlocked",
"getBlockedAccounts",
"recoverFund",
"setWhitelistFeeContract",
"removeWhitelistFeeContract",
"getWhitelistFeeContracts",
"requestOracleData",
"getOraclePrice",
"setOraclePrice",
"oracleFinish",
"oracleVerify",
"oracleRequest",
"designateAsRole",
"getDesignatedByRole",
"currentIndex",
"currentHash",
"getBlock",
"getBlockHash",
"getBlockByIndex",
"getBlockByHash",
"getTransaction",
"getTransactionHeight",
"getTransactionFromBlock",
"getTransactionSigners",
"getTransactionVMState",
"getBlockSystemFee",
"notaryVerify",
"notaryBalanceOf",
"notaryExpirationOf",
"notaryLockDepositUntil",
"notaryWithdraw",
"notaryGetMaxNotValidBeforeDelta",
"notarySetMaxNotValidBeforeDelta",
"notaryOnNEP17Payment",
"treasuryVerify",
"treasuryOnNEP17Payment",
"treasuryOnNEP11Payment",
"isNativeContract",
"getNativeContractName",
"getAllNativeContracts",
"estimateNativeCallGas",
"batchNativeCalls",
"getNetworkConfiguration",
"getNativeContractManifest",
"safeNativeCall",
"externalNativeCall",
]),
"Neo" => Some(&[
"verifySignature",
"callContract",
"deployContract",
"getNeoBalance",
"getGasBalance",
"getGasPrice",
"getStoragePrice",
"getCommittee",
"getValidators",
"isCommittee",
"isValidator",
"getRandom",
"getBlockHeight",
"getCurrentBlock",
"getBlockByIndex",
"getBlockTime",
"getTransaction",
"getTransactionHeight",
"transactionExists",
"getNetworkMagic",
"verifyWithWitness",
"sha256Hash",
"ripemd160Hash",
"transferNeo",
"transferGas",
]),
_ => None,
}
}
fn resolve_runtime_member(member: &str) -> Option<BuiltinCall> {
match member {
"notify" => Some(BuiltinCall::RuntimeNotify),
"checkWitness" => Some(BuiltinCall::RuntimeCheckWitness),
"gasLeft" => Some(BuiltinCall::Syscall("System.Runtime.GasLeft".to_string())),
"burnGas" => Some(BuiltinCall::Syscall("System.Runtime.BurnGas".to_string())),
"log" => Some(BuiltinCall::Syscall("System.Runtime.Log".to_string())),
"getTime" => Some(BuiltinCall::Syscall("System.Runtime.GetTime".to_string())),
"getTrigger" => Some(BuiltinCall::Syscall(
"System.Runtime.GetTrigger".to_string(),
)),
"getInvocationCounter" => Some(BuiltinCall::Syscall(
"System.Runtime.GetInvocationCounter".to_string(),
)),
"getCurrentSigners" => Some(BuiltinCall::Syscall(
"System.Runtime.CurrentSigners".to_string(),
)),
"getCallFlags" => Some(BuiltinCall::Syscall(
"System.Contract.GetCallFlags".to_string(),
)),
"getScriptContainer" => Some(BuiltinCall::Syscall(
"System.Runtime.GetScriptContainer".to_string(),
)),
"loadScript" => Some(BuiltinCall::Syscall(
"System.Runtime.LoadScript".to_string(),
)),
"getNetwork" => Some(BuiltinCall::Syscall(
"System.Runtime.GetNetwork".to_string(),
)),
"getPlatform" => Some(BuiltinCall::Syscall("System.Runtime.Platform".to_string())),
"getAddressVersion" => Some(BuiltinCall::Syscall(
"System.Runtime.GetAddressVersion".to_string(),
)),
"getRandom" => Some(BuiltinCall::Syscall("System.Runtime.GetRandom".to_string())),
"getExecutingScriptHash" => Some(BuiltinCall::Syscall(
"System.Runtime.GetExecutingScriptHash".to_string(),
)),
"getCallingScriptHash" => Some(BuiltinCall::Syscall(
"System.Runtime.GetCallingScriptHash".to_string(),
)),
"getEntryScriptHash" => Some(BuiltinCall::Syscall(
"System.Runtime.GetEntryScriptHash".to_string(),
)),
_ => None,
}
}
fn resolve_abi_member(member: &str) -> Option<BuiltinCall> {
match member {
"encode" => Some(BuiltinCall::AbiEncode),
"encodePacked" => Some(BuiltinCall::AbiEncodePacked),
"encodeCall" => Some(BuiltinCall::AbiEncodeCall),
"encodeWithSignature" => Some(BuiltinCall::AbiEncodeWithSignature),
"encodeWithSelector" => Some(BuiltinCall::AbiEncodeWithSignature),
"decode" => Some(BuiltinCall::AbiDecode),
_ => None,
}
}
fn resolve_storage_member(member: &str) -> Option<BuiltinCall> {
match member {
"find" => Some(BuiltinCall::StorageFind),
"put" => Some(BuiltinCall::StoragePut),
"get" => Some(BuiltinCall::StorageGet),
"remove" => Some(BuiltinCall::StorageDelete),
"initializeContext" => Some(BuiltinCall::Syscall(
"System.Storage.GetContext".to_string(),
)),
"getContext" => Some(BuiltinCall::Syscall(
"System.Storage.GetContext".to_string(),
)),
"getReadOnlyContext" => Some(BuiltinCall::Syscall(
"System.Storage.GetReadOnlyContext".to_string(),
)),
"asReadOnly" => Some(BuiltinCall::Syscall(
"System.Storage.AsReadOnly".to_string(),
)),
_ => None,
}
}
fn resolve_neo_member(member: &str) -> Option<BuiltinCall> {
match member {
"verifySignature" => Some(BuiltinCall::VerifySignature),
"callContract" => Some(BuiltinCall::ContractCall),
"deployContract" => Some(BuiltinCall::DeployContract),
"getNeoBalance" => Some(BuiltinCall::NativeCall {
contract: NativeContract::Neo,
method: "balanceOf".to_string(),
}),
"getGasBalance" => Some(BuiltinCall::NativeCall {
contract: NativeContract::Gas,
method: "balanceOf".to_string(),
}),
"getGasPrice" => Some(BuiltinCall::NativeCall {
contract: NativeContract::Policy,
method: "getFeePerByte".to_string(),
}),
"getStoragePrice" => Some(BuiltinCall::NativeCall {
contract: NativeContract::Policy,
method: "getStoragePrice".to_string(),
}),
"getCommittee" => Some(BuiltinCall::NativeCall {
contract: NativeContract::Neo,
method: "getCommittee".to_string(),
}),
"getValidators" => Some(BuiltinCall::NativeCall {
contract: NativeContract::Neo,
method: "getNextBlockValidators".to_string(),
}),
"isCommittee" => Some(BuiltinCall::NativeCall {
contract: NativeContract::Neo,
method: "getCommittee".to_string(),
}),
"isValidator" => Some(BuiltinCall::NativeCall {
contract: NativeContract::Neo,
method: "getNextBlockValidators".to_string(),
}),
"getRandom" => Some(BuiltinCall::Syscall("System.Runtime.GetRandom".to_string())),
"getBlockHeight" => Some(BuiltinCall::NativeCall {
contract: NativeContract::Ledger,
method: "currentIndex".to_string(),
}),
"getCurrentBlock" => Some(BuiltinCall::NativeCall {
contract: NativeContract::Ledger,
method: "currentIndex".to_string(),
}),
"transactionExists" => Some(BuiltinCall::NativeCall {
contract: NativeContract::Ledger,
method: "getTransaction".to_string(),
}),
"getNetworkMagic" => Some(BuiltinCall::Syscall(
"System.Runtime.GetNetwork".to_string(),
)),
"getBlockByIndex" => Some(BuiltinCall::NativeCall {
contract: NativeContract::Ledger,
method: "getBlock".to_string(),
}),
"getBlockTime" => Some(BuiltinCall::Syscall("System.Runtime.GetTime".to_string())),
"getTransaction" => Some(BuiltinCall::NativeCall {
contract: NativeContract::Ledger,
method: "getTransaction".to_string(),
}),
"getTransactionHeight" => Some(BuiltinCall::NativeCall {
contract: NativeContract::Ledger,
method: "getTransactionHeight".to_string(),
}),
"verifyWithWitness" => Some(BuiltinCall::Syscall(
"System.Runtime.CheckWitness".to_string(),
)),
"sha256Hash" => Some(BuiltinCall::NativeCall {
contract: NativeContract::CryptoLib,
method: "sha256".to_string(),
}),
"ripemd160Hash" => Some(BuiltinCall::NativeCall {
contract: NativeContract::CryptoLib,
method: "ripemd160".to_string(),
}),
_ => None,
}
}
fn resolve_stdlib_member(member: &str) -> Option<BuiltinCall> {
match member {
"serialize" | "deserialize" | "jsonSerialize" | "jsonDeserialize" | "itoa" | "atoi"
| "base64Encode" | "base64Decode" | "base64UrlEncode" | "base64UrlDecode"
| "base58Encode" | "base58Decode" | "base58CheckEncode" | "base58CheckDecode"
| "hexEncode" | "hexDecode" | "memoryCompare" | "memorySearch" | "stringSplit"
| "strLen" => Some(BuiltinCall::NativeCall {
contract: NativeContract::StdLib,
method: member.to_string(),
}),
_ => None,
}
}
fn resolve_cryptolib_member(member: &str) -> Option<BuiltinCall> {
match member {
"sha256" | "ripemd160" | "verifyWithECDsa" | "murmur32" | "keccak256"
| "recoverSecp256K1" | "verifyWithEd25519" | "bls12381Serialize"
| "bls12381Deserialize" | "bls12381Equal" | "bls12381Add" | "bls12381Mul"
| "bls12381Pairing" | "bls12381G1Add" | "bls12381G1Mul" | "bls12381G2Add"
| "bls12381G2Mul" | "bls12381G1Neg" | "bls12381G2Neg" => {
Some(BuiltinCall::NativeCall {
contract: NativeContract::CryptoLib,
method: member.to_string(),
})
}
_ => None,
}
}
#[cfg(test)]
mod devpack_intrinsic_surface_tests {
use super::*;
use std::path::PathBuf;
fn special_case_lowerings(base: &str) -> &'static [&'static str] {
match base {
"Runtime" => &[
"initializeServices",
"notifyIndexed",
"notify",
"requireWitness",
"checkAnyWitness",
"checkAllWitnesses",
"checkMultiSigWitness",
],
"Storage" => &["putContractMetadata"],
"Neo" => &[
"isCommittee",
"getCommittee",
"getValidators",
"isValidator",
"transferGas",
"transferNeo",
],
_ => &[],
}
}
fn has_lowering(base: &str, member: &str) -> bool {
let resolved = match base {
"Runtime" => resolve_runtime_member(member).is_some(),
"Storage" => resolve_storage_member(member).is_some(),
"Neo" => resolve_neo_member(member).is_some(),
other => panic!("unexpected library base '{other}'"),
};
resolved || special_case_lowerings(base).contains(&member)
}
fn devpack_library_path(base: &str) -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("devpack")
.join("libraries")
.join(format!("{base}.sol"))
}
fn declared_public_functions(source: &str) -> Vec<String> {
let mut names = Vec::new();
for line in source.lines() {
let trimmed = line.trim_start();
let Some(rest) = trimmed.strip_prefix("function ") else {
continue;
};
let Some(name) = rest.split('(').next() else {
continue;
};
let name = name.trim();
if name.is_empty() || name.starts_with('_') {
continue;
}
if trimmed.contains(" private ") || trimmed.ends_with(" private") {
continue;
}
names.push(name.to_string());
}
names
}
#[test]
fn every_declared_devpack_library_function_has_an_intrinsic_lowering() {
for base in ["Runtime", "Storage", "Neo"] {
let path = devpack_library_path(base);
let source = std::fs::read_to_string(&path)
.unwrap_or_else(|e| panic!("failed to read {}: {e}", path.display()));
let declared = declared_public_functions(&source);
assert!(
!declared.is_empty(),
"no function declarations found in {} — extraction regressed?",
path.display()
);
let missing: Vec<&String> = declared
.iter()
.filter(|name| !has_lowering(base, name))
.collect();
assert!(
missing.is_empty(),
"devpack/libraries/{base}.sol declares functions with no compiler \
lowering (calls would hard-fail; prune them or add a lowering): {missing:?}"
);
}
}
#[test]
fn every_declared_devpack_library_function_is_whitelisted() {
for base in ["Runtime", "Storage", "Neo"] {
let path = devpack_library_path(base);
let source = std::fs::read_to_string(&path)
.unwrap_or_else(|e| panic!("failed to read {}: {e}", path.display()));
let whitelist =
builtin_library_supported_members(base).expect("builtin library whitelist");
let missing: Vec<String> = declared_public_functions(&source)
.into_iter()
.filter(|name| !whitelist.contains(&name.as_str()))
.collect();
assert!(
missing.is_empty(),
"devpack/libraries/{base}.sol declares functions missing from \
builtin_library_supported_members: {missing:?}"
);
}
}
#[test]
fn every_whitelisted_member_has_an_intrinsic_lowering() {
for base in ["Runtime", "Storage", "Neo"] {
let whitelist =
builtin_library_supported_members(base).expect("builtin library whitelist");
let missing: Vec<&&str> = whitelist
.iter()
.filter(|member| !has_lowering(base, member))
.collect();
assert!(
missing.is_empty(),
"builtin_library_supported_members(\"{base}\") lists members \
with no compiler lowering: {missing:?}"
);
}
}
#[test]
fn fictional_local_storage_syscalls_are_not_resolvable() {
for member in ["putLocal", "getLocal", "removeLocal", "findLocal"] {
assert!(
resolve_storage_member(member).is_none(),
"Storage.{member} must not resolve to an intrinsic"
);
assert!(
!builtin_library_supported_members("Storage")
.expect("Storage whitelist")
.contains(&member),
"Storage.{member} must not be whitelisted"
);
}
for member in [
"storageGetLocal",
"storagePutLocal",
"storageDeleteLocal",
"storageFindLocal",
] {
assert!(
resolve_syscalls_member(member).is_none(),
"Syscalls.{member} must not resolve to an intrinsic"
);
assert!(
!builtin_library_supported_members("Syscalls")
.expect("Syscalls whitelist")
.contains(&member),
"Syscalls.{member} must not be whitelisted"
);
}
}
#[test]
fn miscompiling_storage_helpers_are_not_resolvable() {
for member in [
"batchPut",
"batchGet",
"batchDelete",
"count",
"countLocal",
"findValues",
"findLocalValues",
"findKeys",
"findLocalKeys",
"clearPrefix",
"exists",
"isValidKey",
"getUsage",
] {
assert!(
resolve_storage_member(member).is_none(),
"Storage.{member} must not resolve to an intrinsic"
);
assert!(
!builtin_library_supported_members("Storage")
.expect("Storage whitelist")
.contains(&member),
"Storage.{member} must not be whitelisted"
);
}
}
#[test]
fn no_resolver_emits_syscalls_outside_the_neo_n3_interop_table() {
const CANONICAL: &[&str] = &[
"System.Contract.Call",
"System.Contract.CallNative",
"System.Contract.CreateMultisigAccount",
"System.Contract.CreateStandardAccount",
"System.Contract.GetCallFlags",
"System.Contract.NativeOnPersist",
"System.Contract.NativePostPersist",
"System.Crypto.CheckMultisig",
"System.Crypto.CheckSig",
"System.Iterator.Next",
"System.Iterator.Value",
"System.Runtime.BurnGas",
"System.Runtime.CheckWitness",
"System.Runtime.CurrentSigners",
"System.Runtime.GasLeft",
"System.Runtime.GetAddressVersion",
"System.Runtime.GetCallingScriptHash",
"System.Runtime.GetEntryScriptHash",
"System.Runtime.GetExecutingScriptHash",
"System.Runtime.GetInvocationCounter",
"System.Runtime.GetNetwork",
"System.Runtime.GetNotifications",
"System.Runtime.GetRandom",
"System.Runtime.GetScriptContainer",
"System.Runtime.GetTime",
"System.Runtime.GetTrigger",
"System.Runtime.LoadScript",
"System.Runtime.Log",
"System.Runtime.Notify",
"System.Runtime.Platform",
"System.Storage.AsReadOnly",
"System.Storage.Delete",
"System.Storage.Find",
"System.Storage.Get",
"System.Storage.GetContext",
"System.Storage.GetReadOnlyContext",
"System.Storage.Put",
];
let probe = |label: &str, builtin: Option<BuiltinCall>| {
if let Some(BuiltinCall::Syscall(name)) = builtin {
assert!(
CANONICAL.contains(&name.as_str()),
"{label} lowers to syscall '{name}' which is not in the \
Neo N3 interop table"
);
}
};
for base in ["Runtime", "Storage", "Syscalls", "Neo"] {
let members = builtin_library_supported_members(base).expect("whitelist");
for member in members {
let builtin = match base {
"Runtime" => resolve_runtime_member(member),
"Storage" => resolve_storage_member(member),
"Syscalls" => resolve_syscalls_member(member),
"Neo" => resolve_neo_member(member),
_ => unreachable!(),
};
probe(&format!("{base}.{member}"), builtin);
}
}
}
}