A Framework for Fallible Operations in the SpaceVM
Section 1: The Execution and Reversion Model of the SpaceVM
1.1 The Stateless Execution Environment
A defining characteristic of the MultiversX SpaceVM is its statelessness during the execution process.1 When a smart contract is invoked, it is not permitted to modify the global blockchain state directly. All proposed changes—such as updates to account balances, modifications to contract storage, or the creation of new tokens—are not immediately committed to the state trie. Instead, the MultiversX Environment Interface (EI), which serves as the bridge between the smart contract’s WASM code and the underlying blockchain, accumulates these state modifications within a transient data structure.
This design choice carries profound architectural implications. It dramatically simplifies the process of error handling: the entire set of proposed changes, held in this temporary, isolated buffer, is simply discarded. Only upon the successful completion of the entire execution flow is this transient data structure validated and atomically applied to the blockchain’s state.1
This model ensures that every state transition is atomic; it either succeeds completely or fails completely, leaving the prior state intact. This obviates entire classes of bugs and vulnerabilities related to partial state updates. However, it also means that the FailExecution mechanism, which triggers the discard of this transient data, is an all-or-nothing proposition.
1.2 The MultiversX Environment Interface (EI) and High-Level Error Handling
Smart contract developers on the MultiversX network primarily interact with the VM through a sophisticated Rust framework.2 This framework provides a high-level, ergonomic abstraction over the low-level WASM environment and the VM hooks. A critical part of this framework is its approach to error handling and preconditions. Developers are provided with powerful macros, notably
sc_panic! and require!, to manage control flow and enforce logical constraints within their contracts.4
The require! macro is the most common tool for validating preconditions. A typical use case is require!(condition, “error message”), which checks if a given boolean expression evaluates to true. If it evaluates to false, the execution is immediately halted, and the provided error message is surfaced. This macro is effectively syntactic sugar for a more direct conditional panic: if!condition { sc_panic!(“error message”) }.
The sc_panic! macro is the ultimate source of a contract-initiated failure. When invoked, it signals to the VM host that an unrecoverable error has occurred from the contract’s perspective. The VM host interprets this signal and initiates the FailExecution process. This creates a direct and unambiguous link between a logical assertion within a smart contract (e.g., checking if a caller is the owner of an asset) and a full, hard reversion of the transaction.
1.3 The Anatomy of FailExecution
The FailExecution call is the VM’s definitive “kill switch” for a smart contract execution. As described in the initial query, its consequences are absolute:
-
Execution Halt: The VM immediately stops processing any further opcodes within the smart contract.
-
State Reversion: The entire transient data structure, containing all proposed state changes from the beginning of the transaction, is discarded. The blockchain state reverts to precisely what it was before the transaction began.
-
Transaction Failure: The transaction is marked with a “failed” status on the blockchain.
-
Gas Consumption: The gas provided for the transaction is consumed up to the point of the FailExecution call. This ensures that the network is compensated for the computational resources used, preventing denial-of-service attacks via transactions that are designed to fail.
-
Event Emission: A signalError event is generated, which can be indexed and observed by off-chain services, indicating that a contract call failed and providing context such as the caller’s address.5
This mechanism is an exceptionally robust safety feature. It acts as a powerful backstop, preventing any contract from committing an invalid or inconsistent state transition to the blockchain. However, its nature as a blunt instrument is its primary drawback. It conflates fundamentally different categories of errors.
This rigidity becomes particularly problematic in the context of contract composability. The promise of a rich dApp ecosystem rests on the ability of contracts to interact with and build upon one another. If Contract A makes a call to Contract B, and Contract B fails for a non-critical, temporary reason (e.g., a resource it needed was momentarily locked by another transaction), the FailExecution model dictates that the entire execution stack is unwound. Not only does Contract B’s work get reverted, but all of Contract A’s work up to that point is also lost. This creates a fragile dependency chain, where the failure of any single component leads to the failure of the whole system.
Section 2: A Proposed Architecture for Returnable VM Errors (Fallible Hooks)
To address the limitations of the monolithic FailExecution model, a more nuanced error-handling architecture is required. This section proposes a framework for introducing fallible VM hooks, which can return actionable error codes to the calling smart contract without causing a complete transaction reversion. This architecture is not a replacement for the existing security model but an extension of it, designed to enhance the expressiveness and resilience of smart contracts.
2.1 The Principle of Error Classification
The cornerstone of the proposed architecture is the formal classification of VM errors into two distinct categories. This classification provides a clear and defensible rationale for determining when a hard revert is necessary versus when a recoverable error is appropriate.
2.1.1 Class 1: Unrecoverable Errors (Retain FailExecution)
This class encompasses errors that are fundamental, non-negotiable, and indicative of a serious flaw in the contract’s logic, a developer mistake, or a potentially malicious attempt to compromise the VM or protocol integrity. These errors must continue to trigger an immediate and irreversible FailExecution. Allowing a contract to handle these errors would create ambiguity and potential attack vectors.
Examples of Class 1 errors include:
-
Invalid Input Formatting: Passing a syntactically incorrect address, a malformed public key, or arguments of the wrong length or type to a VM hook.
-
Protocol Constraint Violations: Attempting to create a token with an invalid ticker name 8, or trying to access a protected storage key reserved for the system.
-
Logical Impossibilities: Performing an integer division by zero or attempting an out-of-bounds memory access within the VM.
-
Security Boundary Violations: A contract trying to manipulate the storage or code of another contract without authorization.
For these cases, FailExecution remains the only correct response. It protects the chain’s integrity and provides a clear, unmistakable signal to the developer that a critical bug exists in their code.
2.1.2 Class 2: Recoverable, State-Contingent Errors (Transition to Returnable Errors)
This class includes errors that arise not from a flaw in the contract’s code but from the specific state of the blockchain at the moment of execution. These conditions are often temporary or dependent on user actions, and a well-designed contract should be able to anticipate and react to them.
Examples of Class 2 errors include:
-
Insufficient Funds: Attempting to transfer more EGLD or ESDT tokens than an account possesses.
-
Ownership Mismatches: Trying to transfer an NFT that the calling address does not own.
-
Permission Denials: Calling a function that requires a specific role (e.g., ESDTRoleLocalMint) that the caller has not been granted.8
-
State-Based Precondition Failures: Attempting to participate in a sale that has already concluded or trying to claim rewards that are not yet available.
For these state-contingent failures, FailExecution is an overly punitive measure. A returnable error code empowers the contract to handle the situation gracefully, improving both developer flexibility and end-user experience.
2.2 The “Soft-Fail” Execution Flow
The implementation of this new error-handling paradigm follows a “soft-fail” execution flow. This flow maintains the core security principle of the stateless VM while providing the necessary feedback to the smart contract. The process is as follows:
-
Invocation: A smart contract calls a VM hook that has been designated as “fallible” (e.g., an ESDT transfer function).
-
Error Detection: The VM hook executes its internal logic and encounters a Class 2 error condition (e.g., the sender’s balance is insufficient).
-
Error Return: Instead of invoking FailExecution, the hook immediately ceases its operation and returns a specific, non-zero integer error code to its caller (the VM). It may optionally return a small data payload containing additional context (e.g., the required balance).
-
Partial State Discard: Crucially, the VM discards any transient state changes that were buffered by that specific hook invocation. For example, if the hook had tentatively debited the sender’s account before checking the final balance, that debit is discarded. However, the larger transient state buffer for the entire transaction remains intact.
-
Return to Contract: The VM returns control to the smart contract, passing along the error code received from the hook.
-
Programmatic Handling: The smart contract, now aware of the specific failure, can use its remaining gas to execute alternative logic. It can check the error code and decide whether to try a different action, log the event for off-chain systems, or simply terminate its execution path gracefully.
-
Transaction Conclusion: If the contract handles the error and continues, or simply finishes its execution path without panicking, the transaction is considered successful. The state changes accumulated before and after the soft-failing call are committed to the blockchain.
This flow provides a powerful try/catch-like mechanism at the smart contract level. It preserves the safety of the system by ensuring no invalid state is ever committed while simultaneously breaking the fragile dependency chain of the all-or-nothing FailExecution model.
2.3 Gas Economics of Fallible Hooks
A critical aspect of this architecture is its interaction with the gas economy of the network. A soft-fail operation cannot be free, as this would open a new vector for denial-of-service attacks where a contract could call a failing function in an infinite loop without penalty.
The gas model for fallible hooks must adhere to the following principles:
-
Cost of Work Done: The gas consumed by a VM hook up to the point where it returns an error must be deducted from the transaction’s total gas limit. This includes the cost of reading state, performing checks, and any computation performed before the failure was detected.
-
Deterministic Costing: The gas cost for a failing call must be deterministic and predictable. Developers should be able to reason about the gas implications of both the success and failure paths of their contract logic.
-
Fair Pricing: The cost of a soft-failing hook should be roughly equivalent to the cost of the checks that led to the failure. It should be cheaper than a successful call (which involves writing state) but more expensive than a simple no-op. The gas schedule, which defines costs for various operations 8, would need to be updated to reflect these new execution paths.
By correctly pricing these soft-fail operations, the network ensures that computational resources are paid for, maintaining economic stability while enabling this more expressive and powerful programming model. This architecture is not a proposal to weaken the VM’s security but to enhance its capabilities. By carefully classifying errors and retaining FailExecution for truly critical failures, the fundamental safety guarantees of the protocol are preserved. The introduction of returnable errors for state-contingent conditions is a new layer built upon this secure foundation, providing developers with the tools needed to build the more complex, resilient, and user-friendly applications that will drive the future growth of the MultiversX ecosystem.
Section 3: Granular Analysis of vmhooks Operations
This section provides a detailed, function-by-function analysis of the core VM hooks, which are logically grouped into files such as baseOps.go, esdtOps.go, storageOps.go, and cryptoOps.go within the vmhost/vmhooks directory.10 Although direct access to the latest source code is unavailable 11, the functionality and error conditions of these hooks can be reliably inferred from a combination of developer documentation, protocol release notes, and the structure of related MultiversX repositories. For each function, this analysis identifies existing error conditions that trigger
FailExecution and proposes a new behavior based on the Class 1/Class 2 error classification framework.
3.1 Base Operations (baseOps.go)
These hooks are inferred to manage the most fundamental on-chain actions, such as native currency transfers and contract lifecycle management. Their correct and predictable behavior is paramount to the stability of the entire network.
| Function Name (Inferred) | Current Error Condition Triggering FailExecution | Proposed Behavior | Justification |
|---|---|---|---|
| Send | The source account has an insufficient EGLD balance to cover the transfer amount. | Returnable Error | This is the canonical Class 2 error. A contract attempting to make a payment or distribute funds should not be forced into a hard revert if one target account cannot be funded. It should be able to receive a specific ERROR_INSUFFICIENT_FUNDS code, log the failure, and continue processing other payments in the same transaction. This enables robust batch payment and escrow-release mechanisms. |
| CreateContract | A nonce collision occurs for the new contract address, or the creator account does not have sufficient balance for the deployment deposit. | Retain FailExecution | A nonce collision is a Class 1 error, suggesting a critical issue like a replay attack or a fundamental flaw in the deployment logic. It must cause a hard revert to prevent unintended contract overwrites or state corruption. Insufficient balance for deployment is also treated as a Class 1 error in this specific context, as contract deployment is a foundational, one-time setup action that should only be attempted when all preconditions are definitively met. |
| GetAccountBalance | The target address provided is syntactically invalid (e.g., wrong length, invalid checksum). | Retain FailExecution | Passing malformed data to the VM is a quintessential Class 1 developer error. The contract has failed a basic sanity check on its inputs. Allowing execution to continue could lead to unpredictable behavior based on how the invalid address is processed further down the line. A hard failure is the safest and most informative response. |
| GetShardOfAddress | The target address provided is syntactically invalid. | Retain FailExecution | The justification is identical to GetAccountBalance. The integrity of inputs passed to the VM’s core sharding and addressing logic must be strictly enforced with FailExecution. This prevents any ambiguity in state lookups. |
3.2 ESDT and NFT Operations (esdtOps.go)
This is arguably the most critical area for the proposed architectural changes, as token interactions are the lifeblood of DeFi, NFT marketplaces, and gaming applications. The ability to handle token-related failures gracefully is essential for building complex economic systems. The functions and their behaviors are inferred from the detailed ESDT documentation 8 and recent development activity, which explicitly mentions testing ESDT multi-transfers with returnable errors.7
| Function Name (Inferred) | Current Error Condition Triggering FailExecution | Proposed Behavior | Justification |
|---|---|---|---|
| ESDTNFTTransfer / MultiESDTNFTTransfer | The caller does not own the specified NFT/SFT nonce; the token is frozen or non-transferable; or the SFT amount is insufficient. | Returnable Error | This is a crucial Class 2 set of conditions. A marketplace contract attempting to execute a sale must be able to handle the case where the seller has already transferred the NFT in a separate transaction. A hard revert in this scenario would break the user experience. The contract needs to receive a specific error code (e.g., ERROR_NOT_OWNER, ERROR_TOKEN_FROZEN) to invalidate the specific trade and potentially notify the buyer, without failing the entire batch of trades it might be processing. This change is a prerequisite for building robust, high-throughput NFT exchanges. |
| ESDTLocalMint / ESDTLocalBurn | The caller does not possess the required ESDTRoleLocalMint or ESDTRoleLocalBurn special role. | Returnable Error | Permissions and roles can be dynamic. A contract might be designed to attempt a minting operation periodically, expecting it to succeed only when a governance vote has granted it the necessary role. A hard FailExecution makes this pattern clumsy and expensive. A returnable ERROR_PERMISSION_DENIED allows the contract to check its permissions by attempting the action, enabling more elegant and gas-efficient designs for DAOs and token controllers. |
| ESDTNFTCreate | The provided token name or ticker contains invalid characters, or the initial properties are non-compliant with protocol rules. | Retain FailExecution | The rules for token issuance are strict to ensure network-wide compatibility and prevent the creation of malformed or problematic assets.8 Violating these rules is a Class 1 setup error. It indicates a mistake in the deployment script or contract constructor that must be fixed. Failing early and hard prevents non-standard assets from polluting the ecosystem. |
| SetSpecialRole | The caller attempts to assign a role but does not have the role-management authority for that token. | Returnable Error | This is a Class 2 permission check. While assigning a role to an invalid (e.g., zero) address should be a hard failure (Class 1), the case where a valid caller attempts to grant a valid role to a valid address but simply lacks the permission should be recoverable. This allows a contract to attempt a permission change and react to the failure, which is useful in complex, multi-layered administrative contract setups. |
3.3 Storage and State Operations (storageOps.go)
These hooks are fundamental to a smart contract’s ability to persist data across transactions. Their security is non-negotiable, as they guard the integrity of a contract’s internal state.
The primary operations are Load (or StorageGet) and Save (or StorageSet). An important aspect of the current design is that Load operations for non-existent keys already behave in a fallible manner: they simply return an empty or zero-filled buffer rather than causing an error. This is the correct and desired behavior and should be maintained.
The only condition that should trigger a failure in this context is an attempt to access storage outside the contract’s permitted sandbox. This is a critical security boundary. Any attempt to read from or write to a protected key (e.g., keys used by the protocol itself) or the storage of another contract must be treated as a hostile act. Therefore, any such attempt is a Class 1 error, and FailExecution is the only appropriate response. This ensures that contracts remain isolated and cannot interfere with one another’s state, a cornerstone of blockchain security.
3.4 Cryptographic Operations (cryptoOps.go)
The introduction of new cryptographic VM hooks in releases like Spica (v1.8.4.0), including VerifySecp256r1, VerifyBLSSignatureShare, and VerifyBLSMultiSig 12, highlights the growing need for on-chain signature verification. These functions are essential for building multisig wallets, cross-chain bridges, and oracle systems. The utility of these hooks is directly tied to their ability to return a result rather than halting execution.
| Function Name (Inferred) | Current Error Condition Triggering FailExecution | Proposed Behavior | Justification |
|---|---|---|---|
| CheckSig / VerifyED25519 | The provided signature is invalid for the given message and public key. | Returnable Error/Boolean | This is a Class 2 condition. The entire purpose of a signature verification function from a smart contract’s perspective is to get an answer (true or false) to the question, Is this signature valid? Forcing a FailExecution on an invalid signature (false) renders the function almost useless for any logic that needs to count valid signatures or handle cases where some signatures in a set are invalid. The hook should return a zero for an invalid signature and a non-zero value for a valid one. |
| VerifySecp256r1, VerifyBLSSignatureShare, VerifyBLSMultiSig | The cryptographic verification of the signature or signature share fails. | Returnable Error/Boolean | As with the native CheckSig, these advanced cryptographic primitives are tools for decision-making within a contract. A bridge contract needs to check a batch of validator signatures and count how many are valid to reach a consensus threshold. If each invalid signature caused a hard revert, this would be impossible to implement on-chain. These hooks must return a clear, non-aborting success/failure indication. |
| Keccak256 / Sha256 | The input data provided to the hashing function is malformed (e.g., exceeds a VM-defined length limit). | Retain FailExecution | A hashing function itself does not fail based on the content of the data. A failure in this context would imply an issue with the parameters of the call itself, such as providing a pointer to a buffer that is too large for the VM to handle safely. This is a Class 1 developer error related to resource management and should result in a hard failure to prevent excessive memory consumption or other VM-level instabilities. |
A crucial element that emerges from this granular analysis is the need for specificity in the returned error codes. Simply returning a generic 1 for “error” is insufficient. To unlock the full potential of this new architecture, the VM hooks must return distinct error codes for different failure modes. For instance, a failing ESDTNFTTransfer call should return different codes for ERROR_NOT_OWNER, ERROR_TOKEN_FROZEN, and ERROR_INSUFFICIENT_SFT_BALANCE. This granularity allows the calling contract to implement sophisticated, multi-pathed logic (switch statements) to handle each failure case appropriately, transforming the error-handling mechanism from a simple binary check into a rich source of on-chain information.
Section 4: Consolidated Recommendations for Evolving FailExecution
The granular analysis of individual VM hooks reveals several recurring patterns of error conditions. By synthesizing these findings, it is possible to formulate a clear, thematic set of high-level recommendations for evolving the FailExecution model. This approach provides a strategic blueprint for protocol architects, organizing the proposed changes around the underlying nature of the error rather than specific function implementations. The guiding principle remains the consistent application of the Class 1 (unrecoverable) and Class 2 (recoverable) error framework.
4.1 Insufficient Balance/Quantity Conditions
A significant number of potential failures in smart contracts relate to accounts not possessing a sufficient quantity of a given asset, be it the native EGLD currency or a specific ESDT token. Currently, these scenarios typically result in a hard transaction revert.
Recommendation: All checks for insufficient balance or quantity of EGLD, fungible ESDT, or Semi-Fungible Tokens (SFTs) should be converted from FailExecution triggers to returnable errors.
Rationale: This is the most prevalent and intuitive category of recoverable, state-contingent (Class 2) errors. The balance of an account is a highly dynamic value that can change from one block to the next. A contract’s logic should not be considered flawed simply because an account’s balance was lower than expected at the moment of execution. By providing a specific, non-aborting error code (e.g., ERROR_INSUFFICIENT_FUNDS), the VM empowers developers to build far more resilient and practical applications. This change enables patterns such as:
-
Graceful Payment Failures: An e-commerce contract can attempt to charge a user and, upon receiving an insufficient funds error, log the failed order and notify the user to top up their account, rather than the transaction failing opaquely.
-
Batch Processing Resilience: A contract designed to airdrop tokens or pay out dividends to a large list of addresses can continue its loop even if some addresses in the batch cannot receive the funds. It can simply skip the failing transfers and successfully complete the rest.
-
Partial Fills: In a decentralized exchange, a contract could be designed to fill an order up to the available balance, a common feature that is difficult to implement when any shortfall causes a full revert.
Affected Hooks (Inferred): Send, ESDTTransfer, MultiESDTNFTTransfer (for SFTs), ESDTLocalBurn.
4.2 Ownership and Permissions Conditions
The second major category of state-contingent errors involves access control: checking if a caller owns a specific asset (like an NFT) or possesses the necessary rights (special roles) to perform an action.
Recommendation: All checks related to token ownership and the possession of special roles should be converted to returnable errors.
Rationale: Like balances, ownership and permissions are dynamic properties of the blockchain state. They are expected to change as a result of user actions and governance decisions. Treating a failed permission check as a catastrophic (Class 1) error prevents contracts from dynamically probing the state. A contract must be able to attempt an action and handle the result based on the permissions landscape at that exact moment. This is fundamental for:
-
NFT Marketplaces: An offer to buy an NFT should not fail catastrophically if the NFT was sold moments before. The contract should receive an ERROR_NOT_OWNER code, allowing it to invalidate the offer and refund the potential buyer in the same transaction.
-
Decentralized Autonomous Organizations (DAOs): A contract managing DAO proposals may allow certain actions only to be performed by addresses with a specific role. The contract logic should be able to attempt an administrative action and handle the ERROR_PERMISSION_DENIED case gracefully if the caller’s role has been revoked.
-
Automated Role Management: A parent contract could be designed to try to grant a role via a child contract, and if it fails due to its own permissions being revoked, it can enter a fallback state.
Affected Hooks (Inferred): ESDTNFTTransfer, MultiESDTNFTTransfer (for NFTs), ESDTLocalMint, ESDTLocalBurn, SetSpecialRole, UnsetSpecialRole.
4.3 Invalid Input Parameter Conditions
This category serves as the critical counterpoint to the proposed changes. It defines the boundary of what should remain unrecoverable. These are errors that stem not from the state of the world, but from the flawed construction of the call itself.
Recommendation: All checks for fundamentally invalid, malformed, out-of-bounds, or syntactically incorrect input parameters must continue to trigger FailExecution.
Rationale: This is a non-negotiable security and stability measure. The VM’s environment interface is a strict contract, and the smart contract is responsible for upholding its end by providing valid inputs. A failure to do so represents a bug in the smart contract code. Allowing a contract to “handle” an error from passing garbage input would create a dangerous precedent and could lead to unpredictable downstream consequences or resource exhaustion within the VM. Retaining FailExecution for these Class 1 errors:
-
Enforces Developer Discipline: It forces developers to sanitize and validate their inputs before calling the VM, which is a best practice.
-
Protects the VM: It prevents the VM from wasting cycles trying to process nonsensical data or from entering an undefined state.
-
Provides Clear Debugging Signals: A hard revert for an invalid parameter is an unambiguous signal to the developer that their contract’s logic is flawed and needs to be fixed and redeployed.
Affected Hooks (Inferred): GetAccountBalance (with invalid address format), ESDTNFTCreate (with invalid ticker format 8),
Load/Save (with protected/malicious keys), Keccak256 (with out-of-bounds length).
4.4 Cryptographic Verification Conditions
The final category involves the new and increasingly important cryptographic hooks used for signature verification.
Recommendation: All cryptographic verification hooks (CheckSig, VerifySecp256r1, etc.) should return a boolean-like result (e.g., 0 for failure, 1 for success) and must never trigger FailExecution for a simple verification mismatch.
Rationale: The very purpose of these functions, from a smart contract’s point of view, is to answer a question: “Is this signature valid?” The answer is inherently a piece of data (true or false) that the contract needs to continue its logic. Forcing a revert when the answer is false makes the function unusable for its primary on-chain use cases. A multisig contract, for instance, needs to iterate through a list of signatures, check each one, and count the valid ones. This is impossible if the first invalid signature halts the entire transaction. This change is essential for:
-
On-chain Multisig Wallets: Enabling contracts to verify a set of signatures and proceed only if an M-of-N threshold is met.
-
Oracles and Bridges: Allowing contracts to verify data bundles signed by a distributed set of reporters, ignoring any invalid signatures in the bundle.
-
Authentication Schemes: Supporting complex authentication logic where a user might provide one of several possible valid signatures.
Affected Hooks (Inferred): CheckSig, VerifyED25519, VerifySecp256r1, VerifyBLSSignatureShare, VerifyBLSMultiSig.12