Documentation Index
8 min readChapter 12

12. Visibility and Modifiers

Smart contract safety relies heavily on strictly defined execution scopes and state mutability controls. In traditional contract languages, developers must navigate complex visibility matrices (public, private, internal, external) alongside variable state mutability declarations (view, pure, payable).

Quorlin redefines this paradigm. Designed to look like Java and read like plain English, Quorlin replaces complex access specifiers with a streamlined, context-explicit modifier system. By categorizing contract interactions into state-reading (reads) and state-modifying (writes) functions—combined with event-indexing controls—Quorlin enforces memory and storage guarantees at compile time long before bytecode reaches the Kortana Virtual Machine (KVM).


12.1 Overview of Access Control and Mutability

In Quorlin, top-level contract functions are externally exposed interface points for the contract. Rather than requiring developers to manually annotate both function visibility and state mutability separately, Quorlin unifies access semantics into state interaction intent:

  1. State Mutability Modifiers: reads and writes.
  2. Event Parameter Modifiers: indexed.
  3. Special Execution Contexts: constructor.

This explicit approach simplifies auditability. When reading Quorlin source code (.ql), a developer or security reviewer immediately knows whether a method is capable of mutating the global ledger state or if it is strictly a read-only query.


12.2 State Access Modifiers: reads and writes

Every standard function declaration inside a Quorlin contract must explicitly state its relationship with the contract's persistent storage by prefixing the signature with either reads or writes.

