Documentation Index
8 min readChapter 30

30. Advanced Access Control

Access control is the foundation of smart contract security. In Quorlin and the Kortana Virtual Machine (KVM), authorization mechanisms govern which entities (externally owned accounts or other smart contracts) can trigger state-changing methods, modify contract storage, or reconfigure operational parameters.

Quorlin's design philosophy combines English-like, readable syntax with strict compiler-level enforcement. By using explicit function mutability keywords (reads and writes), built-in execution context primitives like caller, and robust assertion statements via require, developers can construct secure, multi-layered access control systems.

This chapter explores advanced access control architectures in Quorlin—ranging from single-owner patterns and fine-grained Role-Based Access Control (RBAC) to defensive state-locking mechanisms and low-level KVM authorization execution.


30.1 Fundamental Authorization Primitives

Before building complex access topologies, it is essential to understand the primitive building blocks provided by the Quorlin compiler (quorlin/sema.cpp, quorlin/lexer.cpp) and the Kortana Virtual Machine runtime environment (kvm/interpreter.cpp, kvm/state_host.cpp).

The caller Built-in Context Variable

In Quorlin, the built-in expression caller represents the 160-bit (20-byte) address of the account or contract that initiated the current function call. During semantic analysis, the compiler evaluates caller as Type::Address.

At the virtual machine level, when a function execution is invoked, the KVM loads the execution context into memory and registers. The address of the caller is supplied via the block and transaction context host (StateHost).

