docs/solidity-guides/logics/error_handling.md
This document explains how to handle errors effectively in FHEVM smart contracts. Since transactions involving encrypted data do not automatically revert when conditions are not met, developers need alternative mechanisms to communicate errors to users.
In the context of encrypted data:
To address these challenges, implement an error handler that records the most recent error for each user. This allows dApps or frontends to query error states and provide appropriate feedback to users.
The following contract snippet demonstrates how to implement and use an error handler:
struct LastError {
euint8 error; // Encrypted error code
uint timestamp; // Timestamp of the error
}
// Define error codes
euint8 internal NO_ERROR;
euint8 internal NOT_ENOUGH_FUNDS;
constructor() {
NO_ERROR = FHE.asEuint8(0); // Code 0: No error
NOT_ENOUGH_FUNDS = FHE.asEuint8(1); // Code 1: Insufficient funds
}
// Store the last error for each address
mapping(address => LastError) private _lastErrors;
// Event to notify about an error state change
event ErrorChanged(address indexed user);
/**
* @dev Set the last error for a specific address.
* @param error Encrypted error code.
* @param addr Address of the user.
*/
function setLastError(euint8 error, address addr) private {
_lastErrors[addr] = LastError(error, block.timestamp);
emit ErrorChanged(addr);
}
/**
* @dev Internal transfer function with error handling.
* @param from Sender's address.
* @param to Recipient's address.
* @param amount Encrypted transfer amount.
*/
function _transfer(address from, address to, euint32 amount) internal {
// Check if the sender has enough balance to transfer
ebool canTransfer = FHE.le(amount, balances[from]);
// Log the error state: NO_ERROR or NOT_ENOUGH_FUNDS
setLastError(FHE.select(canTransfer, NO_ERROR, NOT_ENOUGH_FUNDS), msg.sender);
// Perform the transfer operation conditionally
balances[to] = FHE.add(balances[to], FHE.select(canTransfer, amount, FHE.asEuint32(0)));
FHE.allowThis(balances[to]);
FHE.allow(balances[to], to);
balances[from] = FHE.sub(balances[from], FHE.select(canTransfer, amount, FHE.asEuint32(0)));
FHE.allowThis(balances[from]);
FHE.allow(balances[from], from);
}
NO_ERROR: Indicates a successful operation.NOT_ENOUGH_FUNDS: Indicates insufficient balance for a transfer.setLastError function to log the latest error for a specific address along with the current timestamp.ErrorChanged event to notify external systems (e.g., dApps) about the error state change.FHE.select function to update balances and log errors based on the transfer condition (canTransfer)._lastErrors for a user’s most recent error and display appropriate feedback, such as "Insufficient funds" or "Transaction successful."The frontend or another contract can query the _lastErrors mapping to retrieve error details:
/**
* @dev Get the last error for a specific address.
* @param user Address of the user.
* @return error Encrypted error code.
* @return timestamp Timestamp of the error.
*/
function getLastError(address user) public view returns (euint8 error, uint timestamp) {
LastError memory lastError = _lastErrors[user];
return (lastError.error, lastError.timestamp);
}
ErrorChanged event.By implementing error handlers as demonstrated, developers can ensure a seamless user experience while maintaining the privacy and integrity of encrypted data operations.