Build / Development Frameworks

Debug Playbook

1. Revert Decoding

Debugging smart contracts on the Somnia Network requires an understanding of how and why transactions fail. Every reverted transaction carries encoded data that can reveal the root cause of failure, whether it’s due to logic errors, insufficient gas, failed access checks, or internal Solidity panics.

1.1 Anatomy of a Revert

When a transaction fails on Somnia, the EVM halts execution and returns revert data, an ABI-encoded payload that follows one of these three formats:

TypeSelectorDescriptionExample
Error(string)0x08c379a0Standard revert reason with messagerequire(balance > 0, "Zero balance");
Panic(uint256)0x4e487b71Internal error (e.g., overflow, div/0, invalid enum)assert(x > 0);
Custom ErrorsFunction selector of custom errorerror Unauthorized(address caller);

On Somnia, these behave identically to Ethereum but may differ in gas costs and stack trace length depending on the validator node configuration.

1.2 Catching and Displaying Reverts in Hardhat

When running tests or scripts on Somnia, wrap calls in try/catch to capture the revert reason.

example.ts

try {
  await treasury.withdraw(1000);
} catch (error: any) {
  console.log('Revert reason:', error.reason || error.message);
  console.log('Full error data:', error.data || error.error?.data);
}

In Chai matchers:

chai-example.ts

await expect(treasury.connect(user).withdraw(1000))
  .to.be.revertedWith('Insufficient funds');

If your contract uses custom errors, Hardhat will not automatically print the name. Decode it manually:

decode-custom-error.ts

const iface = new ethers.utils.Interface(contractABI);
try {
  await treasury.connect(attacker).withdraw(9999);
} catch (error: any) {
  const data = error.data || error.error?.data;
  if (data) {
    const decoded = iface.parseError(data);
    console.log(`Custom Error: ${decoded.name}`);
    console.log('Arguments:', decoded.args);
  }
}

1.3 Decoding Panic Codes

Internal Solidity panics correspond to low-level EVM exceptions. Somnia propagates these codes like any other EVM chain.

Panic Code Description Typical Cause
0x01 Assertion failed Logic invariant broken
0x11 Arithmetic overflow/underflow Unchecked math operation
0x12 Division by zero Incorrect math division
0x21 Invalid enum conversion Out-of-bounds value
0x31 Storage array index out of bounds Bad loop or mapping access
0x32 Memory array index out of bounds Corrupt array operation

To detect Panic errors dynamically:

detect-panic.ts

if (error.data?.startsWith('0x4e487b71')) {
  const code = parseInt(error.data.slice(10), 16);
  console.log('Panic Code:', `0x${code.toString(16)}`);
}

1.4 Advanced Revert Inspection with Hardhat Traces

Hardhat’s tracing layer can reveal the full execution path of a revert.

trace

npx hardhat test --trace

You’ll see nested calls, gas usage per function, and exactly where the failure occurred. This is invaluable for multi-contract interactions like on-chain governance or liquidity management.

Example output:

CALL treasury.withdraw
 └─ CALL token.transfer -> reverted with reason: 'Insufficient balance'

1.5 Custom Error Decoding for Verified Contracts

If a Somnia contract is verified on the explorer, you can fetch its ABI dynamically to decode errors programmatically:

fetch-abi.ts

import axios from 'axios';
const abiURL = `https://explorer.somnia.network/api?module=contract&action=getabi&address=${address}`;
const { data } = await axios.get(abiURL);
const iface = new ethers.utils.Interface(JSON.parse(data.result));

Then use iface.parseError(error.data) to decode reverts directly from on-chain logs or transactions.


2. Common Error Patterns on Somnia

Even experienced developers encounter recurring issues. Below are the most common EVM-level errors observed when deploying or testing on Somnia Testnet (Shannon) and Mainnet.

Error Type Cause Fix
execution reverted Fallback revert with no message Add explicit revert messages or decode ABI data
out of gas Gas exhausted mid-call Use estimateGas() or increase gas limit
invalid opcode Calling a non-existent function Validate ABI and deployed bytecode
nonce too low Pending transaction not mined yet Wait for confirmation or reset nonce
replacement underpriced Gas bump too small Raise gas price by 10–20%
static call violation State-changing call via eth_call Use .sendTransaction() instead

2.1 Example: Catching a Custom Error in Somnia Treasury Contract

Treasury.sol

error Unauthorized(address caller);

function mint(address to, uint amount) external {
  if (msg.sender != owner) revert Unauthorized(msg.sender);
  _mint(to, amount);
}

Decoding in JS:

catch-unauthorized.ts

try {
  await treasury.connect(randomUser).mint(addr, 100);
} catch (e: any) {
  const iface = new ethers.utils.Interface(['error Unauthorized(address caller)']);
  const decoded = iface.parseError(e.data);
  console.log('Unauthorized address:', decoded.args[0]);
}

2.2 Handling Complex Contract Interactions

