Skip to content

Solidity Feature Support

The neo-solidity compiler parses Solidity 0.8.x source code and lowers it to NeoVM bytecode targeting Neo N3. "Feature support" describes how each Solidity language construct maps to NeoVM semantics — whether it compiles unchanged, compiles with behavioral differences, is rejected outright, or is intentionally blocked because no safe Neo equivalent exists.

This page is the human-readable companion to the canonical machine-audited matrix at docs/SOLIDITY_SUPPORT_MATRIX.md.

Summary

MetricCountPercentage
Total audited features142100%
Fully supported11178%
Partial support1913%
Not supported32%
Intentionally blocked96%

Status icons used throughout this page:

  • ✅ Fully supported — compiles and behaves as expected on NeoVM.
  • ⚠️ Partial support — compiles, but with behavioral differences or limitations documented below.
  • ❌ Not supported — the compiler does not implement this feature.
  • 🚫 Intentionally blocked — the compiler emits a diagnostic error because no safe Neo equivalent exists.

A. Types

FeatureStatusNotes
boolMaps to NeoVM Boolean.
int8 .. int256All widths parsed. NeoVM uses arbitrary-precision BigInteger internally.
uint8 .. uint256All widths parsed. NeoVM uses arbitrary-precision BigInteger internally.
addressMaps to Neo UInt160 (Hash160, 20 bytes).
address payable⚠️Parsed and canonicalized to address. The .transfer() and .send() members are blocked — use NEP-17 transfer() instead.
bytes1 .. bytes32Fixed-length byte arrays via NeoType::ByteArray { fixed_len }.
bytes (dynamic)Dynamic byte array.
stringUTF-8 string type.
enumBacked by uint8. Converted via convert_enum.
structFull struct support with nested fields. Serialized via StdLib.serialize/StdLib.deserialize for storage.
mapping(K => V)Storage mappings with Neo StorageMap. Key type validation enforced.
T[] (dynamic array)new T[](n) allocation supported via NEWARRAY.
T[N] (fixed array)⚠️Parsed. new T[N] supported when N is a compile-time constant.
fixed / ufixedNot supported. Also unsupported in mainline Solidity compilers.
User-defined value typestype X is Y creates transparent aliases. wrap/unwrap compile to no-ops.
bytes.concat(...)Chains NeoVM CAT opcodes. Zero args produce an empty byte array.
string.concat(...)Same implementation as bytes.concat via CAT opcode chain.
Contract types (IERC20)Resolved to Neo UInt160 address. Interface types tracked.
Tuple typesRepresented as NeoVM arrays internally.

Partial type details

address payable — The type is accepted and treated identically to address. However, the EVM-specific members .transfer(amount) and .send(amount) are blocked with a diagnostic directing you to use NEP-17 transfer(). Code that only uses address payable as a type annotation compiles without issue.

T[N] (fixed array) — Fixed-size arrays compile when the size N is a compile-time constant. Runtime-computed sizes require dynamic arrays (T[]).

solidity
// ✅ Compiles — size is a compile-time constant
uint256[10] memory arr;

// ✅ Compiles — dynamic array with runtime size
uint256[] memory arr = new uint256[](n);

B. Expressions

