IVM Syscall ABI

May 5, 2026 · View on GitHub

This document defines the IVM syscall numbers, pointer-ABI calling conventions, reserved number ranges, and the canonical table of contract-facing syscalls used by Kotodama lowering. It complements ivm.md (architecture) and kotodama_grammar.md (language).

Versioning

  • The set of recognized syscalls depends on the bytecode header abi_version field. The first release accepts only abi_version = 1; other values are rejected at admission. Unknown numbers for the active abi_version deterministically trap with E_SCALL_UNKNOWN.
  • Runtime upgrades keep abi_version = 1 and do not expand syscall or pointer‑ABI surfaces.
  • Syscall gas costs are part of the versioned gas schedule bound to the bytecode header version. See ivm.md (Gas policy).

Numbering ranges

  • 0x00..=0x1F: VM core/utility (debug/exit helpers are available under CoreHost; remaining dev helpers are mock-host only).
  • 0x20..=0x5F: Iroha core ISI bridge (stable in ABI v1).
  • 0x60..=0x7F: extension ISIs gated by protocol features (still part of ABI v1 when enabled).
  • 0x80..=0xFF: host/crypto helpers and reserved slots; only numbers present in the ABI v1 allowlist are accepted.

Durable helpers (ABI v1)

  • The durable state helper syscalls (0x50–0x5A: STATE_{GET,SET,DEL}, ENCODE/DECODE_INT, BUILD_PATH_*, JSON/SCHEMA encode/decode) are part of the V1 ABI and included in abi_hash computation.
  • CoreHost wires STATE_{GET,SET,DEL} to WSV-backed durable smart-contract state; dev/test hosts may persist locally but must preserve identical syscall semantics.

Pointer‑ABI calling convention (smart‑contract syscalls)

  • Arguments are placed in registers r10+ as raw u64 values or as pointers into the INPUT region to immutable Norito TLV envelopes (e.g., AccountId, AssetDefinitionId, Name, Json, NftId).
  • Scalar return values are the u64 returned from the host. Pointer results are written by the host into r10.

Canonical syscall table (subset)

HexNameArguments (in r10+)ReturnsGas (base + variable)Notes
0x1ASET_ACCOUNT_DETAIL&AccountId, &Name, &Jsonu64=0G_set_detail + bytes(val)Writes a detail for the account
0x22MINT_ASSET&AccountId, &AssetDefinitionId, &NoritoBytes(Numeric)u64=0G_mintMints amount of asset to account
0x23BURN_ASSET&AccountId, &AssetDefinitionId, &NoritoBytes(Numeric)u64=0G_burnBurns amount from account
0x24TRANSFER_ASSET&AccountId(from), &AccountId(to), &AssetDefinitionId, &NoritoBytes(Numeric)u64=0G_transferTransfers amount between accounts
0x29TRANSFER_V1_BATCH_BEGINu64=0G_transferBegin FASTPQ transfer batch scope
0x2ATRANSFER_V1_BATCH_ENDu64=0G_transferFlush accumulated FASTPQ transfer batch
0x2BTRANSFER_V1_BATCH_APPLYr10=&NoritoBytes(TransferAssetBatch)u64=0G_transferApply a Norito-encoded batch in a single syscall
0x25NFT_MINT_ASSET&NftId, &AccountId(owner)u64=0G_nft_mint_assetRegisters a new NFT
0x26NFT_TRANSFER_ASSET&AccountId(from), &NftId, &AccountId(to)u64=0G_nft_transfer_assetTransfers ownership of NFT
0x27NFT_SET_METADATA&NftId, &Jsonu64=0G_nft_set_metadataUpdates NFT metadata
0x28NFT_BURN_ASSET&NftIdu64=0G_nft_burn_assetBurns (destroys) an NFT
0xA1SMARTCONTRACT_EXECUTE_QUERYr10=&NoritoBytes(QueryRequest)r10=ptr (&NoritoBytes(QueryResponse))G_scq + per_item*items + per_byte*bytes(resp)Iterable queries run ephemerally; QueryRequest::Continue rejected
0xA2CREATE_NFTS_FOR_ALL_USERSu64=countG_create_nfts_for_allHelper; feature‑gated
0xA3SET_SMARTCONTRACT_EXECUTION_DEPTHdepth:u64u64=prevG_set_depthAdmin; feature‑gated
0xA4GET_AUTHORITY– (host writes result)&AccountIdG_get_authHost writes pointer to current authority into r10
0xF7GET_MERKLE_PATHaddr:u64, out_ptr:u64, optional root_out:u64u64=lenG_mpath + lenWrites path (leaf→root) and optional root bytes
0xFAGET_MERKLE_COMPACTaddr:u64, out_ptr:u64, optional depth_cap:u64, optional root_out:u64u64=depthG_mpath + depth[u8 depth][u32 dirs_le][u32 count][count*32 siblings]
0xFFGET_REGISTER_MERKLE_COMPACTreg_index:u64, out_ptr:u64, optional depth_cap:u64, optional root_out:u64u64=depthG_mpath + depthSame compact layout for register commitment