contract TokenVault { number totalDeposited; map<address, number> deposits; // Read-only method: inspects storage without modification reads number getDeposit(address account) { return deposits[account]; } // State-modifying method: updates persistent storage writes truth deposit() { number amount = caller.value; deposits[caller] = deposits[caller] + amount; totalDeposited = totalDeposited + amount; return yes; } }

12.2.1 The reads Modifier

The reads modifier marks a function as non-mutating. In the Quorlin compiler semantics (quorlin/sema.cpp) and standard library mapping (quorlin/standard.cpp), a reads function is categorized under Mutability::View.

Guarantees and Constraints

When a function is declared with reads:

  • Storage Access: Read operations from contract state variables and map data structures are fully permitted.
  • Storage Mutation Forbidden: The function cannot execute state assignments, write to storage slots, or modify map mappings.
  • Event Emissions Forbidden: The emit keyword is prohibited inside a reads context because emitting an event alters state logs on the Kortana ledger.
  • Sub-calls: A reads function can only invoke other reads functions on external contracts or standard interfaces.

Return Types

A reads function explicitly specifies its return type after the modifier keyword. The primary language types and their semantic representations include:

Quorlin KeywordInternal Type (Type)Standard ABI TypeDescription
numberType::U256uint256256-bit unsigned integer
truthType::BoolboolBoolean (yes / no)
addressType::Addressaddress20-byte Kortana address
textType::TextstringUTF-8 encoded text string
nothingType::VoidvoidReturn void / empty value
contract StakingInfo { number rewardRate; reads number calculateReward(address staker, number duration) { return duration * rewardRate; } }

12.2.2 The writes Modifier

The writes modifier explicitly authorizes a function to alter the contract's persistent storage state or trigger state log events. In the semantic analyzer, writes corresponds to Mutability::Mut.

Capabilities

Functions annotated with writes can perform all standard stateful operations:

  • Writing values to persistent storage variables and state maps.
  • Executing arithmetic operations that write updated balances back to storage.
  • Emitting logged contract events using the emit keyword.
  • Invoking external contract methods that execute state updates.
contract Governor { map<address, truth> hasVoted; number totalVotes; event Voted(address indexed voter, number voteCount); writes truth castVote(number weight) { require hasVoted[caller] == no, "already voted"; hasVoted[caller] = yes; totalVotes = totalVotes + weight; emit Voted(caller, weight); return yes; } }

12.3 Event Parameter Modifiers: indexed

Events in Quorlin enable contracts to output structured log data onto the Kortana blockchain. When defining an event signature, individual parameters can be annotated with the indexed modifier.

event Transfer(address indexed from, address indexed to, number amount);

12.3.1 Internal ABI Processing of indexed

During compiler execution, the ABI emitter (quorlin/abi.cpp) processes event parameter declarations. The helper function parameter_json serializes parameter details into an Ethereum-compatible JSON ABI format:

std::string parameter_json(std::string_view name, Type type, bool indexed, bool with_indexed) { std::string out = "{\"name\":" + quoted(name) + ",\"type\":" + quoted(abi_type_name(type)) + ",\"internalType\":" + quoted(type_name(type)); if (with_indexed) out += ",\"indexed\":" + std::string{indexed ? "true" : "false"}; return out + "}"; }

When with_indexed is set to true (as is the case for event parameters), the output JSON includes the key "indexed": true or "indexed": false.

12.3.2 Log Topic Generation

  • Indexed Parameters: Up to three parameters per event can be marked with indexed. Indexed arguments are hashed and stored directly as log topics in the execution output, enabling off-chain applications and indexers to efficiently search and filter ledger events.
  • Non-Indexed Parameters: Parameters without the indexed flag (such as number amount in the Transfer event) are encoded directly into the log data payload.

12.4 Special Function Contexts: constructor

The constructor block is a unique, un-named initialization block executed exactly once when a contract is instantiated on the Kortana network.

contract Token { number totalSupply; map<address, number> balances; constructor { totalSupply = 1000000; balances[caller] = 1000000; } }

Rules and Semantics

  1. Implicit Modifiers: The constructor block is inherently state-modifying (writes context) and cannot be marked with explicit reads or writes keywords.
  2. No Return Type: Unlike standard functions, a constructor cannot declare a return type.
  3. ABI Generation: As shown in quorlin/abi.cpp, the compiler automatically emits a standard constructor entry into the output ABI JSON array, even if the constructor accepts no arguments. This guarantees full compatibility with off-chain deployment tools expecting a standard contract ABI specification.

12.5 Compile-Time Enforcement and AST Semantic Analysis

The Quorlin toolchain operates a strict four-stage compilation pipeline (quorlin/compiler.cpp):

Source Code (.ql)
       │
       ▼
  1. Lexer        (quorlin/lexer.cpp)
       │
       ▼
  2. Parser       (quorlin/parser.cpp)
       │
       ▼
  3. Analyzer     (quorlin/sema.cpp)  ◄── "The Important Gate"
       │
       ▼
  4. CodeGen      (quorlin/codegen.cpp) ──► KVM Bytecode

Semantic Analysis Gate

The Analyzer class in quorlin/sema.cpp acts as the security boundary. The compiler enforces a strict rule: The code generator does no validation of its own. It relies entirely on the Analyzer stage passing without diagnostics.

If a developer attempts to modify a storage variable or emit an event within a reads function:

  1. The Analyzer inspects the active AST node and detects a mutability mismatch (Mutability::View versus state mutation).
  2. A diagnostic error is logged to the DiagnosticBag.
  3. Compilation halts immediately at Stage 3.
  4. No malformed KVM bytecode or invalid module headers (quorlin/module.cpp) are generated.

12.6 ABI Mapping and EVM Interoperability

To bridge Quorlin's human-readable syntax with the Ethereum Virtual Machine / KVM binary standards, the compiler translates internal types and function modifiers into standard Ethereum ABI strings (quorlin/parser.cpp and quorlin/abi.cpp).

12.6.1 Type Translation Engine

The function abi_type_name maps Quorlin's native source types into standard EVM ABI signatures used to calculate 4-byte function selectors:

std::string_view abi_type_name(Type type) noexcept { switch (type) { case Type::U256: return "uint256"; case Type::Bool: return "bool"; case Type::Address: return "address"; case Type::Text: return "string"; case Type::Void: return "void"; case Type::Invalid: return "<invalid>"; } return "<unknown>"; }

Conversely, type_name yields the developer-facing Quorlin syntax used in diagnostics:

std::string_view type_name(Type type) noexcept { switch (type) { case Type::U256: return "number"; case Type::Bool: return "truth"; case Type::Address: return "address"; case Type::Text: return "text"; case Type::Void: return "nothing"; case Type::Invalid: return "<invalid>"; } return "<unknown>"; }

12.6.2 Standard Interface Signatures

Because function selectors derive directly from standardized ABI parameter names, interface declarations enforce exact canonical strings. For instance, the ERC-20 standard interface definitions inside quorlin/standard.cpp map directly to standard types regardless of how the high-level Quorlin code expresses them:

built.push_back(StandardInterface{ "IERC20", { {"totalSupply", "totalSupply()", {}, Type::U256, Mutability::View}, {"balanceOf", "balanceOf(address)", {Type::Address}, Type::U256, Mutability::View}, {"transfer", "transfer(address,uint256)", {Type::Address, Type::U256}, Type::Bool, Mutability::Mut}, {"allowance", "allowance(address,address)", {Type::Address, Type::Address}, Type::U256, Mutability::View}, } });

This translation ensures that a function written as reads number balanceOf(address owner) in Quorlin generates the standard balanceOf(address) selector (0x70a08231), allowing external Web3 libraries and Solidity contracts to execute cross-contract calls seamlessly.


12.7 Summary and Comparison with Solidity

Quorlin's visibility and modifier model replaces complex keyword permutations with straightforward rules:

Design ConceptSolidity EquivalentQuorlin SyntaxContext & Scope
View / Pure Queryexternal view / purereads <type> funcName(...)Read-only storage access; state changes and log emissions forbidden.
State Mutationexternal / publicwrites <type> funcName(...)Full read/write storage access and emit capabilities permitted.
Initializationconstructor(...)constructor { ... }One-time execution upon contract deployment; implicit write access.
Indexed Log FilterindexedindexedAnnotates event parameters to produce log topics for filtering.
256-bit Integer Typeuint256numberMapped to uint256 in standard ABI outputs.
Boolean TypebooltruthExpressed using yes and no literals in source code.

By eliminating granular visibility tiers and enforcing explicit reads and writes modifiers, Quorlin code eliminates common access-control ambiguities while producing standardized, highly verifiable bytecode for the Kortana Virtual Machine.