26. Safe Math and BigInts
In smart contract execution, arithmetic safety is paramount. Integer overflows and underflows have historically been responsible for critical vulnerabilities across decentralized applications. The Quorlin Smart Contract Language and the underlying Kortana Virtual Machine (KVM) provide a native 256-bit numeric model designed around security, mathematical predictability, and hardware-level performance.
This chapter details Quorlin's numeric architecture, explaining how 256-bit integers (BigInts) are represented in source code and in memory, how safe math checks are performed, how explicit wrapping operators function, and how the underlying KVM ISA handles arithmetic instructions and gas metering.
The Native Numeric Type: number
Quorlin intentionally avoids floating-point operations and variable-width fixed integers (such as uint8, uint32, or int128) in favor of a single, highly optimized native integer type: number.
At the compiler and language level:
- Keyword:
number - Internal Representation:
Type::U256 - ABI Type Name:
uint256
When writing Quorlin contracts, developers declare numeric variables using number:
contract Vault { number totalDeposits; map<address, number> balances; reads number getBalance(address account) { return balances[account]; } }
When generating standard Ethereum/EVM-compatible ABI metadata (via abi.cpp), Quorlin automatically maps the native number keyword to its standard ABI string representation (uint256). This ensures seamless interoperability with Web3 tooling, hardware wallets, and cross-contract interactions.
KVM Byte-Level Representation
In the KVM interpreter (kvm/interpreter.cpp and kvm/arith.cpp), every word on the stack and in registers is stored as a 256-bit unsigned integer (uint256_t), typically partitioned into four 64-bit limbs:
$$\text{Word} = [\text{limb}_3, \text{limb}_2, \text{limb}_1, \text{limb}_0]$$
This 256-bit capacity allows contracts to handle large financial calculations (such as token amounts with 18 decimal places or cryptographic scalar values) directly without requiring third-party BigInt libraries.
Safe Math vs. Wrapping Operators
Quorlin enforces safe math by default. Standard arithmetic operations automatically reject values that cross the 256-bit boundaries ($0$ to $2^{256}-1$), ensuring that execution safely halts or reverts when bounds are exceeded.
Standard Safe Operators
The default binary operators operate within safe boundaries:
| Operator | Internal BinaryOp | Description | Overflows/Underflows |
|---|---|---|---|
+ | BinaryOp::Add | Addition | Traps/Reverts on overflow ($> 2^{256}-1$) |
- | BinaryOp::Sub | Subtraction | Traps/Reverts on underflow ($< 0$) |
* | BinaryOp::Mul | Multiplication | Traps/Reverts on overflow |
/ | BinaryOp::Div | Unsigned Division | Returns 0 on division by zero |
% | BinaryOp::Mod | Modulo | Returns 0 on modulo by zero |
Example of safe math behavior:
contract TokenSupply { number totalSupply; writes truth mint(number amount) { // Automatically reverts if totalSupply + amount exceeds 2^256 - 1 totalSupply = totalSupply + amount; return yes; } writes truth burn(number amount) { // Automatically reverts if amount > totalSupply (underflow protection) totalSupply = totalSupply - amount; return yes; } }
Unchecked / Explicit Wrapping Operators
In specialized scenarios—such as custom hashing routines, PRNG calculations, bitmap manipulation, or optimized cryptographic primitives—modular fixed-width arithmetic ($2^{256}$) is required. Quorlin provides explicit wrapping operators:
| Wrapped Operator | Internal BinaryOp | Behavior |
|---|---|---|
+~ | BinaryOp::AddWrap | Wraps modulo $2^{256}$ on overflow |
-~ | BinaryOp::SubWrap | Wraps modulo $2^{256}$ on underflow |
*~ | BinaryOp::MulWrap | Retains lower 256 bits on overflow |
contract Counter { number sequence; writes number nextSequence() { // Explicitly allowed to wrap back to 0 after reaching 2^256 - 1 sequence = sequence +~ 1; return sequence; } }
KVM Arithmetic Instruction Set Architecture (ISA)
The KVM ISA (kvm/isa.cpp) features dedicated instructions designed for 256-bit operations. Instructions follow register (R), immediate (I), or jump (J) formats.
Primary Arithmetic Opcodes
The table below outlines how arithmetic operations map to KVM opcodes, their encoding format, and their assigned gas schedule tier (as defined in kvm/gas.cpp):
| Opcode | KVM Instruction Format | Gas Schedule Category | Base Gas Cost |
|---|---|---|---|
Opcode::Add | Format R | kGasVeryLow | 3 |
Opcode::Sub | Format R | kGasVeryLow | 3 |
Opcode::Mul | Format R | kGasLow | 5 |
Opcode::Div | Format R | kGasLow | 5 |
Opcode::SDiv | Format R | kGasLow | 5 |
Opcode::Mod | Format R | kGasLow | 5 |
Opcode::SMod | Format R | kGasLow | 5 |
Opcode::AddMod | Format R | Custom / Complex | Variable |
Opcode::MulMod | Format R | Custom / Complex | Variable |
Opcode::Exp | Format R | Dynamic | Dependent on Exponent size |
Opcode::SignExtend | Format R | kGasVeryLow | 3 |
Code Generation (codegen.cpp)
During the compilation pipeline (compiler.cpp), the analyzer (Analyzer::analyze) validates operand compatibility, ensuring both operands are Type::U256. The code generator then emits register-based KVM instructions using helper functions:
// Sample internal emission routine from quorlin/codegen.cpp [[nodiscard]] Instruction r_form(Opcode op, uint8_t rd, uint8_t rs1, uint8_t rs2 = 0, uint16_t funct = 0) { Instruction out; out.opcode = op; out.rd = rd; out.rs1 = rs1; out.rs2 = rs2; out.funct = funct; return out; }
Edge Case Dynamics and KVM KEVM Semantics
KVM specifies strict execution behaviors for numeric edge cases in compliance with the KEVM Semantics Specification (§18).
Division and Modulo by Zero
Unlike high-level execution environments that crash or trigger unhandled hardware faults upon division by zero, KVM enforces deterministic behavior:
$$\text{Div}(x, 0) = 0$$ $$\text{Mod}(x, 0) = 0$$
In kvm/arith.cpp, division logic directly checks for a zero denominator before proceeding:
uint256_t signed_div(const uint256_t& a, const uint256_t& b) noexcept { if (b.is_zero()) return uint256_t::zero(); ... }
Two's Complement Signed Arithmetic and $\text{INT_MIN} / -1$
Although Quorlin's primary high-level numeric type is unsigned (number), KVM supports signed integer operations (SDiv, SMod, SLt, SGt, SignExtend) to maintain compatibility with low-level smart contract code and EVM interoperability.
In two's-complement 256-bit arithmetic:
- $\text{INT_MIN} = 2^{255} = \text{\texttt{0x8000000000000000000000000000000000000000000000000000000000000000}}$
- $-1 = 2^{256} - 1 = \text{\texttt{0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF}}$
Dividing $\text{INT_MIN}$ by $-1$ results in $+2^{255}$, which cannot fit into a signed 256-bit integer (the maximum signed value is $2^{255}-1$). In KVM, this specific overflow case is handled as follows:
$$\text{SDiv}(\text{INT_MIN}, -1) = \text{INT_MIN}$$
The explicit implementation in kvm/arith.cpp is shown below:
namespace { /// The most negative signed 256-bit value: 1 followed by 255 zeros. [[nodiscard]] uint256_t int_min() noexcept { return uint256_t{0, 0, 0, 0x8000000000000000ULL}; } } // namespace bool is_negative(const uint256_t& value) noexcept { return (value.limb(3) >> 63) != 0; } uint256_t negate(const uint256_t& value) noexcept { return ~value + uint256_t{1}; } uint256_t signed_div(const uint256_t& a, const uint256_t& b) noexcept { if (b.is_zero()) return uint256_t::zero(); // INT_MIN / -1 explicit boundary condition 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; ... }
Signed Comparisons (SLt, SGt)
Comparing signed 256-bit numbers using unsigned magnitude logic produces invalid results because negative numbers have leading 1 bits (making them unsigned-larger than positive numbers). kvm/arith.cpp resolves signed ordering through explicit sign extraction:
bool signed_lt(const uint256_t& a, const uint256_t& b) noexcept { const bool a_negative = is_negative(a); const bool b_negative = is_negative(b); // Different signs: the negative number is strictly smaller if (a_negative != b_negative) return a_negative; // Same sign: unsigned comparison matches signed ordering in two's complement return a < b; }
Bitwise and Shift Operations
Quorlin provides low-level bitwise operations that execute as single-cycle instruction routines in KVM.
| Operation | Quorlin Syntax | KVM Opcode | Gas Tier | Description |
|---|---|---|---|---|
| Bitwise AND | a & b | Opcode::And | kGasVeryLow | Bitwise conjunction |
| Bitwise OR | a | b | Opcode::Or | kGasVeryLow | Bitwise disjunction |
| Bitwise XOR | a ^ b | Opcode::Xor | kGasVeryLow | Bitwise exclusive OR |
| Logical Shift Left | a << b | Opcode::Shl | kGasVeryLow | Shifts bits left, padding with zeros |
| Logical Shift Right | a >> b | Opcode::Shr | kGasVeryLow | Shifts bits right, padding with zeros |
| Arithmetic Shift Right | N/A (KVM Native) | Opcode::Sar | kGasVeryLow | Shifts bits right, preserving the sign bit |
All bitwise operations operate on the complete 256-bit word space. Shifts greater than or equal to 256 bits return 0 (or ~0 for arithmetic right-shifts on negative values).
Practical Cookbook: Arithmetic Patterns in Quorlin
1. Fixed-Point Percentage Calculations
Because floating-point numbers are omitted to prevent non-deterministic consensus splits, fractional calculations are performed using scalar scaling factors (basis points):
contract LiquidityPool { number constant FEE_BASIS_POINTS = 30; // 0.30% fee number constant DENOMINATOR = 10000; reads number calculateFee(number amount) { // Multiply first before dividing to preserve precision number fee = (amount * FEE_BASIS_POINTS) / DENOMINATOR; return fee; } }
2. Safeguarded Compound Interest / Exponentiation
Exponentiation in KVM uses Opcode::Exp, which consumes gas scaled dynamically based on the byte-size of the exponent. When executing large exponential calculations, bounded checks ensure contracts do not exhaust their gas limit:
contract StakingRewards { reads number calculateGrowth(number baseRate, number terms) { require terms <= 50, "exponent too high"; // Emits Opcode::Exp under the hood number multiplier = baseRate ** terms; return multiplier; } }
3. Bitmask Flag Operations
Bitwise operators allow packed representation of multiple binary flags within a single standard number storage slot:
contract AccessControl { number permissions; number constant FLAG_READ = 1; // 0001 number constant FLAG_WRITE = 2; // 0010 number constant FLAG_EXEC = 4; // 0100 writes truth grantWrite() { permissions = permissions | FLAG_WRITE; return yes; } reads truth canWrite(address user) { return (permissions & FLAG_WRITE) != 0; } }
Summary
Quorlin's numeric subsystem pairs a simple developer-facing abstraction (number) with safe default execution semantics. Under the hood, the Kortana Virtual Machine executes these operations using native 256-bit word primitives (uint256_t), structured gas metering, and explicit edge-case handling for division-by-zero and signed integer boundaries. By combining standard safe arithmetic with explicit wrapping operators (+~, -~, *~), Quorlin balances mathematical security with performance across all contract execution paths.