Execution
The execution layer processes every transaction submitted to the Basalt network, transforming state transitions into deterministic, verifiable outcomes. It encompasses transaction validation, virtual machine execution (native C# AOT and WASM), sandbox isolation, a host interface for contract-to-chain interaction, and a granular gas metering model.
Transaction Types
Basalt defines four transaction types, each identified by a numeric type code:
| Type Code | Name | Description |
|---|---|---|
| 0 | Transfer | Simple value transfer between accounts |
| 1 | ContractDeploy | Deploy new contract code to the chain |
| 2 | ContractCall | Invoke a function on a deployed contract |
| 3 | System | Protocol-level operations (staking, slashing, governance) |
Transaction Structure
Every transaction, regardless of type, conforms to the following structure:
| Field | Type | Description |
|---|---|---|
| Type | uint8 | Transaction type code (0, 1, 2, or 3) |
| Nonce | uint64 | Sender's transaction sequence number |
| Sender | byte[20] | Address of the transaction originator |
| To | byte[20] | Recipient address (zero address for ContractDeploy) |
| Value | UInt256 | Amount of native token to transfer |
| GasLimit | uint64 | Maximum gas units the sender is willing to consume |
| GasPrice | UInt256 | Price per gas unit in the smallest denomination |
| Data | byte[] | Payload (function selector + arguments for calls, bytecode for deploys) |
| Priority | uint8 | Transaction priority hint (0 = normal, 1 = high) |
| ChainId | uint32 | Network identifier to prevent cross-chain replay |
| Signature | byte[64] | Ed25519 signature over the canonical serialization of all preceding fields |
Type-Specific Behavior
Transfer (Type 0): The To field is the recipient address. The Data field is typically empty. The Value field specifies the amount to transfer. If the sender has insufficient balance (after accounting for gas costs), the transaction fails.
ContractDeploy (Type 1): The To field is set to the zero address (0x0000...0000). The Data field contains the compiled contract bytecode (native shared library or WASM module). The new contract's address is derived deterministically from the sender's address and nonce. The deployed code is stored under key 0xFF01 in the contract's storage trie.
ContractCall (Type 2): The To field is the target contract's address. The Data field contains the function selector (first 4 bytes) followed by ABI-encoded arguments. The Value field may be non-zero for payable functions.
System (Type 3): Reserved for protocol-level operations that modify consensus or governance state. System transactions are typically generated by the protocol itself (e.g., epoch transitions, reward distributions) or by validators performing staking operations.
Validation Pipeline
Every transaction passes through a seven-step validation pipeline before it is eligible for execution. Each step is a hard gate -- failure at any step causes the transaction to be rejected immediately.
Step 1: Signature Verification
The transaction's Ed25519 signature is verified against the canonical serialization of the transaction (all fields except the signature itself). The public key used for verification is derived from the Sender address. If the signature is invalid, the transaction is rejected.
During block validation, signature verification is performed using batch verification for efficiency, achieving a 2--3x speedup over individual verification when processing a full block of transactions.
Step 2: Nonce Check
The transaction's nonce must exactly equal the sender's current nonce in the world state. This prevents:
- Replay attacks: A previously executed transaction cannot be resubmitted because the sender's nonce has already been incremented.
- Out-of-order execution: Transactions from the same sender are processed in strict sequential order.
If the nonce is too low (already used), the transaction is rejected. If the nonce is too high (gap in the sequence), the transaction is held in the mempool pending the arrival of the missing intermediate transactions.
Step 3: Balance Check
The sender's account balance must be sufficient to cover the maximum possible cost of the transaction:
required_balance = Value + (GasLimit * GasPrice)
This check is performed before execution. The actual gas cost may be less than GasLimit * GasPrice, but the full amount must be available to guarantee that the sender can pay for the worst-case execution.
Step 4: Gas Limit Validation
The transaction's GasLimit must fall within the acceptable range:
- Minimum: The intrinsic gas cost of the transaction (base cost plus per-byte data cost).
- Maximum: The block gas limit (which constrains the total gas consumed by all transactions in a block).
Transactions with a gas limit below the intrinsic cost will always fail and are rejected upfront. Transactions exceeding the block gas limit cannot fit in any block and are also rejected.
Step 5: Data Size Validation
The Data field is subject to size limits based on the transaction type:
| Transaction Type | Maximum Data Size |
|---|---|
| ContractCall (Type 2) | 128 KB |
| ContractDeploy (Type 1) | 2 MB |
| Transfer (Type 0) | 128 KB |
| System (Type 3) | 128 KB |
These limits prevent excessively large transactions from consuming disproportionate network bandwidth and storage.
Step 6: Chain ID Validation
The transaction's ChainId must match the network's configured chain ID. This prevents cross-chain replay attacks where a transaction signed for one Basalt network (e.g., testnet) is submitted to a different network (e.g., mainnet).
Step 7: Compliance Check
The transaction is evaluated against the Basalt compliance layer:
- The sender's address is checked against the SanctionsList (
IsSanctioned(byte[])). Sanctioned addresses cannot send transactions. - The receiver's address is similarly checked.
- For high-value transfers or regulated token operations, the ComplianceEngine evaluates additional rules based on both parties' KYC status, jurisdiction, and transfer limits.
If any compliance check fails, the transaction is rejected with a descriptive error code.
BasaltVM
The Basalt Virtual Machine supports two execution modes, enabling contracts written in different languages and compiled to different targets.
Mode 1: Native C# AOT
Contracts written in C# are compiled to native shared libraries using the .NET ILC (IL Compiler) ahead-of-time compilation pipeline. This produces platform-specific native code (x64 or ARM64) that executes at near-native speed without JIT compilation overhead.
Advantages:
- Maximum execution speed -- native machine code with no interpretation or JIT warmup.
- Full access to the Basalt SDK type system and analyzers at compile time.
- Deterministic compilation via the Basalt SDK build toolchain.
Compilation workflow:
- The developer writes a contract using the
Basalt.Sdk.Contractslibrary. - The
Basalt.Sdk.AnalyzersRoslyn analyzer validates the contract at compile time (no disallowed APIs, correct attribute usage, etc.). - The contract is compiled via
dotnet publishwith Native AOT enabled, producing a.so(Linux) or.dylib(macOS) shared library. - The shared library is submitted as the
Datapayload of aContractDeploytransaction.
Mode 2: WASM (Wasmtime)
Contracts written in Rust, C, or any language that compiles to WebAssembly can be deployed as WASM modules and executed via the Wasmtime runtime.
Advantages:
- Language agnostic -- any language with a WASM compilation target is supported.
- Platform independent -- the same WASM module runs on any Basalt node regardless of host architecture.
- Strong sandboxing guarantees inherited from the WASM execution model (linear memory, no direct host access).
Compilation workflow:
- The developer writes a contract in Rust (or another WASM-targeting language).
- The contract is compiled to a
.wasmmodule using the appropriate toolchain (e.g.,cargo build --target wasm32-unknown-unknown). - The
.wasmmodule is submitted as theDatapayload of aContractDeploytransaction.
Execution Mode Selection
The deployed code's format is detected automatically during contract execution. The first bytes of the Data payload are inspected:
- If the payload begins with the WASM magic bytes (
\0asm), it is executed via Wasmtime. - Otherwise, it is treated as a native shared library and loaded via the platform's dynamic linker.
Sandbox Isolation
All contract execution occurs within a strict sandbox that prevents contracts from accessing host system resources or interfering with the node's operation.
Resource Limits
| Resource | Limit |
|---|---|
| Memory | 256 MB |
| CPU time | Configurable per transaction (enforced via gas metering) |
| Stack size | 1 MB |
| Threads | Single-threaded only |
| Filesystem access | None |
| Network access | None |
System Call Filtering
On Linux, the sandbox enforces a seccomp-bpf syscall allowlist. Only the following system calls are permitted:
| Allowed Syscall | Purpose |
|---|---|
read | Reading from pre-opened file descriptors (WASM linear memory) |
write | Writing to pre-opened file descriptors |
mmap | Memory allocation for the WASM linear memory |
clock_gettime | Accessing monotonic clock for gas metering |
Any attempt to invoke a system call not on the allowlist results in immediate termination of the contract execution with a SyscallViolation error. The transaction is marked as failed, gas is consumed up to the point of violation, and no state changes from the transaction are applied.
Determinism Guarantees
The sandbox ensures deterministic execution across all nodes:
- No access to wall-clock time (only monotonic clock for internal metering).
- No access to random number generators (contracts must use on-chain randomness sources).
- No floating-point non-determinism (WASM floating-point semantics are fully deterministic; native AOT contracts are compiled with strict floating-point flags).
- Single-threaded execution eliminates race conditions.
Host Interface
Contracts interact with the blockchain state through a set of host functions exposed by the BasaltVM runtime. These functions are the only mechanism by which a contract can read or modify on-chain state, communicate with other contracts, or emit observable side effects.
Storage Operations
| Function | Signature | Gas Cost (Hot/Cold) | Description |
|---|---|---|---|
storage_read | (key: bytes32) -> bytes | 50 / 200 | Read a value from the contract's storage trie |
storage_write | (key: bytes32, value: bytes) -> void | 500 (existing) / 5,000 (new) | Write a value to the contract's storage trie |
storage_delete | (key: bytes32) -> void | 200 / 200 | Delete a key from the contract's storage trie |
Hot vs. Cold: A storage slot is "hot" if it has been accessed earlier in the same transaction. The first access to a slot within a transaction is "cold" and incurs a higher gas cost due to the disk I/O required to load the trie node.
Context Functions
| Function | Signature | Gas Cost | Description |
|---|---|---|---|
get_caller | () -> address | 3 | Returns the address of the immediate caller |
get_block_timestamp | () -> uint64 | 3 | Returns the current block's Unix timestamp |
get_block_height | () -> uint64 | 3 | Returns the current block height |
get_tx_value | () -> uint256 | 3 | Returns the value sent with the current call |
get_gas_remaining | () -> uint64 | 3 | Returns the remaining gas for this execution |
Cross-Contract Calls
| Function | Signature | Gas Cost | Description |
|---|---|---|---|
call_contract | (address, function_selector, args, value, gas) -> bytes | 150 (hot) / 400 (cold) | Invoke a function on another contract |
Cross-contract calls create a new execution frame with its own gas allocation (capped by the caller's remaining gas and the explicit gas parameter). If the callee reverts, the caller can inspect the return data to determine the failure reason. State changes from reverted calls are rolled back.
Cryptographic Functions
| Function | Signature | Gas Cost | Description |
|---|---|---|---|
verify_ed25519 | (message, signature, public_key) -> bool | 1,000 (hot) / 2,500 (cold) | Verify an Ed25519 signature |
verify_bls | (message, signature, public_key) -> bool | 5,000 (hot) / 12,500 (cold) | Verify a BLS12-381 signature |
blake3_hash | (data) -> bytes32 | 10 (hot) / 25 (cold) | Compute the BLAKE3 hash of arbitrary data |
These precompiled cryptographic operations allow contracts to verify off-chain signatures and compute hashes without implementing the algorithms in contract code, which would be prohibitively expensive in gas.
Event Emission
| Function | Signature | Gas Cost | Description |
|---|---|---|---|
emit_event | (topics: bytes32[], data: bytes) -> void | 100 (hot) / 200 (cold) | Emit an event log entry |
Events are the primary mechanism for contracts to communicate observable outcomes to off-chain systems. Emitted events are included in the transaction receipt and indexed by the Bloom filter for efficient retrieval. Up to 4 topics can be indexed per event.
Gas Model
Every operation in the BasaltVM has an associated gas cost that reflects its computational and storage impact. Gas metering ensures that:
- Execution is bounded -- no transaction can consume unbounded resources.
- Resource usage is priced fairly -- more expensive operations (disk I/O, cryptographic verification) cost more gas.
- Denial-of-service attacks are economically prohibitive -- an attacker must pay for every unit of computation consumed.
Gas Cost Table
| Operation | Hot Gas Cost | Cold Gas Cost | Notes |
|---|---|---|---|
| Arithmetic (add, mul, etc.) | 1 | 3 | Basic ALU operations |
| Storage read | 50 | 200 | First access to a slot is cold |
| Storage read (hot) | 50 | -- | Subsequent access within same tx |
| Storage write (existing key) | 500 | 800 | Updating an existing value |
| Storage write (new key) | 5,000 | 8,000 | Creating a new storage slot |
| Cross-contract call | 150 | 400 | Per call frame |
| Ed25519 verify | 1,000 | 2,500 | Per signature |
| BLS12-381 verify | 5,000 | 12,500 | Per signature |
| Event emission | 100 | 200 | Per event, plus per-byte data cost |
| BLAKE3 hash | 10 | 25 | Per hash invocation |
Hot vs. Cold Access
The dual-cost model distinguishes between hot and cold access:
- Cold access: The first time a storage slot, contract, or resource is accessed within a transaction. Requires a full trie traversal and potentially a disk read. Charged at the higher "cold" rate.
- Hot access: Any subsequent access to the same resource within the same transaction. The data is already cached in memory, so the lower "hot" rate applies.
This model incentivizes contracts to minimize the number of distinct storage slots accessed (reducing cold reads) and to batch operations on the same data (benefiting from hot pricing).
Gas Refunds
Certain operations that reduce state size receive partial gas refunds:
- Storage deletion: Deleting a storage slot refunds a portion of the original write cost, incentivizing state cleanup.
- Self-destruct: A contract that self-destructs receives a gas refund proportional to the storage freed.
Refunds are capped at 50% of the total gas consumed by the transaction to prevent refund manipulation.
Intrinsic Gas
Every transaction has a base intrinsic gas cost before any execution begins:
| Component | Cost |
|---|---|
| Base transaction cost | 21,000 gas |
| Per non-zero data byte | 16 gas |
| Per zero data byte | 4 gas |
| Contract creation surcharge | 32,000 gas |
Gas Sponsoring
Basalt supports a gas sponsoring mechanism that allows enterprises to prepay gas costs on behalf of their users, removing the requirement for end users to hold native tokens.
Enterprise Gas Quotas
Enterprises can purchase gas quotas in bulk through a protocol-level sponsoring contract. The sponsoring model works as follows:
- Quota purchase: An enterprise deposits native tokens into the gas sponsoring contract, receiving a gas quota denominated in gas units at a fixed price locked for a 30-day period.
- Per-address allocation: The enterprise configures daily gas caps for individual sponsored addresses. Each sponsored address can consume up to its daily cap without holding any native tokens.
- Transaction submission: When a sponsored user submits a transaction, the gas cost is deducted from the enterprise's quota rather than the user's balance. The transaction includes a sponsorship attestation that the validator verifies against the sponsoring contract.
Pricing Model
| Parameter | Value |
|---|---|
| Pricing period | 30 days |
| Price lock | Fixed at purchase time for the entire period |
| Minimum purchase | 10,000,000 gas units |
| Per-address daily cap | Configurable by the enterprise (default: 1,000,000 gas/day) |
| Rollover | Unused gas does not roll over to the next period |
Benefits
- User experience: End users interact with the blockchain without needing to acquire or manage native tokens.
- Cost predictability: Enterprises lock in gas prices for 30-day periods, enabling budgeting and cost forecasting.
- Compliance: Enterprises maintain control over which addresses can transact under their sponsorship, supporting regulatory requirements.
Gas sponsoring is entirely optional. Transactions without a sponsorship attestation are processed normally, with gas costs deducted from the sender's balance.