When interacting with multi-layered DeFi protocols or bridging modules on Somnia, reverts can originate several calls deep. Use Hardhat’s trace or Foundry’s -vvvv verbosity to see the full stack.

Foundry example:

foundry-verbosity

forge test -vvvv

This reveals each opcode execution, event emission, and revert reason.

2.3 Invalid ABI or Proxy Conflicts

Many Somnia projects use upgradeable proxies. Reverts from a proxy may originate in the implementation contract. If you get a generic execution reverted, verify you’re using the correct implementation ABI:

get-impl.ts

const implAddr = await provider.getStorageAt(proxyAddress, '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc');
const iface = new ethers.utils.Interface(implementationABI);

3. Transaction Simulation

Simulating transactions allows developers to predict revert causes, estimate gas usage, and test behaviors without risking real SOMI or STT.

3.1 Fork Somnia Networks Locally

Create a local fork of Somnia Mainnet or Shannon Testnet:

hardhat-fork

npx hardhat node --fork https://api.infra.mainnet.somnia.network

Or in configuration:

hardhat-config.ts

networks: {
  hardhat: {
    forking: {
      url: process.env.SOMNIA_RPC_TESTNET,
      blockNumber: 123456, //example
    }
  }
}

This mirrors on-chain state locally, so you can safely replay any transaction.

3.2 Using callStatic for Dry-Run Simulation

callStatic runs a transaction without broadcasting or altering state.

callstatic.ts

try {
  const result = await treasury.callStatic.withdraw(1000);
  console.log('Call successful:', result);
} catch (error: any) {
  console.log('Simulation failed with reason:', error.reason);
}

3.3 Using eth_call Manually

For raw RPC simulation:

eth-call.ts

const tx = {
  to: contract.address,
  data: contract.interface.encodeFunctionData('stake', [amount])
};
const result = await provider.call(tx);
console.log('Returned data:', result);

If the call reverts, inspect error.data to decode it with the ABI.

See the JSON-RPC API Reference for eth_call parameter details and error codes.

3.4 Impersonating Accounts for Privileged Actions

Simulate admin or contract-controlled operations:

impersonate.ts

await network.provider.request({
  method: 'hardhat_impersonateAccount',
  params: ['0xAdminAddress']
});
const admin = await ethers.getSigner('0xAdminAddress');
await treasury.connect(admin).setFee(5);

Stop impersonation when finished:

stop-impersonate.ts

await network.provider.request({
  method: 'hardhat_stopImpersonatingAccount',
  params: ['0xAdminAddress']
});

3.5 Snapshot and Rollback Control

Snapshots let you test different outcomes quickly:

snapshot.ts

const snapshot = await network.provider.send('evm_snapshot', []);
await treasury.mint(100);
await network.provider.send('evm_revert', [snapshot]);

This resets the blockchain to its previous state instantly.

3.6 Simulating On-Chain Transactions

If you have a failed transaction hash from Somnia Mainnet:

replay-tx.ts

const tx = await provider.getTransaction('0x123...');
await provider.call({ to: tx.to!, data: tx.data });

This reproduces the failure locally and lets you inspect the revert reason directly in Hardhat.

3.7 Advanced Fork Testing with Foundry

anvil-forge

anvil --fork-url https://dream-rpc.somnia.network --fork-block-number 3456789
forge test -vvvv

You can use cheatcodes like:

foundry-cheatcodes.sol

vm.startPrank(admin);
contract.withdraw(1000);
vm.stopPrank();

3.8 Gas Profiling and Cost Analysis

Somnia gas costs can differ from Ethereum due to consensus differences. Always estimate gas usage per function:

estimate-gas.ts

const gas = await contract.estimateGas.executeTrade(orderId);
console.log('Estimated gas:', gas.toString());

Compare against Shannon and Mainnet to identify anomalies.

3.9 Full Transaction Lifecycle Test

Fork Somnia Testnet

Create a fork of the testnet to reproduce on-chain state locally.

Impersonate the deployer account

Use impersonation to perform privileged actions and reproduce behavior.

Run callStatic to simulate critical functions

Dry-run core functions to inspect return values and revert reasons.

Capture reverts and decode with ABI

Decode revert data, custom errors, and panic codes to get actionable context.

Use snapshot/revert to iterate quickly

Take snapshots to test multiple scenarios and revert between them.

Once clean, deploy and verify on testnet

After local verification, deploy to the testnet and verify behavior.

Run same steps on mainnet fork before release

Final validation on a mainnet fork ensures production parity.


Note

Summary

  • Always decode revert data rather than relying on generic error strings.
  • Decode custom errors to get structured failure context.
  • Use forked local environments for safe and realistic debugging.
  • Combine callStatic, trace, and snapshot/revert for fast iteration.
  • Validate gas behavior across testnet and mainnet for accurate production cost.

Debugging on Somnia means understanding the EVM intimately. Every revert, panic, and trace is a clue—decode them, simulate safely, and ship with confidence.

Copied from docs.somnia.network (somnia-main-docs) by scripts/import-gitbook.mts, until it finds a home in a product's docs.Edit this page