# Hardforks in Neo: How the Hardfork Enum Tracks Protocol Upgrades

> Discover how Neo hardforks, tracked by the Hardfork enum, enable deterministic protocol upgrades affecting validation rules and more. Understand this key feature of the Neo blockchain.

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

---

**Hardforks in Neo are deterministic, block-height-based protocol upgrades that modify validation rules, fee schedules, and VM semantics, tracked symbolically through the `Hardfork` enum and activated via `ProtocolSettings`.**

The **neo-project/neo** repository implements a structured hardfork system that allows the blockchain to evolve without breaking historical consensus. By mapping symbolic fork identifiers to specific activation heights, the protocol can conditionally enable new features while maintaining backward compatibility for legacy blocks.

## The Hardfork Enum: Symbolic Protocol Versions

At the core of Neo's upgrade mechanism is the **`Hardfork`** enum defined in [`src/Neo/Hardfork.cs`](https://github.com/neo-project/neo/blob/main/src/Neo/Hardfork.cs). This byte-backed enumeration provides a compact, canonical list of every named protocol upgrade.

### Enum Structure and Naming Convention

The enum uses a consistent naming pattern combining the `HF_` prefix with mythological creature names:

```csharp
public enum Hardfork : byte
{
    HF_Aspidochelone,
    HF_Basilisk,
    HF_Cockatrice,
    HF_Domovoi,
    HF_Echidna,
    HF_Faun,
    HF_Gorgon
}

```

Each entry represents a specific set of consensus rule changes. By using a **byte backing**, the enum minimizes storage overhead when serialized in block headers or configuration files.

## Configuring Activation Heights with ProtocolSettings