Gas enforcement

  • CoreHost charges extra gas for ISI syscalls using the native ISI schedule; FASTPQ batch transfers are charged per entry.
  • ZK_VERIFY syscalls reuse the confidential verification gas schedule (base + proof size).
  • SMARTCONTRACT_EXECUTE_QUERY charges base + per-item + per-byte; sorting multiplies per-item cost and unsorted offsets add a per-item penalty.

Notes

  • All pointer arguments reference Norito TLV envelopes in the INPUT region and are validated on first dereference (E_NORITO_INVALID on error).
  • All mutations are applied via Iroha’s standard executor (through CoreHost), not directly by the VM.
  • Exact gas constants (G_*) are defined by the active gas schedule; see ivm.md.

Errors

  • E_SCALL_UNKNOWN: syscall number not recognized for the active abi_version.
  • Input validation errors propagate as VM traps (e.g., E_NORITO_INVALID for malformed TLVs).

Cross‑references

  • Architecture and VM semantics: ivm.md
  • Language and builtin mapping: docs/source/kotodama_grammar.md

Generation note

  • A complete list of syscall constants can be generated from source with:
    • make docs-syscalls → writes docs/source/ivm_syscalls_generated.md
    • make check-docs → verifies the generated table is up to date (useful in CI)
  • The subset above remains a curated, stable table for contract-facing syscalls.

Admin/Role TLV Examples (Mock Host)

This section documents the TLV shapes and minimal JSON payloads accepted by the mock WSV host for admin‑style syscalls used in tests. All pointer arguments follow the pointer‑ABI (Norito TLV envelopes placed in INPUT). Production hosts may use richer schemas; these examples aim to clarify types and basic shapes.

  • REGISTER_PEER / UNREGISTER_PEER

    • Args: r10=&Json
    • Example JSON: { "peer": "peer-id-or-info" }
    • CoreHost note: REGISTER_PEER expects a RegisterPeerWithPop JSON object with peer + pop bytes (optional activation_at, expiry_at, hsm); UNREGISTER_PEER accepts a peer-id string or { "peer": "..." }.
  • CREATE_TRIGGER / REMOVE_TRIGGER / SET_TRIGGER_ENABLED

    • CREATE_TRIGGER:
      • Args: r10=&Json
      • Minimal JSON: { "name": "t1" } (additional fields ignored by the mock)
    • REMOVE_TRIGGER:
      • Args: r10=&Name (trigger name)
    • SET_TRIGGER_ENABLED:
      • Args: r10=&Name, r11=enabled:u64 (0 = disabled, non‑zero = enabled)
    • CoreHost note: CREATE_TRIGGER expects a full trigger spec (base64 Norito Trigger string or { "id": "<trigger_id>", "action": ... } with action as a base64 Norito Action string or a JSON object), and SET_TRIGGER_ENABLED toggles the trigger metadata key __enabled (missing defaults to enabled).
  • Roles: CREATE_ROLE / DELETE_ROLE / GRANT_ROLE / REVOKE_ROLE

    • CREATE_ROLE:
      • Args: r10=&Name (role name), r11=&Json (permissions set)
      • JSON accepts either key "perms" or "permissions", each a string array of permission names.
      • Examples:
        • { "perms": [ "mint_asset:rose#wonder" ] }
        • { "permissions": [ "read_assets:<i105-account-id>", "transfer_asset:rose#wonder" ] }
      • Supported permission name prefixes in the mock:
        • register_domain, register_account, register_asset_definition
        • read_assets:<account_id>
        • mint_asset:<asset_definition_id>
        • burn_asset:<asset_definition_id>
        • transfer_asset:<asset_definition_id>
    • DELETE_ROLE:
      • Args: r10=&Name
      • Fails if any account is still assigned this role.
    • GRANT_ROLE / REVOKE_ROLE:
      • Args: r10=&AccountId (subject), r11=&Name (role name)
    • CoreHost note: permission JSON may be a full Permission object ({ "name": "...", "payload": ... }) or a string (payload defaults to null); GRANT_PERMISSION/REVOKE_PERMISSION accept &Name or &Json(Permission).
  • Unregister ops (domain/account/asset): invariants (mock)

    • UNREGISTER_DOMAIN (r10=&DomainId) fails if accounts or asset definitions exist in the domain.
    • UNREGISTER_ACCOUNT (r10=&AccountId) fails if the account has non‑zero balances or owns NFTs.
    • UNREGISTER_ASSET (r10=&AssetDefinitionId) fails if any balances exist for the asset.

Notes

  • These examples reflect the mock WSV host used in tests; real node hosts may expose richer admin schemas or require additional validation. The pointer‑ABI rules still apply: TLVs must be in INPUT, version=1, type IDs must match, and payload hashes must validate.