FeatureStatusNotes
Arithmetic (+, -, *, /, %)Binary ops via try_lower_expression_binary_ops.
Comparison (==, !=, <, >, <=, >=)Via try_lower_expression_comparisons.
Logical (&&, ||, !)Short-circuit evaluation.
Bitwise (&, |, ^, ~, <<, >>)Full bitwise support.
Unary (++, --, -, !)Pre/post increment/decrement.
Ternary (? :)ConditionalOperator lowered with labels.
Assignment (=, +=, -=, etc.)Compound assignments in assignments/compound.rs.
deleteState vars, mapping entries, locals, array elements, struct fields.
Tuple expressions (a, b, c)Lowered to NeoVM arrays.
Tuple destructuring (a, b) = f()⚠️Supported. Some complex nested target forms may require intermediate locals.
Type castingExplicit casts between compatible types.
type(X).min / type(X).maxSupported for integer types.
type(T).nameCompile-time string constant.
type(I).interfaceIdComputed from selector XOR of interface methods.
abi.encode(...)⚠️Supported in context of address.call/staticcall. Standalone use is limited.
abi.encodePacked(...)⚠️Same as abi.encode — used for Neo contract call encoding.
abi.encodeWithSignature(...)Lowered to Neo System.Contract.Call.
abi.encodeWithSelector(...)Lowered to Neo System.Contract.Call.
abi.encodeCall(...)Maps to StdLib.serialize.
abi.decode(...)Maps to StdLib.deserialize. Type tuple parsed from second argument.
Named function call args f({x: 1})Named args reordered to positional order at IR level.

Partial expression details

Tuple destructuring — Standard patterns like (uint a, uint b) = getValues() work. Deeply nested destructuring targets (e.g., destructuring into struct members or nested tuples in a single statement) may require the compiler to introduce intermediate locals.

abi.encode / abi.encodePacked — These functions are primarily designed for cross-contract call encoding on Neo, not for producing Ethereum-ABI-compatible byte sequences. When used as arguments to address.call() or address.staticcall(), they work as expected. Standalone use for byte-level manipulation may not produce EVM-identical output.

solidity
// ✅ Works — encoding for cross-contract call
address(target).call(abi.encodeWithSignature("transfer(address,uint256)", to, amount));

// ⚠️ Limited — standalone encoding may differ from EVM ABI
bytes memory encoded = abi.encode(a, b, c);

C. Statements

FeatureStatusNotes
if / elseStandard conditional branching.
for loopInit, condition, post, body all lowered.
while loopCondition + body.
do...while loopBody + condition.
breakLoop break.
continueLoop continue.
returnSingle and multi-value returns.
emit Event(...)Maps to Runtime.Notify. Indexed params supported.
revert(...)Maps to NeoVM ABORT with message.
revert CustomError(...)Named revert with args.
Variable declarationLocal variable definitions with optional initializer.
Block { ... }Scoped statement blocks.
unchecked { ... }NeoVM uses BigInteger (no overflow). Unchecked blocks compile as normal blocks.
assembly { ... }🚫Blocked: "inline assembly is not supported — use NativeCalls.sol".
try / catchMaps to NeoVM TRY/ENDTRY. Single catch clause preferred.
catch Error(string)Named catch with parameter binding.
catch Panic(uint256)⚠️Lowered with runtime integer-type guard. Values are NeoVM exception payloads, not canonical EVM panic codes.
catch (bytes)Low-level catch with raw bytes.

Partial statement details

catch Panic(uint256) — NeoVM exceptions do not carry EVM-style panic codes (0x01 for assert, 0x11 for overflow, etc.). The catch clause binds the NeoVM exception payload as an integer, but the numeric values will not match Ethereum panic code semantics. Use catch (bytes memory reason) for maximum portability.

unchecked { ... } — Since NeoVM uses arbitrary-precision BigInteger, integer overflow cannot occur. The unchecked block is accepted for source compatibility but has no behavioral effect — all arithmetic is inherently unchecked on NeoVM.

solidity
// Compiles identically with or without unchecked on NeoVM
unchecked {
    uint256 result = a + b; // No overflow possible — BigInteger
}

D. Functions