While the enum defines *what* forks exist, **`ProtocolSettings`** determines *when* they activate. Located in [`src/Neo/ProtocolSettings.cs`](https://github.com/neo-project/neo/blob/main/src/Neo/ProtocolSettings.cs), this class maintains the immutable configuration that governs node behavior.

### The Hardforks Dictionary

The activation mapping is stored as an immutable dictionary:

```csharp
public required ImmutableDictionary<Hardfork, uint> Hardforks { get; init; }

```

This structure maps each `Hardfork` enum value to a specific block height (as a `uint`). For example, `{ HF_Echidna → 1_000_000 }` indicates that the Echidna fork rules take effect at block 1,000,000.

### Validation and Default Behavior

The `ProtocolSettings` constructor enforces data integrity through several mechanisms:

- **`EnsureOmmitedHardforks`** (lines 38-50): Automatically populates missing fork entries with height `0`, treating them as *always enabled* for backward compatibility.
- **`CheckingHardfork`** (lines 55-80): Validates that the fork sequence is continuous and that activation heights are monotonically increasing, preventing configuration errors.

When initializing default settings, the constructor calls `EnsureOmmitedHardforks(new Dictionary<Hardfork, uint>())`, ensuring all forks default to height `0` unless explicitly overridden in [`protocol.json`](https://github.com/neo-project/neo/blob/main/protocol.json).

## Runtime Detection: IsHardforkEnabled

To conditionally execute logic based on the current block height, Neo provides the **`IsHardforkEnabled`** method in two flavors.

### ProtocolSettings Implementation

The base implementation in [`src/Neo/ProtocolSettings.cs`](https://github.com/neo-project/neo/blob/main/src/Neo/ProtocolSettings.cs) (lines 89-99) provides a pure function for height comparison:

```csharp
public bool IsHardforkEnabled(Hardfork hardfork, uint index)
{
    if (Hardforks.TryGetValue(hardfork, out uint height))
        return index >= height;
    return false;
}

```

This method returns `true` if the specified block index has reached or exceeded the configured activation height for the given fork.

### ApplicationEngine Wrapper

For smart contract execution contexts, [`src/Neo/SmartContract/ApplicationEngine.cs`](https://github.com/neo-project/neo/blob/main/src/Neo/SmartContract/ApplicationEngine.cs) (lines 41-51) provides a convenience wrapper:

```csharp
public bool IsHardforkEnabled(Hardfork hardfork)
{
    if (ProtocolSettings == null) return false;
    if (PersistingBlock is null) return ProtocolSettings.Hardforks.ContainsKey(hardfork);
    return ProtocolSettings.IsHardforkEnabled(hardfork, PersistingBlock.Index);
}

```

This version automatically uses the current `PersistingBlock.Index` when available, or checks for the fork's existence in configuration when executing outside of block persistence (such as during verification).

## Practical Implementation Examples

### Querying Protocol Settings Directly

When configuring a node or analyzing chain history, you can check fork status against any block height:

```csharp
// Load custom settings from protocol.json
var settings = ProtocolSettings.Load("protocol.json");

// Check if Echidna fork is active at block 1,200,000
bool echidnaActive = settings.IsHardforkEnabled(Hardfork.HF_Echidna, 1_200_000);
Console.WriteLine(echidnaActive);   // → true if configured height <= 1,200,000

```

### Conditional Logic in Smart Contract Execution

Native contracts use `ApplicationEngine.IsHardforkEnabled` to branch behavior:

```csharp
// Inside a native contract method
if (engine.IsHardforkEnabled(Hardfork.HF_Faun))
{
    // Apply new fee schedule introduced in Faun fork
    fee = ApplicationEngine.FeeFactor * MaxExecFeeFactor;
}
else
{
    // Maintain legacy behavior for pre-Faun blocks
    fee = MaxExecFeeFactor;
}

```

### Declaring Fork-Dependent Contract Methods

New native methods can be annotated to only appear after specific forks:

```csharp
// In a native contract class (e.g., StdLib.cs)
[ContractMethod(Hardfork.HF_Echidna, CpuFee = 1 << 5)]
public static void NewCryptographicFeature()
{
    // Implementation exposed only when HF_Echidna is active
}

```

The `ContractMethod` attribute ensures the VM only exposes this method when the specified hardfork is enabled, preventing premature usage.

## Summary

- **Hardforks in Neo** are deterministic protocol upgrades activated at specific block heights, allowing the blockchain to evolve without breaking historical consensus.
- The **`Hardfork` enum** in [`src/Neo/Hardfork.cs`](https://github.com/neo-project/neo/blob/main/src/Neo/Hardfork.cs) provides a byte-backed, symbolic identifier for each upgrade (e.g., `HF_Echidna`, `HF_Faun`).
- **`ProtocolSettings.Hardforks`** stores the immutable mapping of forks to activation heights, with automatic validation ensuring monotonic block numbers and continuous sequences.
- **`IsHardforkEnabled`** serves as the single source of truth for runtime checks, available both as a pure function in `ProtocolSettings` and as a context-aware wrapper in `ApplicationEngine`.
- Native contracts and the VM use these mechanisms to conditionally enable new opcodes, fee schedules, and methods only after their respective fork heights.

## Frequently Asked Questions

### What happens if a hardfork configuration is missing from protocol.json?

When a hardfork is omitted from the configuration file, the **`EnsureOmmitedHardforks`** method in `ProtocolSettings` automatically assigns it a height of `0`. This treats the fork as *always enabled*, ensuring backward compatibility for nodes that haven't updated their configuration files to include newer fork definitions.

### How does Neo prevent hardforks from activating out of order?

The **`CheckingHardfork`** validation method in `ProtocolSettings` (lines 55-80) enforces two critical constraints: first, that the sequence of forks is continuous without gaps in the enum order, and second, that activation heights are monotonically increasing. If a configuration attempts to set a later fork at a lower block height than an earlier fork, the node throws a configuration exception during startup.

### Can smart contracts directly query hardfork status during execution?

Yes, smart contracts running in the **ApplicationEngine** can query hardfork status through the `IsHardforkEnabled` method. This method automatically references the current `PersistingBlock.Index` to determine if a specific fork is active. Native contracts use this mechanism extensively to branch logic for fee calculations, cryptographic operations, and feature availability without requiring manual block height comparisons.

### What is the difference between ProtocolSettings and ApplicationEngine hardfork checks?

**`ProtocolSettings.IsHardforkEnabled`** is a pure function that accepts any block index as a parameter, making it suitable for configuration validation, historical analysis, and offline tools. In contrast, **`ApplicationEngine.IsHardforkEnabled`** is a context-aware wrapper that automatically uses the current execution block height (or checks configuration presence when no block is being persisted), making it ideal for smart contract execution and VM operations.