contract OwnershipGuard { address owner; constructor { // Assign the deployer of the contract as the initial owner owner = caller; } reads address getOwner() { return owner; } }

Truth Assertions with require

Authorization checks rely heavily on conditional guard clauses. Quorlin provides the require statement, which checks a boolean expression (truth). If the condition evaluates to no (false), execution halts immediately, all state modifications made during the transaction are reverted, and a failure string is returned.

writes truth verifyAccess(address user) { require user == caller, "Caller address mismatch"; return yes; }

Under the hood, the code generator (quorlin/codegen.cpp) translates a failed require check into a conditional jump (j_form or conditional branch opcode) to an error-handling block that issues the KVM Revert opcode.


30.2 Single-Ownership Patterns and Secure Governance Transfers

The most common access control structure is single-ownership, where a designated administrative address holds privileged execution rights over key contract routines.

Standard Ownership Transfer Vulnerabilities

A common mistake in simple contracts is directly updating the owner variable in a single step. If the administrative address is accidentally transferred to an invalid or unmanaged address, control of the contract is permanently lost.

Secure Two-Step Ownership Handshake

To prevent permanent lockouts, advanced Quorlin contracts implement a two-step transfer pattern:

  1. The current owner nominates a pending owner.
  2. The pending owner must explicitly call a claiming function to accept ownership.
contract ManagedAccess { address owner; address pendingOwner; event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner); event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); constructor { owner = caller; } // Helper method to guard administrative functions writes truth checkOwner() { require caller == owner, "Unauthorized: caller is not owner"; return yes; } writes truth proposeOwner(address newOwner) { checkOwner(); require newOwner != 0x0000000000000000000000000000000000000000, "Invalid address"; pendingOwner = newOwner; emit OwnershipTransferStarted(owner, newOwner); return yes; } writes truth claimOwnership() { require caller == pendingOwner, "Unauthorized: caller is not pending owner"; address oldOwner = owner; owner = pendingOwner; pendingOwner = 0x0000000000000000000000000000000000000000; emit OwnershipTransferred(oldOwner, owner); return yes; } }

30.3 Role-Based Access Control (RBAC)

For complex decentralised applications, single-ownership is insufficient. Role-Based Access Control (RBAC) enables contracts to distribute administrative privileges across distinct categories such as ADMIN, MINTER, PAUSER, or OPERATOR.

Implementing Storage Maps for Roles

Quorlin supports key-value mapping structures (map<KeyType, ValueType>). We can structure multi-role maps by mapping addresses to specific boolean (truth) permissions or numeric bitmasks.

Here is an explicit implementation of a dynamic RBAC system using nested mapping patterns:

contract AccessRegistry { // Define numerical role identifiers // 1: Admin, 2: Minter, 3: Burner map<number, map<address, truth>> roleMembers; map<address, truth> superAdmins; event RoleGranted(number indexed role, address indexed account, address indexed sender); event RoleRevoked(number indexed role, address indexed account, address indexed sender); constructor { superAdmins[caller] = yes; } reads truth hasRole(number role, address account) { return roleMembers[role][account]; } reads truth isSuperAdmin(address account) { return superAdmins[account]; } writes truth grantRole(number role, address account) { require superAdmins[caller] == yes, "Caller must be super admin"; require account != 0x0000000000000000000000000000000000000000, "Cannot grant to zero address"; roleMembers[role][account] = yes; emit RoleGranted(role, account, caller); return yes; } writes truth revokeRole(number role, address account) { require superAdmins[caller] == yes, "Caller must be super admin"; roleMembers[role][account] = no; emit RoleRevoked(role, account, caller); return yes; } // Example restricted function for a minter role writes truth mintTokens(address target, number amount) { // Role ID 2 represents MINTER require roleMembers[2][caller] == yes, "Caller lacks MINTER role"; // Execute minting state updates... return yes; } }

30.4 State Mutability Constraints: reads vs writes

Quorlin forces static semantics on contract methods via the explicit usage of reads (non-mutative/view methods) and writes (state-mutative methods).

                 +-----------------------------------+
                 |         Function Call             |
                 +-----------------------------------+
                                   |
         +-------------------------+-------------------------+
         |                                                   |
  Declared as `reads`                               Declared as `writes`
         |                                                   |
+---------------------------------+                +---------------------------------+
| Semantic Analyzer (sema.cpp)   |                | Semantic Analyzer (sema.cpp)   |
| Ensures NO storage writes occur |                | Permits read/write operations   |
+---------------------------------+                +---------------------------------+
         |                                                   |
+---------------------------------+                +---------------------------------+
| KVM Execution Context           |                | KVM StateHost Execution         |
| Reads storage via `get_storage` |                | Executes `set_storage` /        |
| Denies state-modifying opcodes  |                | `put_storage` modifications     |
+---------------------------------+                +---------------------------------+
  1. reads Enforcement: Functions marked as reads can perform lookup operations (get_storage), evaluate conditional checks, and compute values, but they are prevented from altering persistent storage slots or firing events.
  2. writes Enforcement: Functions marked as writes are granted explicit permission to perform state mutations (set_storage), emit topics, and update contract storage.

An authorization check inside a reads function can safely verify permissions without modifying contract state or triggering gas-heavy write operations.


30.5 Defensive Mutability Protection and State Reentrancy Locks

Advanced access control extends beyond identity verification; it also governs execution timing. A critical access control risk in smart contract development is reentrancy, where an external contract call recursively re-enters a writes method before the primary execution stack finishes clearing.

Quorlin allows developers to build efficient state locks using simple persistent flags (truth).

contract VaultGuard { number totalVaultBalance; map<address, number> balances; truth locked; event Deposit(address indexed user, number amount); event Withdraw(address indexed user, number amount); constructor { locked = no; } // Reentrancy lock modifier logic writes truth acquireLock() { require locked == no, "ReentrancyGuard: reentrant call detected"; locked = yes; return yes; } writes truth releaseLock() { locked = no; return yes; } writes truth withdraw(number amount) { acquireLock(); number userBalance = balances[caller]; require userBalance >= amount, "Insufficient balance"; // Update internal accounting state BEFORE state transitions or transfers balances[caller] = userBalance - amount; totalVaultBalance = totalVaultBalance - amount; emit Withdraw(caller, amount); // State lock released at end of execution releaseLock(); return yes; } }

30.6 KVM Execution and Gas Architecture for Access Guards

Access control assertions operate directly on KVM instruction streams. Understanding how checks are compiled helps optimize gas consumption and performance.

Compiler Instruction Generation

Consider an explicit check against caller:

require caller == owner, "Unauthorized";

The Quorlin semantic analyzer (sema.cpp) and code generator (codegen.cpp) process this construct by emitting specific opcodes:

  1. Opcode::Caller: Pushes the current transaction invoker's 20-byte address onto a KVM evaluation register.
  2. Opcode::MLoad or Register Load: Retrieves the word stored in the storage slot allocated for owner.
  3. Opcode::Eq: Compares the two registers for equality.
  4. Opcode::JmpIf / Branching Logic: Evaluates the boolean condition. If equal (1/true), execution proceeds to the next instruction index. If unequal (0/false), it jumps directly to the revert section.
  5. Opcode::Revert: Aborts execution, unwinds pending state mutations, and passes the offset of the failure string back to the runtime host.

Gas Schedule Optimization

In the KVM gas model (kvm/gas.hpp), authorization checks incur minimal base costs:

  • Opcode Comparisons (Eq, Ne, Lt): Classified under kGasVeryLow (3 gas units per ALU cycle).
  • Storage Lookups (SLoad / Trie Reads): Loading warm state variables (like owner) consumes minimal gas compared to cold storage writes.

Byplacing authorization checks at the very beginning of functions, failed calls abort immediately. This design minimizes gas expenditure for invalid calls before any expensive computation or state modification occurs.


30.7 Summary Checklist for Access Control Architecture

When designing advanced access control layers for Quorlin contracts, ensure your implementation adheres to these core principles:

Design PatternSecurity ObjectiveQuorlin Feature
Early RejectionExecute permission guards before mutating state variables.require caller == owner, "Msg";
Two-Step OwnershipPrevent accidental contract lockouts due to typos or dead addresses.Handshake methods using pendingOwner variables.
Explicit MutabilityRestrict state-modifying opcodes using function declaration keywords.Use reads for pure state queries and writes for state changes.
State LockingSafeguard complex inter-contract workflows against recursive execution.Mutex pattern using persistent truth locked; flags.
Role PartitioningAvoid single-point-of-failure administration by separating capabilities.Nested role mappings (map<number, map<address, truth>>).