Documentation Index
8 min readChapter 5

5. Variables and Mutability

In Quorlin, variables serve as the fundamental mechanism for storing and manipulating data within smart contracts. Designed to feel familiar to developers coming from Java, C#, or Solidity, Quorlin uses plain, expressive English keywords (number, truth, address, text) while imposing strong compile-time type safety and explicit mutability controls.

This chapter covers how variables are declared, scoped, and mutated across two distinct memory domains: persistent contract storage and transient function execution memory. It also details how function mutability modifiers (reads and writes) enforce safety rules at compile time.


5.1 Overview of Variable Classification

Variables in Quorlin are categorized by their storage duration and accessibility:

  1. State Variables (Storage): Declared at the root scope of a contract. State variables persist permanently on the Kortana blockchain within the unified state trie.
  2. Local Variables (Transient Memory / Registers): Declared inside constructors or function bodies. Local variables exist only for the duration of a function execution and are discarded when execution completes.
  3. Implicit Context Variables: Built-in read-only values supplied by the Kortana Virtual Machine (KVM) execution context, such as caller.

The following table summarizes the primary Quorlin data types, their internal compiler representation, and their corresponding Ethereum-compatible ABI types:

Quorlin TypeInternal Type (sema)ABI / EVM EquivalentDescription
numberType::U256uint256Unsigned 256-bit integer
truthType::BoolboolBoolean value (yes or no)
addressType::Addressaddress20-byte Kortana / Ethereum address
textType::TextstringUTF-8 dynamic text string (subject to size bounds)
map<K, V>MappingsN/A (Key-value store)Key-value mapping pointing from key type K to value type V
recordCustom StructDynamic tupleUser-defined aggregate record type

5.2 State Variables

State variables represent the persistent state of your smart contract. Every state variable declared inside a contract block is allocated a specific storage slot in the contract's storage space.

State Variable Declaration

State variables are declared at the contract scope prior to constructor and function definitions:

