Parity and Limitations
This page documents the current state of parity between the Neo Solidity compiler's embedded runtime and the production Neo N3 virtual machine, known limitations, and planned improvements.
What "Parity" Means
Parity refers to how closely the embedded NeoVM runtime in neo-solc matches the behavior of the official Neo N3 reference implementation. Full parity means identical behavior for all opcodes, syscalls, gas accounting, and edge cases.
The embedded runtime is designed for local testing and development. It prioritizes correctness of core functionality over exhaustive edge-case coverage. For production validation, always test on Neo-Express or testnet.
Current Status Summary
As of January 2026, the embedded runtime passes 320+ tests and is considered production-ready for core functionality.
| Category | Status | Accuracy |
|---|---|---|
| Opcode execution | Complete | Full Neo N3 opcode suite |
| Stack operations | Complete | All stack/slot operations |
| Arithmetic and logic | Complete | Including MODMUL, MODPOW, SQRT |
| Control flow | Complete | All jump variants, CALL/CALLA/CALLT |
| Exception handling | Complete | TRY/CATCH/FINALLY with rethrow |
| Storage syscalls | Complete | Get/Put/Delete/Find with iterators |
| Runtime syscalls | Complete | Platform, network, time, gas, notifications |
| Crypto syscalls | Complete | SHA256, RIPEMD160, Keccak256, Murmur32, CheckSig |
| Gas accounting | ~85% accurate | Per-opcode and per-syscall tables |
| Iterator handling | Functional | Materialized (not streaming) |
| Native contracts | Partial | Core methods implemented, stubs for others |
| Blockchain accessors | Partial | Placeholder/stub data |
Known Gaps by Priority
P1 -- High Priority (Correctness)
Exception Handling Stack Unwinding
Stack unwinding during exception propagation and the associated gas effects need verification against the Neo N3 specification. The current implementation handles the common cases correctly, but edge cases involving nested try/catch/finally blocks with complex stack states may diverge.
Impact: Contracts with deeply nested exception handling may behave differently in production.
Workaround: Test exception-heavy code paths on Neo-Express.
Gas Precision for Complex Operations
Gas accounting is approximately 85% accurate. The main gaps are:
- Dynamic costs for large integer operations (the runtime uses fixed costs instead of size-dependent costs)
- Complex compound operations where gas depends on collection size
- Some syscall costs are approximations of the Neo N3 fee schedule
Impact: Gas estimates from the embedded runtime may undercount or overcount by up to 15% for complex operations.
Workaround: Use Neo-Express for accurate gas measurement before mainnet deployment. Budget a safety margin of 20% above embedded runtime estimates.
P2 -- Medium Priority (Performance and Spec Compliance)
Iterator Streaming
Storage.Find currently materializes all matching entries into memory before returning the iterator. The production Neo N3 implementation uses lazy streaming, loading entries on demand.
Impact: Functional correctness is not affected. Memory usage may be higher than production for large result sets. Performance characteristics differ for contracts that iterate over many storage entries.
Workaround: None needed for correctness. Be aware that memory usage in tests may not reflect production behavior for large datasets.
ByteString vs Buffer Type Distinction
The embedded runtime treats both ByteString and Buffer as generic byte arrays. In the Neo N3 specification, these are distinct types with different mutation semantics:
ByteStringis immutable (copy-on-write)Bufferis mutable (in-place modification)
Impact: Code that relies on the distinction between mutable and immutable byte sequences may behave differently. Most Solidity-compiled contracts do not depend on this distinction.
Workaround: Avoid relying on ByteString immutability guarantees in tests. Verify byte mutation behavior on Neo-Express if your contract uses low-level byte operations.
P3 -- Low Priority (Nice to Have)
Blockchain Accessors
GetTransaction, GetBlock, and GetContract return placeholder/stub data rather than real blockchain state. The embedded runtime does not maintain a simulated blockchain.
Impact: Contracts that read blockchain metadata (block height, transaction data, other contract state) will receive fixed test values.
Workaround: Use Neo-Express for integration tests that depend on blockchain state.
Additional Hash Functions
The runtime implements SHA256, RIPEMD160, Keccak256, and Murmur32. Additional hash functions may be needed if the Neo N3 CryptoLib native contract surface expands.
Impact: None for current contracts. Future Neo N3 protocol updates may add hash functions not yet supported.
Native Contract Method Surface
The full method surface of Policy, ContractManagement, and Ledger native contracts is not completely implemented. Core methods (Deploy, Update, GetContract, fee queries) work; less common methods return stub values.
Impact: Contracts calling uncommon native contract methods may receive incorrect results in tests.
Workaround: Test native contract interactions on Neo-Express.
Completed Items
The following items were previously tracked as gaps and have been resolved:
- CheckSig / CheckMultisig -- Real secp256k1 verification with DER and compact signature support
- Storage syscalls -- Complete Get/Put/Delete/Find with iterator token disposal
- Runtime syscalls -- Platform, network, time, gas, notifications, checkWitness
- Crypto syscalls -- SHA256, RIPEMD160, Keccak256, Murmur32, Hash160, Hash256
- All opcodes -- Full Neo N3 opcode suite with proper stack effects
- Gas accounting -- Per-opcode and per-syscall tables with ~85% spec accuracy
Compiler-Level Limitations
These are not runtime parity issues but fundamental limitations of the Solidity-to-NeoVM compilation target.
Blocked EVM Features
The following Solidity/EVM features are not supported and will produce E3001 UnsupportedFeature errors:
| Feature | Reason |
|---|---|
delegatecall | No equivalent in NeoVM execution model |
staticcall | NeoVM uses call flags instead |
Inline assembly (assembly { ... }) | EVM-specific opcodes |
selfdestruct | No equivalent in Neo N3 |
extcodesize / extcodecopy | No bytecode introspection in NeoVM |
CREATE2 | Neo uses deterministic contract hashing |
block.difficulty / block.prevrandao | Not available in Neo N3 |
tx.gasprice | Neo uses a different fee model |
Partial Features with Neo-Specific Semantics
| Feature | Neo Behavior |
|---|---|
msg.value | Not directly applicable; Neo uses NEP-17 token transfers |
| Function overloading | Supported with constraints on ABI name uniqueness |
| Dynamic arrays in storage | Must use mappings or fixed-size arrays |
receive() / fallback() | Mapped to Neo contract entry points |
Dynamic Call Sites and Wildcard Permissions
Contracts that use dynamic contract addresses (computed at runtime rather than hardcoded) require wildcard permissions in the manifest. This is a security concern because wildcard permissions grant the contract the ability to call any other contract.
Use the --deny-wildcard-* flags and --manifest-permissions to enforce explicit permission policies. See CLI Reference for details.
Intentionally Different Behaviors
Some behaviors are intentionally different from the production Neo N3 runtime:
| Behavior | Embedded Runtime | Production | Reason |
|---|---|---|---|
| Random numbers | Deterministic seed | Block-based entropy | Reproducible tests |
| Block timestamps | Fixed test value | Real timestamps | Reproducible tests |
| Network magic | Test value | Mainnet/testnet magic | Isolation |
| Contract hashes | Computed from test data | Computed from deployment | Test isolation |
| Oracle responses | Deterministic pseudo ID | Real oracle network | No external dependencies |
These differences are by design and ensure that tests are deterministic and reproducible without requiring a running Neo node.
Planned Improvements
The following improvements are planned but not yet scheduled:
- Gas precision tightening -- Align dynamic gas costs with the Neo N3 specification for large integer and collection operations
- Streaming iterators -- Replace materialized iterator implementation with lazy streaming
- ByteString/Buffer distinction -- Implement proper mutation semantics
- Differential testing framework -- Automated comparison of embedded runtime output against Neo-Express
- Fuzzing framework -- Property-based testing of opcode and syscall implementations
Reporting Issues
If you encounter a behavior difference between the embedded runtime and production Neo N3:
- Verify the behavior on Neo-Express to confirm it is a runtime parity issue
- Check this page to see if the gap is already documented
- Report the issue with a minimal reproduction case
Issue tracker: github.com/r3e-network/neo-solidity/issues
Include:
- The Solidity source code that demonstrates the issue
- Expected behavior (from Neo-Express or the Neo N3 specification)
- Actual behavior from the embedded runtime
- Compiler version (
neo-solc --version)
See Also
- Runtime Specification -- full runtime documentation
- Architecture -- compiler pipeline
- Error Reference -- diagnostic codes
- CLI Reference -- compiler options
- Troubleshooting -- common issues and solutions