FeatureStatusNotes
Regular functionspublic, external, internal, private visibility.
ConstructorSingle constructor. Multiple constructors rejected.
view / pureState mutability tracked and enforced at IR level.
payable⚠️Parsed. payable on non-receive functions warns — Neo has no native gas payment in function calls.
returns (T)Single return type.
returns (T1, T2, ...)Multi-return via NeoVM arrays.
Function overloading⚠️Parsed. Neo ABI dispatches by name only — overloads with the same name may collide.
modifierFull modifier expansion with _ placeholder substitution.
receive()⚠️Parsed. Diagnostic suggests using onNEP17Payment() callback instead.
fallback()⚠️Parsed. Diagnostic suggests using onNEP17Payment() callback instead.
virtual / overrideInheritance flattening resolves overrides. Multi-level chains supported.
Function selectors (.selector)Computed from canonical parameter types.
NatSpec comments@notice, @dev, @param, @return preserved in metadata.

Partial function details

payable — Neo does not attach native value to function calls the way EVM does with msg.value. The payable modifier is accepted for source compatibility, but a warning is emitted on non-receive functions. Token payments on Neo are handled through NEP-17/NEP-11 callbacks.

Function overloading — The Neo ABI dispatches methods by name string, not by selector hash. Two overloads with the same name but different parameter types will collide in the manifest. Use distinct function names for public/external methods.

solidity
// ⚠️ Overload collision in Neo ABI — both produce "transfer" in manifest
function transfer(address to, uint256 amount) public { ... }
function transfer(address to, uint256 amount, bytes calldata data) public { ... }

// ✅ Use distinct names instead
function transfer(address to, uint256 amount) public { ... }
function transferWithData(address to, uint256 amount, bytes calldata data) public { ... }

receive() / fallback() — These EVM constructs handle incoming Ether. On Neo, token receipts are handled by explicit callbacks. The compiler emits a diagnostic suggesting you implement onNEP17Payment(address from, uint256 amount, bytes memory data) instead.


E. OOP Features

FeatureStatusNotes
Single inheritanceC3 linearization with flatten_contract_inheritance.
Multiple inheritanceDiamond inheritance detected. Constructor arg conflicts reported.
interfaceInterface types tracked. Methods validated.
abstract contractUnimplemented functions detected. Non-abstract contracts get actionable errors.
library⚠️Builtin devpack libraries are compiler intrinsics. User-defined libraries partially supported.
using X for YLibrary member-call syntax fully supported. using X for * and using {f,g} for T included.
super keywordSupported via inheritance flattening with __super_ method preservation.
is (inheritance)Inheritance specifiers fully processed.
Constructor chainingBase constructor arguments resolved from inheritance specifiers.
Event inheritanceInterface events collected recursively via collect_interface_events_recursive.

Partial OOP details

library — The devpack ships built-in libraries (Runtime, Storage, Syscalls, etc.) that are compiler intrinsics — they lower directly to syscalls and native contract calls. User-defined libraries with internal functions work. Libraries with external functions or complex state interactions have limited support.

solidity
// ✅ Works — using devpack intrinsic library
import "devpack/libraries/Runtime.sol";
require(Runtime.checkWitness(sender), "unauthorized");

// ✅ Works — user-defined library with internal functions
library MathLib {
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        return a + b;
    }
}

using MathLib for uint256;
uint256 result = x.add(y);

F. Storage and Memory

FeatureStatusNotes
State variablesMapped to Neo Storage with prefix-based keys.
constantCompile-time constants inlined.
immutableTracked via is_immutable flag. Modification blocked at compile time.
memory keywordParsed. NeoVM is stack-based so memory is implicit.
storage keywordStorage references for mappings and state variables.
calldata keywordParsed. Treated as memory — NeoVM has no calldata region.
Nested mappingsmapping(K1 => mapping(K2 => V)) with composite storage keys.
Struct in storageSerialized/deserialized via StdLib.serialize/StdLib.deserialize.
Array .push() / .pop()Storage array operations supported.
Array .lengthBoth memory and storage arrays.
new bytes(n) / new string(n)Buffer allocation via NEWBUFFER.
new T[](n)Dynamic array allocation via NEWARRAY.
new Contract(...)🚫Blocked: "use ContractManagement for contract deployment".

Storage key derivation

