# 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:

<table><thead><tr><th>Type</th><th>Selector</th><th width="217.08984375">Description</th><th>Example</th></tr></thead><tbody><tr><td><code>Error(string)</code></td><td><code>0x08c379a0</code></td><td>Standard revert reason with message</td><td><code>require(balance > 0, "Zero balance");</code></td></tr><tr><td><code>Panic(uint256)</code></td><td><code>0x4e487b71</code></td><td>Internal error (e.g., overflow, div/0, invalid enum)</td><td><code>assert(x > 0);</code></td></tr><tr><td>Custom Errors</td><td>Function selector of custom error</td><td><code>error Unauthorized(address caller);</code></td><td></td></tr></tbody></table>

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.

{% code title="example.ts" %}

```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);
}
```

{% endcode %}

In **Chai matchers**:

{% code title="chai-example.ts" %}

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

{% endcode %}

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

{% code title="decode-custom-error.ts" %}

```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);
  }
}
```

{% endcode %}

### 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:

{% code title="detect-panic.ts" %}

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

{% endcode %}

### 1.4 Advanced Revert Inspection with Hardhat Traces

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

{% code title="trace" %}

```bash
npx hardhat test --trace
```

{% endcode %}

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:

{% code title="fetch-abi.ts" %}

```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));
```

{% endcode %}

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

{% code title="Treasury.sol" %}

```solidity
error Unauthorized(address caller);

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

{% endcode %}

Decoding in JS:

{% code title="catch-unauthorized.ts" %}

```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]);
}
```

{% endcode %}

### 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:

{% code title="foundry-verbosity" %}

```bash
forge test -vvvv
```

{% endcode %}

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:

{% code title="get-impl.ts" %}

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

{% endcode %}

***

## 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:

{% code title="hardhat-fork" %}

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

{% endcode %}

Or in configuration:

{% code title="hardhat-config.ts" %}

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

{% endcode %}

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.

{% code title="callstatic.ts" %}

```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);
}
```

{% endcode %}

### 3.3 Using eth\_call Manually

For raw RPC simulation:

{% code title="eth-call.ts" %}

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

{% endcode %}

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

See the [JSON-RPC API Reference](/developer/json-rpc-api.md) for `eth_call` parameter details and error codes.

### 3.4 Impersonating Accounts for Privileged Actions

Simulate admin or contract-controlled operations:

{% code title="impersonate.ts" %}

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

{% endcode %}

Stop impersonation when finished:

{% code title="stop-impersonate.ts" %}

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

{% endcode %}

### 3.5 Snapshot and Rollback Control

Snapshots let you test different outcomes quickly:

{% code title="snapshot.ts" %}

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

{% endcode %}

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:

{% code title="replay-tx.ts" %}

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

{% endcode %}

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

### 3.7 Advanced Fork Testing with Foundry

{% code title="anvil-forge" %}

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

{% endcode %}

You can use cheatcodes like:

{% code title="foundry-cheatcodes.sol" %}

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

{% endcode %}

### 3.8 Gas Profiling and Cost Analysis

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

{% code title="estimate-gas.ts" %}

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

{% endcode %}

Compare against Shannon and Mainnet to identify anomalies.

### 3.9 Full Transaction Lifecycle Test

{% stepper %}
{% step %}
**Fork Somnia Testnet**

Create a fork of the testnet to reproduce on-chain state locally.
{% endstep %}

{% step %}
**Impersonate the deployer account**

Use impersonation to perform privileged actions and reproduce behavior.
{% endstep %}

{% step %}
**Run callStatic to simulate critical functions**

Dry-run core functions to inspect return values and revert reasons.
{% endstep %}

{% step %}
**Capture reverts and decode with ABI**

Decode revert data, custom errors, and panic codes to get actionable context.
{% endstep %}

{% step %}
**Use snapshot/revert to iterate quickly**

Take snapshots to test multiple scenarios and revert between them.
{% endstep %}

{% step %}
**Once clean, deploy and verify on testnet**

After local verification, deploy to the testnet and verify behavior.
{% endstep %}

{% step %}
**Run same steps on mainnet fork before release**

Final validation on a mainnet fork ensures production parity.
{% endstep %}
{% endstepper %}

***

{% hint style="info" %}
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.
{% endhint %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.somnia.network/developer/development-frameworks/debug-playbook.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
