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:
- State Mutability Modifiers:
readsandwrites. - Event Parameter Modifiers:
indexed. - 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
mapdata 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
emitkeyword is prohibited inside areadscontext because emitting an event alters state logs on the Kortana ledger. - Sub-calls: A
readsfunction can only invoke otherreadsfunctions 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 Keyword | Internal Type (Type) | Standard ABI Type | Description |
|---|---|---|---|
number | Type::U256 | uint256 | 256-bit unsigned integer |
truth | Type::Bool | bool | Boolean (yes / no) |
address | Type::Address | address | 20-byte Kortana address |
text | Type::Text | string | UTF-8 encoded text string |
nothing | Type::Void | void | Return 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
emitkeyword. - 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
indexedflag (such asnumber amountin theTransferevent) 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
- Implicit Modifiers: The
constructorblock is inherently state-modifying (writescontext) and cannot be marked with explicitreadsorwriteskeywords. - No Return Type: Unlike standard functions, a constructor cannot declare a return type.
- ABI Generation: As shown in
quorlin/abi.cpp, the compiler automatically emits a standardconstructorentry 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:
- The
Analyzerinspects the active AST node and detects a mutability mismatch (Mutability::Viewversus state mutation). - A diagnostic error is logged to the
DiagnosticBag. - Compilation halts immediately at Stage 3.
- 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 Concept | Solidity Equivalent | Quorlin Syntax | Context & Scope |
|---|---|---|---|
| View / Pure Query | external view / pure | reads <type> funcName(...) | Read-only storage access; state changes and log emissions forbidden. |
| State Mutation | external / public | writes <type> funcName(...) | Full read/write storage access and emit capabilities permitted. |
| Initialization | constructor(...) | constructor { ... } | One-time execution upon contract deployment; implicit write access. |
| Indexed Log Filter | indexed | indexed | Annotates event parameters to produce log topics for filtering. |
| 256-bit Integer Type | uint256 | number | Mapped to uint256 in standard ABI outputs. |
| Boolean Type | bool | truth | Expressed 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.