Documentation Index
10 min readChapter 17

17. Built-in Security Features

Smart contract development demands defensive design at every level of the stack. A single missing check, unhandled overflow, or unexpected state modification can lead to irreversible protocol loss. The Quorlin Smart Contract Language and the Kortana Virtual Machine (KVM) incorporate security directly into the language syntax, static analysis pipeline, module verification specs, and execution interpreter.

By making unsafe behavior syntactically distinct, statically analyzing every contract before bytecode generation, and strictly bounds-checking state and memory inside the KVM, Quorlin eliminates common smart contract vulnerabilities by construction.


17.1 Gated Compilation Pipeline and Analyzer Safeguards

A fundamental security guarantee in Quorlin is that bytecode generation is strictly gated by semantic analysis. In many language toolchains, code generators attempt best-effort emission even when static analysis produces non-fatal warnings or partially resolved types. Quorlin strictly rejects this pattern.

As implemented in quorlin/compiler.cpp, the compilation lifecycle follows a strict four-stage pipeline:

[ Source Text ] ──> 1. Lexer ──> 2. Parser ──> 3. Analyzer ──> 4. Code Generator ──> [ KVM Bytecode ]
                         │            │             │
                   Errors│      Errors│       Errors│ (Halt Gate)
                         ▼            ▼             ▼
                    [ Diagnostic Bag / Immediate Compilation Abort ]

The code generator (CodeGenerator) does not perform type checking, identifier resolution, or validation during execution. It assumes the Abstract Syntax Tree (AST) is mathematically and logically sound based on the Analyzer output:

// --- 3. Analyse -----------------------------------------------------------------------------// Analyzer analyzer{result.diagnostics}; const AnalysisResult analysis = analyzer.analyze(unit); // The important gate. The code generator does no checking of its own — it trusts that every // identifier resolves and every type agrees, exactly as the KVM interpreter trusts module // verification. Running it after a failed analysis would not produce a bad diagnostic; it would // produce a bad module. if (result.diagnostics.has_errors()) return result; // --- 4. Generate Bytecode -------------------------------------------------------------------// CodeGenerator generator{analysis, result.diagnostics};

If any diagnostic error occurs in the lexing, parsing, or semantic analysis phase, the compilation pipeline halts immediately. This prevents the generation of malformed bytecode or corrupt KVM binary modules (.kvm).


17.2 Explicit Function Mutability: reads vs writes

Quorlin makes contract mutability explicit at the grammar level. Functions must explicitly state whether they read state or write to state using the keywords reads and writes.