contract Vault { // State variable declarations address owner; number totalDeposited; truth isLocked; text vaultName; constructor { owner = caller; totalDeposited = 0; isLocked = no; vaultName = "Secure Vault"; } }

Storage Operations and KVM Cost Model

When a state variable is read or written, the Quorlin compiler translates these operations into explicit KVM storage operations:

  • Reads (get_storage): Reading a state variable queries the underlying WorldState trie using StateHost::get_storage(address, key).
  • Writes (set_storage): Mutating a state variable updates the unified state trie via StateHost::set_storage(address, key, value).

Because storage modifications alter the global state trie, modifying state variables incurs significantly higher gas costs than modifying local variables. The KVM gas schedule calculates gas based on whether a write fills an empty storage slot (cold write) or overwrites an existing slot (warm write).


5.3 Local Variables

Local variables are declared inside function bodies or constructors. They are allocated temporary space during program execution within KVM execution frames and registers.

Local Declaration and Assignment

Unlike state variables, local variables can be declared and initialized simultaneously within function execution blocks:

writes truth executeComputation(number inputVal) { // Local variable declaration and immediate assignment number stepOne = inputVal + 10; number stepTwo = stepOne * 2; // Local variable reassignment stepOne = stepTwo - 5; return yes; }

Scope and Lifetime

Local variables remain in scope only within the block ({ ... }) in which they are declared. Attempting to access a local variable outside its enclosing block yields a semantic compilation error. Local variables do not persist across KVM execution calls.


5.4 Primitive Types and Literals

Quorlin introduces clear, English-based primitives for literal values and operational logic.

1. number

The number type represents an unsigned 256-bit integer (uint256). Standard arithmetic operators (+, -, *, /, %) and bitwise operators (&, |, ^, <<, >>) operate on number types.

number price = 500; number fee = 15; number total = price + fee;

2. truth

The truth type replaces standard boolean indicators with explicit standard English keywords: yes and no.

truth active = yes; truth paused = no; if active { // Logical execution }

3. address

The address type holds a 20-byte standard blockchain account address. Hexadecimal address literals are prefixed with 0x.

address recipient = 0x1234567890abcdef1234567890abcdef12345678;

4. text

The text type represents UTF-8 character strings. String literals are enclosed in double quotes.

text standardGreeting = "Hello, Kortana!";

5.5 Function Mutability: reads vs writes

Quorlin strictly categorizes contract functions based on whether they read or mutate state variables. Mutability guarantees are enforced at compile time by the Quorlin Semantic Analyzer (Analyzer).

                    ┌───────────────────────────┐
                    │     Function Definition   │
                    └─────────────┬─────────────┘
                                  │
                    Is it marked 'reads' or 'writes'?
                                  │
                  ┌───────────────┴───────────────┐
                  ▼                               ▼
          ┌──────────────┐                ┌──────────────┐
          │    reads     │                │    writes    │
          └──────┬───────┘                └──────┬───────┘
                 │                               │
       Can read state vars              Can read state vars
       CANNOT write state vars          CAN write state vars
       (View / Pure Call)               (State Transition)

Read-Only Functions (reads)

Functions tagged with the reads keyword guarantee that contract state variables will not be modified during their execution. A reads function can compute values, read local variables, and query state variables, but any attempt to mutate a state variable results in a compile error.

reads number getBalance(address account) { // Permitted: Reading from state mapping 'balances' return balances[account]; }

If a developer attempts to modify a state variable inside a reads function:

// INVALID: Will fail compilation during semantic analysis reads truth illegalMutation() { totalDeposited = totalDeposited + 1; // Semantic Error: Cannot write state in a 'reads' function return yes; }

State-Mutating Functions (writes)

Functions tagged with the writes keyword explicitly denote state-changing operations. They are permitted to alter state variables, write to mappings, instantiate updates, and emit event topics.

writes truth deposit() { number currentBalance = balances[caller]; balances[caller] = currentBalance + 100; totalDeposited = totalDeposited + 100; return yes; }

5.6 Mappings and Struct Records

Mappings (map<KeyType, ValueType>)

Mappings store key-value pairs inside contract state. Mappings are declared as state variables and modified via subscript assignment within writes functions.

contract TokenStore { map<address, number> balances; map<address, map<address, truth>> permissions; writes truth grantPermission(address operator) { permissions[caller][operator] = yes; return yes; } reads truth checkPermission(address owner, address operator) { return permissions[owner][operator]; } }

Key characteristics of mappings:

  • Mappings are state-bound data structures and cannot be declared as local variables within function scope.
  • Reading a non-existent key returns the zero-initialized default value of the target type (e.g., 0 for number, no for truth).

Records (record)

Records allow grouping related primitive types into custom composite types. Records can be instantiated locally or used within state storage.

record UserProfile { number id; truth isActive; address wallet; } contract AccountRegistry { map<address, UserProfile> profiles; writes truth registerUser(number userId) { UserProfile newUser = UserProfile(userId, yes, caller); profiles[caller] = newUser; return yes; } reads UserProfile getProfile(address user) { return profiles[user]; } }

When accessing record fields, dot notation is used (e.g., userProfile.isActive).


5.7 Built-In Context Variables

Quorlin provides immutable, implicit variables made available by the KVM execution environment during run time.

caller

The caller keyword returns the address of the account or external contract initiating the current execution call frame. caller is immutable and cannot be reassigned.

writes truth reclaimOwnership() { require caller == owner, "Unauthorized caller"; // Execution continues... return yes; }

5.8 Comprehensive Example: Mutability Patterns

The contract below demonstrates variable types, variable scopes, and function mutability controls in Quorlin:

contract PropertyRegistry { // --- State Variables (Persistent Storage) --- address publicRegistryOwner; number totalRegisteredProperties; truth isRegistryOpen; record Property { number propertyId; address currentOwner; number registeredValue; } map<number, Property> registry; event PropertyRegistered(number indexed id, address indexed owner, number value); event PropertyTransferred(number indexed id, address indexed oldOwner, address indexed newOwner); constructor { publicRegistryOwner = caller; totalRegisteredProperties = 0; isRegistryOpen = yes; } // --- State Read Function --- reads Property getPropertyDetails(number id) { return registry[id]; } reads truth checkOwnership(number id, address checkAddr) { Property prop = registry[id]; return prop.currentOwner == checkAddr; } // --- State Write Function --- writes truth registerProperty(number id, number value) { require isRegistryOpen == yes, "Registry closed"; require registry[id].propertyId == 0, "Property already exists"; // Creating local struct record Property newProp = Property(id, caller, value); // Mutating State Storage registry[id] = newProp; totalRegisteredProperties = totalRegisteredProperties + 1; emit PropertyRegistered(id, caller, value); return yes; } writes truth transferProperty(number id, address newOwner) { Property currentProp = registry[id]; require currentProp.currentOwner == caller, "Not property owner"; // Local variable allocation address previousOwner = currentProp.currentOwner; // Struct field mutation & state updates currentProp.currentOwner = newOwner; registry[id] = currentProp; emit PropertyTransferred(id, previousOwner, newOwner); return yes; } }