State variables are stored in Neo Storage using deterministic key derivation. For simple state variables, the key is derived from the variable name. For mappings, the key is computed as:

SHA256(key_bytes || slot_hash)

Where slot_hash is SHA256(variable_name). Nested mappings iterate this process for each key level. See EVM to NeoVM Mapping for the full storage lowering specification.


G. Error Handling

FeatureStatusNotes
require(condition)Maps to NeoVM ASSERT.
require(condition, "msg")ASSERT with message.
require(condition, CustomError(...))Error name and arg count preserved in NeoVM THROW message.
assert(condition)Maps to NeoVM ASSERT.
revert()Maps to NeoVM ABORT.
revert("message")ABORT with message.
revert CustomError(...)Named revert with arguments.
Custom error definitionserror X(...) parsed and used in revert statements.
try / catchNeoVM TRY/ENDTRY structured exception handling.
try with return bindingtry f() returns (uint r) { ... } supported.
Multiple catch clauses⚠️Lowered with runtime stack-item type guards (ISTYPE). Selector-level Error/Panic distinction remains limited.

Partial error handling details

Multiple catch clauses — NeoVM exceptions are untyped. When multiple catch clauses are present (catch Error(string), catch Panic(uint256), catch (bytes)), the compiler inserts ISTYPE guards to route exceptions by stack item type. This provides reasonable dispatch but does not replicate EVM's distinct error/panic channels exactly.

solidity
// ✅ Recommended — single catch clause
try target.someFunction() returns (uint256 result) {
    // success
} catch (bytes memory reason) {
    // handle any failure
}

// ⚠️ Works but with caveats — multiple catch clauses
try target.someFunction() returns (uint256 result) {
    // success
} catch Error(string memory reason) {
    // string exceptions routed here
} catch (bytes memory lowLevelData) {
    // everything else
}

H. EVM-Specific Features

These features reference EVM runtime concepts. The compiler maps them to Neo equivalents where possible, blocks them where no safe equivalent exists, and emits warnings for approximate mappings.

FeatureStatusNeo Mapping
msg.senderRuntime.GetCallingScriptHash()
msg.value⚠️Only mapped inside onNEP17Payment callback.
msg.dataNo equivalent — Neo uses typed parameters.
msg.sigNo equivalent.
block.timestampRuntime.GetTime() (normalized to seconds).
block.numberLedger.CurrentIndex().
block.chainidNeo network magic number.
block.coinbaseAuto-mapped to address(0) with warning (dBFT has no miner).
block.difficulty / block.prevrandaoAuto-mapped to Runtime.getRandom() with warning.
block.gaslimitAuto-mapped to Policy.getExecFeeFactor() with warning.
block.basefeeAuto-mapped to Policy.getFeePerByte() with warning.
tx.origin⚠️Parsed. Warning about authorization risks. Maps to first signer script hash.
tx.gaspriceAuto-mapped to Policy.getFeePerByte() with warning.
gasleft()System.Runtime.GasLeft syscall.
blockhash(n)Auto-mapped to Ledger.getBlockHash() with warning.
keccak256(...)CryptoLib.keccak256.
sha256(...)CryptoLib.sha256.
ecrecover(...)CryptoLib.verifyWithECDsa.
selfdestruct(addr)Auto-mapped to ContractManagement.destroy() with warning.
address.call(...)System.Contract.Call.
address.staticcall(...)System.Contract.Call with read-only flag.
address.delegatecall(...)🚫Blocked: no delegate call on Neo.
address.transfer(amount)🚫Blocked: use NEP-17 transfer().
address.send(amount)🚫Blocked: use NEP-17 transfer().
address.balance🚫Blocked: use NativeCalls.neoBalanceOf() / NativeCalls.gasBalanceOf().
address.code🚫Blocked: use ContractManagement.getContract().
address.codehashAuto-mapped to contract script hash with warning. Non-contract returns bytes32(0).
Ether units (wei, gwei, ether)⚠️Parsed. Warning that Neo uses GAS token (10^8 decimals).
Time units (seconds, minutes, etc.)Compile-time constants normalized to seconds.
this keywordRuntime.GetExecutingScriptHash().
type(X).creationCode🚫Blocked: no bytecode access on Neo.
type(X).runtimeCode🚫Blocked: no bytecode access on Neo.

