# How ApplicationEngine Executes Smart Contracts and Manages Execution Context in NEO

> Discover how the NEO ApplicationEngine executes smart contracts and manages execution context with isolated contexts, gas metering, and atomic state commits. Learn more about this virtual machine.

- Repository: [The Neo Project/neo](https://github.com/neo-project/neo)
- Tags: internals
- Published: 2026-03-08

---

**The NEO ApplicationEngine extends the base ExecutionEngine to provide a deterministic, gas-metered virtual machine that loads smart contracts into isolated ExecutionContexts, dispatches opcodes through hard-fork-aware jump tables, and atomically commits state changes when contexts unload.**

The `ApplicationEngine` class in the [neo-project/neo](https://github.com/neo-project/neo) repository is the core component responsible for smart contract execution. It inherits from the generic `ExecutionEngine` and adds NEO-specific functionality including system call handling, GAS accounting, and cross-contract invocation management.

## Engine Initialization and Hard-Fork Configuration

Execution begins with the static `Create` method in [`src/Neo/SmartContract/ApplicationEngine.cs`](https://github.com/neo-project/neo/blob/main/src/Neo/SmartContract/ApplicationEngine.cs) (line 91). This factory method constructs an engine instance configured for a specific trigger type (such as `Application` or `Verification`), initializes the protocol settings, and sets the GAS limit for the transaction.

The engine selects the appropriate **jump table** based on the current hard-fork. Two static tables are composed at initialization:

- `DefaultJumpTable`: Contains standard NEO behavior for the current protocol version
- `NotEchidnaJumpTable`: Used before the **Echidna** hard-fork for backward compatibility

These tables map VM opcodes to their handler methods, ensuring deterministic execution across network upgrades.

## Loading Scripts and Creating Execution Contexts

Before execution, the engine must load the target bytecode into an `ExecutionContext`. The `LoadScript` method (line 384) handles this by:

1. Creating a new `ExecutionContext` for the entry script
2. Cloning the snapshot cache to isolate state changes
3. Optionally configuring the initial state
4. Delegating to `LoadContext` for registration

The `LoadContext` method (line 330) assigns a unique script hash to the context, registers it in the `invocationCounter` dictionary to track reentrancy, and notifies the diagnostic subsystem that a new context has entered.

## Executing Bytecode and Gas Accounting

Once contexts are loaded, the base `ExecutionEngine` runs the main execution loop. Before each instruction executes, `PreExecuteInstruction` (line 26) adds the opcode's GAS cost to the running total using the price table. After execution completes, `PostExecuteInstruction` informs diagnostic observers.

The `AddFee` method (line 29) manages the actual GAS accounting:

- Accumulates pico-GAS into `_feeConsumed`
- Respects whitelist contracts that may bypass fees
- Throws `InvalidOperationException` when the GAS limit is exhausted

This ensures that every computational step has a deterministic cost, preventing denial-of-service attacks through infinite loops or heavy computation.

## Handling System Calls and Interop Services

When the VM encounters the `SYSCALL` opcode, the jump table routes execution to `OnSysCall` (line 102). This method:

1. Verifies the required `CallFlags` against the current context's permissions
2. Charges the fixed interop price defined in the `InteropDescriptor`
3. Converts stack arguments to the expected types
4. Invokes the registered handler delegate
5. Pushes any return value onto the evaluation stack

The `InteropDescriptor` class defines native services including storage operations, cryptographic functions, and runtime information, each with specific GAS costs and permission requirements.

## Cross-Contract Calls and Context Management

Smart contracts can invoke other contracts through the `CALLT` opcode, handled by `OnCallT` (line 78). This extracts the method token from the NEF format, validates call flags, pops arguments from the stack, and forwards to `CallContractInternal` (line 556).

`CallContractInternal` performs the heavy lifting for cross-contract invocation:

- Looks up the target contract in `ContractManagement`
- Verifies the contract isn't blocked by policy
- Checks the caller's permissions against the method's `CallFlags`
- Updates the per-contract `invocationCounter` to prevent infinite recursion
- Creates a new `ExecutionContext` via `LoadContract` (line 447)

`LoadContract` initializes the context with the contract's NEF script, sets the appropriate call flags and script hash, and performs a shallow copy of the contract state. If the contract defines an `_initialize` method, it is also prepared for execution.

When a context completes, `ContextUnloaded` (line 336) handles cleanup:

- Commits the snapshot cache to persist state changes
- Aggregates notification counts for diagnostics
- Handles return values for cross-contract calls
- Resolves any awaiting native-contract tasks

## Summary

The NEO ApplicationEngine provides a robust, deterministic environment for smart contract execution through:

- **Hard-fork aware initialization** that selects appropriate jump tables based on protocol version
- **Isolated ExecutionContexts** that separate state between contracts and track invocation depth
- **Per-instruction GAS metering** via `PreExecuteInstruction` and `AddFee` to prevent resource exhaustion
- **Secure interop handling** through `OnSysCall` with explicit permission checks and fixed pricing
- **Atomic state commitment** when contexts unload, ensuring consistency across complex multi-contract transactions

## Frequently Asked Questions

### How does ApplicationEngine handle gas consumption during execution?

The engine tracks GAS consumption through the `AddFee` method in [`ApplicationEngine.cs`](https://github.com/neo-project/neo/blob/main/ApplicationEngine.cs) (line 29), which accumulates pico-GAS costs into `_feeConsumed`. Before each instruction executes, `PreExecuteInstruction` charges the opcode's price from the price table, while `OnSysCall` adds fixed interop costs. If the accumulated fees exceed the gas limit provided to `Create`, the engine throws an `InvalidOperationException` to halt execution.

### What is the difference between LoadScript and LoadContract?

`LoadScript` (line 384) creates an `ExecutionContext` for arbitrary bytecode, typically used for the initial entry script or transaction scripts, and clones the snapshot cache to isolate state. `LoadContract` (line 447) specifically handles NEF-formatted smart contracts, setting the contract's script hash, call flags, and state, and optionally preparing the `_initialize` method. While `LoadScript` is general-purpose, `LoadContract` enforces contract-specific validation and metadata handling.

### How does the jump table support hard-fork upgrades?

The ApplicationEngine composes static jump tables at initialization via `ComposeDefaultJumpTable`, creating both `DefaultJumpTable` for current behavior and `NotEchidnaJumpTable` for pre-Echidna hard-fork compatibility. These tables map opcodes to handler methods like `OnSysCall` and `OnCallT`. When `Create` instantiates the engine, it selects the appropriate table based on the current protocol settings, ensuring that opcode behavior remains consistent for historical blocks while allowing new features in newer blocks.

### What happens when a smart contract calls another contract?

When a contract executes the `CALLT` opcode, the jump table routes to `OnCallT` (line 78), which extracts the method token and validates permissions before calling `CallContractInternal` (line 556). This method looks up the target contract, checks that it isn't blocked, verifies the caller's `CallFlags` permissions, increments the `invocationCounter` to prevent infinite recursion, and creates a new `ExecutionContext` via `LoadContract`. Arguments are pushed onto the new context's stack, and execution continues in the called contract until it returns, at which point `ContextUnloaded` handles the return value and state commitment.