# Solidity 0.8.x Support Matrix
> **Compiler**: neo-devpack-solidity v0.18.1
> **Parser**: foundry-solang-parser 0.3.9
> **Target**: NeoVM (Neo N3)
> **Audit date**: 2026-04-30
Legend:
- ✅ Fully supported
- ⚠️ Partial support (see notes)
- ❌ Not supported
- 🚫 Intentionally blocked with diagnostic error
Scope note: this matrix describes neo-solc support on NeoVM, not full EVM
compatibility. Features marked supported may still have Neo-specific semantics.
For migration guidance and production validation gaps, see
`docs/internals/parity-and-limitations.md`.
---
## A. Types
| `bool` | ✅ | Maps to NeoVM Boolean |
| `int8`..`int256` | ✅ | All widths parsed; NeoVM uses arbitrary-precision BigInteger |
| `uint8`..`uint256` | ✅ | All widths parsed; NeoVM uses arbitrary-precision BigInteger |
| `address` | ✅ | Maps to Neo UInt160 (Hash160, 20 bytes) |
| `address payable` | ⚠️ | Parsed and canonicalized to `address`; `.transfer()` / `.send()` map to Neo GAS transfer semantics, not EVM attached value |
| `bytes1`..`bytes32` | ✅ | Fixed-length byte arrays via `NeoType::ByteArray { fixed_len }` |
| `bytes` (dynamic) | ✅ | Dynamic byte array |
| `string` | ✅ | UTF-8 string type |
| `enum` | ✅ | Converted via `convert_enum`; backed by uint8 |
| `struct` | ✅ | Full struct support with nested fields; `StructDefinition` converted |
| `mapping(K => V)` | ✅ | Storage mappings with Neo StorageMap; key type validation enforced |
| `T[]` (dynamic array) | ✅ | `new T[](n)` allocation supported |
| `T[N]` (fixed array) | ⚠️ | Parsed; `new T[N]` supported when `N` is compile-time constant |
| `fixed` / `ufixed` | ❌ | Not supported (also unsupported in mainline Solidity) |
| User-defined value types (`type X is Y`) | ✅ | Transparent type aliases; `wrap`/`unwrap` compile to no-ops |
| `bytes.concat(...)` | ✅ | Chains NeoVM CAT opcodes; zero args produce empty byte array |
| `string.concat(...)` | ✅ | Same implementation as `bytes.concat` via CAT opcode chain |
| Contract types (e.g., `IERC20`) | ✅ | Resolved to Neo UInt160 address; interface types tracked |
| Tuple types | ✅ | Represented as NeoVM arrays internally |
| Function types (`function(...) internal / external`) | ❌ | Not representable on NeoVM; state variables, locals, params, and return types of function type are rejected by the `NeoType` resolver. Use named functions and inheritance instead of function pointers. |
---
## B. Expressions
| Arithmetic (`+`, `-`, `*`, `/`, `%`) | ✅ | Binary ops via `try_lower_expression_binary_ops` |
| Comparison (`==`, `!=`, `<`, `>`, `<=`, `>=`) | ✅ | Via `try_lower_expression_comparisons` |
| Logical (`&&`, `\|\|`, `!`) | ✅ | Short-circuit evaluation in `logical.rs` |
| Bitwise (`&`, `\|`, `^`, `~`, `<<`, `>>`) | ✅ | Full bitwise support |
| Unary (`++`, `--`, `-`, `!`) | ✅ | Pre/post increment/decrement |
| Ternary (`? :`) | ✅ | `ConditionalOperator` lowered with labels |
| Assignment (`=`, `+=`, `-=`, etc.) | ✅ | Compound assignments in `assignments/compound.rs` |
| `delete` | ✅ | State vars, mapping entries, locals, array elements, struct fields |
| Tuple expressions `(a, b, c)` | ✅ | `Expression::List` lowered to NeoVM arrays |
| Tuple destructuring `(a, b) = f()` | ⚠️ | Nested destructuring assignment supported; some complex target forms still require intermediate locals |
| Type casting | ✅ | `TypeCastingShowcase.sol` example compiles |
| `type(X).min` / `type(X).max` | ✅ | Supported for integer types |
| `type(T).name` | ✅ | Compile-time string constant for contract/type names |
| `type(I).interfaceId` | ✅ | Computed from selector XOR of interface methods |
| `abi.encode(...)` | ⚠️ | Supported for selected compiler/runtime paths; dynamic payloads and standalone production use need Neo-Express validation |
| `abi.encodePacked(...)` | ⚠️ | Same as `abi.encode` — used for Neo contract call encoding |
| `abi.encodeWithSignature(...)` | ⚠️ | In low-level call contexts it rewrites to Neo contract calls; standalone use approximates calldata as selector plus encoded args |
| `abi.encodeWithSelector(...)` | ⚠️ | In low-level call contexts it rewrites to Neo contract calls; standalone use approximates calldata as selector plus encoded args |
| `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 |
---
## C. Statements
| `if` / `else` | ✅ | Standard conditional branching |
| `for` loop | ✅ | Init, condition, post, body all lowered |
| `while` loop | ✅ | Condition + body |
| `do...while` loop | ✅ | Body + condition |
| `break` | ✅ | Loop break |
| `continue` | ✅ | Loop continue |
| `return` | ✅ | Single 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; `RevertNamedArgs` also handled |
| Variable declaration | ✅ | Local variable definitions with optional initializer |
| Block `{ ... }` | ✅ | Scoped statement blocks |
| `unchecked { ... }` | ✅ | Suppresses Solidity 0.8 checked-arithmetic guards inside the block; supported fixed-width arithmetic wraps |
| `assembly { ... }` | ⚠️ | Limited Yul subset lowering; unsupported EVM-only operations warn and emit no assembly logic for that block |
| `try` / `catch` | ✅ | Maps to NeoVM TRY/ENDTRY; single catch clause preferred |
| `catch Error(string)` | ✅ | Named catch with parameter binding |
| `catch Panic(uint256)` | ✅ | Matches the canonical `keccak256("Panic(uint256)")[..4] = 0x4e487b71` selector on the revert envelope and decodes the 32-byte BE code (Task #103) |
| `catch (bytes)` | ✅ | Low-level catch with raw bytes |
---
## D. Functions
| Regular functions | ✅ | Public, external, internal, private |
| Constructor | ✅ | Single constructor; multiple constructors rejected |
| `view` / `pure` | ✅ | State mutability tracked and enforced at IR level |
| `payable` | ⚠️ | Parsed; `payable` on non-receive functions warns (Neo has no native gas payment) |
| `returns (T)` | ✅ | Single return type |
| `returns (T1, T2, ...)` | ✅ | Multi-return via NeoVM arrays |
| Function overloading | ⚠️ | Supported with `neo_name` mangling; one canonical ABI name is preserved and non-primary overloads are exported under generated Neo method names |
| `modifier` | ✅ | Full modifier expansion with `_` placeholder substitution |
| `receive()` | ⚠️ | **Silently remapped** to `onNEP17Payment(address,uint256,bytes)` in the manifest when no explicit `onNEP17Payment` is declared (NEP-17 convention, see `src/solidity/convert/functions.rs:32`). Ethereum devs: the body is preserved but exported under a new ABI name. See "receive()/fallback() remapping" note below. |
| `fallback()` | ⚠️ | Retains the name `fallback` in the manifest. No EVM-style unknown-method dispatch; compiler emits diagnostic W105 suggesting `onNEP17Payment()`. |
| `virtual` / `override` | ✅ | Inheritance flattening resolves overrides; multi-level chains supported |
| Function selectors (`.selector`) | ✅ | Computed from canonical parameter types |
| NatSpec comments | ✅ | `@notice`, `@dev`, `@param`, `@return` preserved in metadata |
---
## E. OOP Features
| Single inheritance | ✅ | C3 linearization with `flatten_contract_inheritance` |
| Multiple inheritance | ✅ | Diamond inheritance detected; constructor arg conflicts reported |
| `interface` | ✅ | Interface types tracked; methods validated |
| `abstract contract` | ✅ | Fully validated; unimplemented functions detected; non-abstract contracts get actionable errors |
| `library` | ⚠️ | Builtin devpack libraries are compiler intrinsics; user-defined libraries are merged/inlined, but still cannot model deployable library state or linking |
| `using X for Y` | ✅ | Library member-call syntax fully supported; `using X for *` and `using {f,g} for T` included |
| `super` keyword | ✅ | Supported via inheritance flattening with `__super_` method preservation |
| `is` (inheritance) | ✅ | Inheritance specifiers fully processed |
| Constructor chaining | ✅ | Base constructor arguments resolved from inheritance specifiers |
| Event inheritance | ✅ | Interface events collected recursively via `collect_interface_events_recursive` |
---
## F. Storage & Memory
| State variables | ✅ | Mapped to Neo Storage with prefix-based keys |
| `constant` | ✅ | Compile-time constants inlined |
| `immutable` | ✅ | Tracked via `is_immutable` flag; modification blocked at compile time |
| `memory` keyword | ✅ | Parsed; NeoVM is stack-based so memory is implicit |
| `storage` keyword | ✅ | Storage references for mappings and state variables |
| `calldata` keyword | ✅ | Parsed; treated as `memory` (correct for NeoVM — no calldata region) |
| Nested mappings | ✅ | `mapping(K1 => mapping(K2 => V))` with composite storage keys |
| Struct in storage | ✅ | Serialized/deserialized via `StdLib.serialize`/`StdLib.deserialize` |
| Array `.push()` / `.pop()` | ✅ | Storage array operations supported |
| Array `.length` | ✅ | Both memory and storage arrays |
| `new bytes(n)` / `new string(n)` | ✅ | Buffer allocation via `NEWBUFFER` |
| `new T[](n)` | ✅ | Dynamic array allocation via `NEWARRAY` |
| `new Contract(...)` | ⚠️ | Does not deploy a child contract; constructor-like logic is inlined/simulated and a zero-address placeholder is produced. Use `ContractManagement.deploy(...)` for real deployment |
---
## G. Error Handling
| `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 definitions (`error X(...)`) | ✅ | Parsed and used in revert statements |
| `try` / `catch` | ✅ | NeoVM TRY/ENDTRY structured exception handling |
| `try` with return binding | ✅ | `try f() returns (uint r) { ... }` supported |
| Multiple catch clauses | ✅ | Lowered with EVM-canonical selector guards for `Error(string)` and `Panic(uint256)`; user-defined named catches retain legacy guards |
---
## H. EVM-Specific Features (with Neo Alternatives)
| `msg.sender` | ✅ | Maps to `Runtime.GetCallingScriptHash()`; internal/self-offset runtime paths may inject a direct-caller override |
| `msg.value` | ⚠️ | Only mapped inside `onNEP17Payment` callback |
| `msg.data` | ⚠️ | Approximated as selector plus encoded current args outside onNEP17Payment |
| `msg.sig` | ⚠️ | Approximated as the current function selector; internal-call propagation still differs from EVM |
| `block.timestamp` | ✅ | Maps to `Runtime.GetTime()` (normalized to seconds) |
| `block.number` | ✅ | Maps to `Ledger.CurrentIndex()` |
| `block.chainid` | ✅ | Maps to Neo network magic number |
| `block.coinbase` | ✅ | Auto-mapped to `address(0)` with warning (dBFT has no miner) |
| `block.difficulty` / `block.prevrandao` | ✅ | Auto-mapped to `Runtime.getRandom()` with warning |
| `block.gaslimit` | ✅ | Auto-mapped to `Policy.getExecFeeFactor()` with warning |
| `block.basefee` | ✅ | Auto-mapped to `Policy.getFeePerByte()` with warning |
| `tx.origin` | ⚠️ | Parsed; warning about authorization risks |
| `tx.gasprice` | ✅ | Auto-mapped to `Policy.getFeePerByte()` with warning |
| `gasleft()` | ✅ | Auto-mapped to `System.Runtime.GasLeft` syscall |
| `blockhash(n)` | ✅ | Auto-mapped to `Ledger.getBlockHash()` with warning |
| `block.sha3` | ⚠️ | Auto-mapped to `Ledger.currentHash`; deprecated in Solidity 0.8+ |
| `keccak256(...)` | ✅ | Maps to `CryptoLib.keccak256` |
| `sha256(...)` | ✅ | Maps to `CryptoLib.sha256` |
| `ecrecover(...)` | ✅ | Uses `CryptoLib.recoverSecp256K1`, then derives the Ethereum address with `keccak256` and `RIGHT 20` |
| `selfdestruct(addr)` | ✅ | Auto-mapped to `ContractManagement.destroy()` with warning |
| `address.call(...)` | ⚠️ | Maps to `System.Contract.Call`; return wrapping and ABI payload parity differ from EVM and need Neo-Express validation |
| `address.staticcall(...)` | ⚠️ | Maps to `System.Contract.Call` (read-only flag); return wrapping and ABI payload parity differ from EVM |
| `address.delegatecall(...)` / `callcode(...)` | 🚫 | Blocked at compile time; Neo N3 has no equivalent caller-storage execution semantics |
| `address.transfer(amount)` | ✅ | Auto-mapped to `GAS.transfer(from,to,amount,data)`; aborts on transfer fail |
| `address.send(amount)` | ✅ | Auto-mapped to `GAS.transfer(from,to,amount,data)`; returns bool |
| `address.balance` | ✅ | Auto-mapped to `GAS.balanceOf(address)` |
| `address.code` | ⚠️ | Returns Neo contract script bytes; non-contracts return empty bytes. `address.code.length` still maps to a contract-existence check |
| `address.codehash` | ✅ | Auto-mapped to contract script hash with warning; non-contract → 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` keyword | ✅ | Maps to `Runtime.GetExecutingScriptHash()`; correct Neo equivalent |
| `type(X).creationCode` | ⚠️ | Compiler-emitted deterministic NEF3-shaped payload for hashing compatibility; not EVM bytecode and requires an in-graph type |
| `type(X).runtimeCode` | ⚠️ | Same deterministic Neo-shaped payload model as `creationCode`; not production bytecode introspection |
---
## I. ERC to NEP Protocol Mapping
| ERC-20 (Fungible Token) | NEP-17 | ✅ | Auto-detected; `transfer(to,amount)` warns to use 4-param NEP-17 form |
| ERC-721 (NFT) | NEP-11 | ✅ | Auto-detected; `transferFrom` warns to use NEP-11 `transfer(to,tokenId,data)` |
| ERC-20 `approve`/`allowance` | N/A | ⚠️ | Warning: not part of NEP-17 spec; Neo uses `Runtime.checkWitness()` |
| ERC-165 `supportsInterface` | Manifest `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-24 | ✅ | Auto-detected; multiple royalty recipients supported |
| `receive()` / `fallback()` | `onNEP17Payment()` | ⚠️ | `receive()` silently remapped; `fallback()` kept as-is. See note below. |
### receive()/fallback() remapping
Neo N3 uses the `onNEP17Payment(address from, uint256 amount, bytes data)` callback rather than EVM-style `receive()` / `fallback()` dispatch. To keep Solidity source portable:
- A Solidity `receive() external payable { ... }` with no explicit `onNEP17Payment` in the contract is **silently remapped** in the manifest to `onNEP17Payment(address,uint256,bytes)`. The body is preserved verbatim; the ABI name, parameters, and selector are rewritten (see `src/solidity/convert/functions.rs:32`).
- If the contract **already declares** an explicit `onNEP17Payment`, `receive()` keeps the name `receive` in the manifest (no remap), and diagnostic `W105` flags it as having no effect on Neo N3.
- `fallback()` is never remapped and keeps its Solidity name in the manifest; there is no Neo equivalent to EVM fallback dispatch, so diagnostic `W105` suggests `onNEP17Payment` instead.
**Migration guidance for Ethereum developers**: if your contract needs to accept tokens, prefer declaring `onNEP17Payment(address from, uint256 amount, Any data)` directly or inherit the devpack [EVM Compatibility Layer](/additional-material/neo-devpack/evm-compatibility-layer). It surfaces the sender, amount, token contract, and attached data that Neo's NEP-17 transfer provides. Use `receive()` only if you are porting EVM source and accept that tooling will see the entrypoint under the Neo NEP-17 name.
---
## Summary
| A. Types | 16 | 2 | 2 | 0 |
| B. Expressions | 16 | 5 | 0 | 0 |
| C. Statements | 17 | 1 | 0 | 0 |
| D. Functions | 9 | 4 | 0 | 0 |
| E. OOP Features | 9 | 1 | 0 | 0 |
| F. Storage & Memory | 12 | 1 | 0 | 0 |
| G. Error Handling | 11 | 0 | 0 | 0 |
| H. EVM-Specific | 21 | 11 | 0 | 1 |
| I. ERC-NEP Mapping | 3 | 4 | 0 | 0 |
| **Total** | **114** | **29** | **2** | **1** |
**Total features audited: 146**
- ✅ Fully supported: 114 (78%)
- ⚠️ Partial support: 29 (20%)
- ❌ Not supported: 2 (1%)
- 🚫 Intentionally blocked: 1 (1%)