Partial EVM feature details

msg.value — Neo does not attach native value to contract calls. The msg.value expression is only meaningful inside onNEP17Payment() callbacks, where it maps to the amount parameter. Outside that context, the compiler emits an error.

solidity
// ✅ Works — msg.value inside payment callback
function onNEP17Payment(address from, uint256 amount, bytes memory data) external {
    require(msg.value >= minDeposit, "insufficient deposit");
    // msg.value maps to the `amount` parameter
}

// ❌ Error — msg.value outside payment context
function deposit() public payable {
    balances[msg.sender] += msg.value; // No equivalent on Neo
}

tx.origin — Maps to the first signer's script hash in the Neo transaction. The compiler emits a warning because tx.origin-based authorization is considered an anti-pattern on both EVM and Neo. Use msg.sender (which maps to Runtime.GetCallingScriptHash()) or Runtime.checkWitness() instead.

Ether units — The literal multipliers (1 ether = 10^18, 1 gwei = 10^9, etc.) are parsed for source compatibility, but a warning is emitted because Neo GAS uses 10^8 decimals, not 10^18. Adjust your constants accordingly.

Auto-mapping warnings

Several EVM globals are auto-mapped to approximate Neo equivalents. The compiler emits warnings for each to ensure developers understand the semantic differences:

EVM GlobalAuto-Mapped ToWhy It Warns
block.coinbaseaddress(0)dBFT consensus has no block miner.
block.difficulty / block.prevrandaoRuntime.getRandom()Different randomness model.
block.gaslimitPolicy.getExecFeeFactor()Different gas accounting.
block.basefeePolicy.getFeePerByte()Different fee model.
tx.gaspricePolicy.getFeePerByte()Different fee model.
blockhash(n)Ledger.getBlockHash()Semantic match but different chain.
selfdestruct(addr)ContractManagement.destroy()No refund mechanism. Permanent.
address.codehashContract script hashNon-contract addresses return bytes32(0).

I. ERC to NEP Protocol Mapping

Ethereum StandardNeo StandardStatusNotes
ERC-20 (Fungible Token)NEP-17Auto-detected. transfer(to,amount) warns to use 4-param NEP-17 form.
ERC-721 (NFT)NEP-11Auto-detected. transferFrom warns to use NEP-11 transfer(to,tokenId,data).
ERC-20 approve/allowanceN/A⚠️Warning: not part of NEP-17 spec. Neo uses Runtime.checkWitness().
ERC-165 supportsInterfaceManifest supportedstandards⚠️Warning: unnecessary on Neo. Manifest-based discovery.
ERC-4626 (Tokenized Vault)NEP-17⚠️Vault logic compiles. ERC-20 interactions must use NEP-17 equivalents.
ERC-2981 (Royalty)NEP-24Auto-detected. Multiple royalty recipients supported.
receive() / fallback()onNEP17Payment()⚠️Diagnostic suggests callback pattern.

For detailed standard migration guides, see the Standards Mapping.


Category Summary

Category⚠️🚫
A. Types16210
B. Expressions18300
C. Statements15101
D. Functions9400
E. OOP Features9100
F. Storage & Memory12001
G. Error Handling9100
H. EVM-Specific20327
I. ERC-NEP Mapping3400
Total1111939

Building with Safe Defaults

Always compile with strict flags in production to avoid unintended wildcard permissions:

bash
neo-solc MyContract.sol \
  --deny-wildcard-contracts \
  --deny-wildcard-methods \
  -o build/MyContract

Further Reading

MIT Licensed