contract Vault { number totalDeposits; map<address, number> balances; // Read-only function: guarantees state cannot be altered reads number getBalance(address user) { return balances[user]; } // State-modifying function: explicitly allows state changes writes truth deposit(number amount) { require amount > 0, "deposit must be positive"; balances[caller] = balances[caller] + amount; totalDeposits = totalDeposits + amount; return yes; } }

Compiler Mapping to Mutability Flags

During static analysis (quorlin/sema.cpp and quorlin/standard.cpp), the compiler parses function attributes and maps them to internal mutability tiers:

  • reads Functions (Mutability::View): Evaluated in a read-only execution sub-context. Any AST node attempting state assignment (e.g., modifying state variables or mapping slots) triggers an analysis error.
  • writes Functions (Mutability::Mut): Allowed to execute state-modifying instructions, update storage slots via the StateHost, emit logs, and call external state-changing contracts.

By requiring every function declaration to specify reads or writes, developer intent is clear during code reviews, and accidental state modifications inside view logic are caught at compile time.


17.3 Checked Arithmetic and Signed Math Safety

Arithmetic vulnerabilities—such as integer underflows, overflows, and unhandled division by zero—are primary targets in smart contract exploits. Quorlin and the KVM handle arithmetic safety at both the compiler level and the execution engine level.

Standard vs. Wrapping Binary Operators

In quorlin/sema.cpp, arithmetic operators are classified into standard safe operations and explicit wrapping operations:

bool is_arithmetic(BinaryOp op) noexcept { switch (op) { case BinaryOp::Add: case BinaryOp::Sub: case BinaryOp::Mul: case BinaryOp::Div: case BinaryOp::Mod: case BinaryOp::AddWrap: case BinaryOp::SubWrap: case BinaryOp::MulWrap: // ... return true; default: return false; } }

Standard arithmetic operators (+, -, *) perform overflow and underflow checks. If an arithmetic result exceeds the bounds of a standard 256-bit word (u256 / number), execution halts immediately.

When explicit wrapping arithmetic is intended (for example, in cryptographic routines or custom ring-buffer indexes), developers must explicitly select wrapping operations (AddWrap, SubWrap, MulWrap). Unintended wrapping is impossible by default.

Signed Math Protections in KVM

Low-level signed arithmetic operations can trigger edge cases if implemented naively. The KVM signed arithmetic module (kvm/arith.cpp) explicitly guards against two's-complement overflow bugs:

uint256_t signed_div(const uint256_t& a, const uint256_t& b) noexcept { if (b.is_zero()) return uint256_t::zero(); // INT_MIN / -1. The true quotient is 2^255, which no signed 256-bit value can hold. // The EVM defines the result as INT_MIN rather than faulting, and a naive // negate-divide-negate would produce it by accident on some paths and not others. const uint256_t minimum = int_min(); if (a == minimum && b == ~uint256_t::zero()) return minimum; const bool a_negative = is_negative(a); const bool b_negative = is_negative(b); const uint256_t magnitude_a = a_negative ? negate(a) : a; const uint256_t magnitude_b = b_negative ? negate(b) : b; // ... }

Key signed math safety features in KVM:

  1. Division by Zero Safeguard: Division by zero automatically evaluates to 0 safely without faulting the host process process or causing undefined behavior.
  2. INT_MIN / -1 Boundaries: 0x8000...0000 / -1 evaluates safely to INT_MIN in compliance with strict 256-bit two's complement specifications, preventing CPU overflow exceptions in the underlying C++ host binary.

17.4 Memory Safety and Integer Truncation Mitigation

A common vector for virtual machine exploits involves supplying extremely large 256-bit integers as byte offsets or memory expansion lengths. If an execution engine truncates a 256-bit integer down to a host native integer (such as uint64_t) without validation, a value like $2^{64} + 8$ wraps around to 8, allowing an attacker to read or write out-of-bounds host memory.

Strict Offset Narrowing in KVM Interpreter

In kvm/interpreter.cpp, all 256-bit memory access inputs pass through explicit narrowing checks before touching internal memory buffers:

// Truncating to 64 bits would turn 2^64 + 8 into offset 8, allowing memory access // validation bypasses. KVM explicitly narrows and rejects unrepresentable inputs.

If a 256-bit offset or length value cannot be represented within native host memory bounds (SIZE_MAX), the interpreter treats the memory expansion request as invalid, preventing heap corruption and out-of-bounds pointer arithmetic.

Binary Header Verification

KVM executable modules (.kvm) are protected against binary tampering and malformed execution layout via rigid binary header validation in kvm/module.cpp:

   offset  size  field
   0       4     magic "KVM\0"
   4       2     version            (big endian)
   6       4     constant count     (big endian)
   10      4     instruction count  (big endian)
   14      4     entry point        (big endian, an instruction index)
   18      ...   constants (32 bytes each)
   ...     ...   code (4 bytes per instruction)

The binary parser checks:

  1. Magic Bytes Verification: Every compiled binary must start with the four magic bytes "KVM\0" (0x4B, 0x56, 0x4D, 0x00).
  2. Strict Big-Endian Encoding: All multi-byte header lengths, constant table counts, and opcode field structures use fixed big-endian byte layouts (write_u16, write_u32), eliminating endianness ambiguity across different server hardware architectures.

17.5 Gas Metering Dynamics and Malicious Abort Defenses

Gas metering guarantees that contracts execute within defined computational resource boundaries, preventing infinite loops and Denial of Service (DoS) attacks on the network.

Operation Pricing Categories

In kvm/gas.cpp, instructions are assigned baseline gas costs according to their computational complexity:

uint64_t base_cost(Opcode opcode, const params::GasSchedule& schedule) noexcept { switch (opcode) { // --- Free Ops: Termination instructions --- case Opcode::Stop: case Opcode::Return: case Opcode::Revert: return kGasZero; // --- INVALID: Consumes all remaining gas --- case Opcode::Invalid: return kGasZero; // Interpreter drains total remaining gas balance // --- VeryLow: Basic 256-bit ALU instructions --- case Opcode::Add: case Opcode::Sub: case Opcode::Lt: case Opcode::Gt: case Opcode::Eq: case Opcode::And: case Opcode::Or: case Opcode::Xor: case Opcode::Shl: case Opcode::Shr: case Opcode::Sar: case Opcode::MLoad: case Opcode::MStore: return kGasVeryLow; // --- Low: Complex math (Mul, Div, Mod) --- case Opcode::Mul: case Opcode::Div: case Opcode::Mod: return kGasLow; } }

Defense Against Malicious Execution Aborts

A critical security mechanism in kvm/gas.cpp is how invalid opcodes and assertions are penalized:

// `INVALID` consumes everything remaining rather than a fixed amount, so its base is zero // and the interpreter drains the counter. A fixed price would make deliberately aborting // cheaper than running out of gas, which is a refund by another name. case Opcode::Invalid: return kGasZero;

When an illegal opcode or an explicit assertion failure occurs, the KVM drains all remaining transaction gas. This design decision prevents attackers from using invalid opcode exceptions as cheap execution refunds or speculative state probing mechanisms.


17.6 Storage State Isolation and Dirty Writes

State modification in Quorlin occurs through a unified global state trie managed by StateHost (kvm/state_host.cpp). Storage slots are explicitly isolated, and writes are priced dynamically based on existing slot states:

Result<uint256_t> StateHost::set_storage(const Address& address, const uint256_t& key, const uint256_t& value) { // The previous value is returned so the interpreter can price the write without a second read: // filling an empty slot costs several times an overwrite, and it needs to know which this is. KORTANA_TRY_ASSIGN(const uint256_t previous, world_.get_storage(address, key)); KORTANA_TRY(world_.put_storage(address, key, value)); return previous; }

This structural separation in StateHost protects the system in two ways:

  1. State Re-entrancy Protection: State lookups and mutations route through world_ (WorldState), ensuring state changes are tracked in a unified trie.
  2. Accurate Gas Surcharges for Storage Expansion: By fetching previous slot values prior to mutation, the system accurately distinguishes between cheap overwrites (previous != 0) and expensive state allocations (previous == 0), preventing storage spam attacks.

17.7 Practical Security Patterns in Quorlin Syntax

Smart contract developers can combine Quorlin's native syntax with these built-in engine safeguards to write secure contracts:

contract EscrowVault { address owner; map<address, number> deposits; truth isLocked; event Deposited(address indexed user, number amount); event Withdrawn(address indexed user, number amount); constructor { owner = caller; isLocked = no; } // Guarded access modifier pattern writes truth setLock(truth status) { require caller == owner, "unauthorized: owner only"; isLocked = status; return yes; } writes truth deposit() { require isLocked == no, "vault is currently locked"; // Context keyword 'caller' guarantees identity binding deposits[caller] = deposits[caller] + value; emit Deposited(caller, value); return yes; } writes truth withdraw(number amount) { require isLocked == no, "vault is currently locked"; number currentBalance = deposits[caller]; // Checked arithmetic prevents withdrawing more than deposited require currentBalance >= amount, "insufficient balance"; // Update state BEFORE emitting events or triggering external interactions deposits[caller] = currentBalance - amount; emit Withdrawn(caller, amount); return yes; } reads number checkBalance(address user) { return deposits[user]; } }

Summary of Built-in Security Guarantees

Security AspectQuorlin / KVM Implementation MechanicsPrimary Threat Mitigated
Compilation GateStrict has_errors() check stops code generation (compiler.cpp).Bytecode corruption from invalid ASTs.
Mutability FlagsSyntactic reads vs writes mapped to View and Mut analysis (sema.cpp).Unintended state modification in views.
Arithmetic RulesChecked operations default; explicit AddWrap required (sema.cpp).Silent integer overflow and underflow attacks.
Signed Division GuardINT_MIN / -1 handled explicitly (kvm/arith.cpp).Undefined behavior and host CPU exception faults.
Memory Boundaries256-bit offsets narrowed safely without native wrapping (interpreter.cpp).Host memory corruption and buffer overflow exploits.
Header ValidationMagic bytes "KVM\0" and big-endian count checking (module.cpp).Executable binary tampering / malformed bytecode.
Gas PenaltiesOpcode::Invalid consumes 100% of remaining gas (gas.cpp).Cheap abort exploits and gas refund game vectors.
State AccessPre-write lookup via StateHost before trie mutation (state_host.cpp).Storage allocation spam and state trie bloat.