EVM / Solidity Error 0x08c379a0: Error(string) revert
0x08c379a0 is the selector of Error(string), emitted by require(cond, "msg") and revert("msg"). ABI-decode the rest of the data to read the reason string; fix the failing condition.
Identify: does your log match?
ContractFunctionExecutionError: The contract function "transfer" reverted with the following reason: ERC20: transfer amount exceeds balance Contract Call: address: 0x5FbDB2315678afecb367f032d93F642f64180aa3 function: transfer(address to, uint256 amount) args: (0x70997970C51812dc3A010C7d01b50e0d17dc79C8, 1000000000000000000000) sender: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 Raw revert data: 0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002645524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e63650000000000000000000000000000000000000000000000000000 Version: viem@2.21.37
- High
require() condition failed
A require(cond, "reason") evaluated false — balance, allowance, deadline or ownership check. The reason string is ABI-encoded after the selector.
- Medium
Pre-v5 OpenZeppelin string errors
OpenZeppelin 4.x contracts revert with strings like "ERC20: transfer amount exceeds balance"; v5 switched to custom errors.
- Low
Wrong msg.sender or value in simulation
eth_call without from, or with value 0, hits a guard that would pass for the real sender.
Fix
Replace require strings with custom errors
function withdraw(uint256 amount) external { require(balances[msg.sender] >= amount, "Insufficient balance"); balances[msg.sender] -= amount; payable(msg.sender).transfer(amount); }
error InsufficientBalance(uint256 available, uint256 required); function withdraw(uint256 amount) external { uint256 bal = balances[msg.sender]; if (bal < amount) revert InsufficientBalance(bal, amount); balances[msg.sender] = bal - amount; (bool ok, ) = payable(msg.sender).call{value: amount}(""); require(ok); }
Custom errors are cheaper to deploy and revert, and carry typed values. call{value:} avoids the 2300-gas stipend of transfer.
Verify: reproduce it on purpose
$ cast decode-error 0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002645524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e63650000000000000000000000000000000000000000000000000000 --sig "Error(string)"Run it against a dev environment to trigger 0x08c379a0 on purpose and test your error handler.
Prevent
- Always simulate with the real from address.
- Prefer custom errors in new contracts.
- Decode raw data with cast decode-error.
FAQ
How do I decode 0x08c379a0 revert data by hand?
Strip the 4-byte selector and ABI-decode the remainder as (string). cast decode-error <data> --sig "Error(string)" does it in one step.
Why does ethers v6 show "execution reverted" with no reason?
The RPC did not return revert data (some providers strip it on estimateGas). Retry with eth_call or a provider that returns data.
What is the gas cost difference vs custom errors?
Custom errors save deployment size and a few hundred gas per revert because the string is not stored or ABI-encoded.
Related errors
Last verified 2026-10-20 · Source: 4byte/OpenChain signatures + OpenZeppelin v5 custom errors + Panic codes · Official docs ↗ · ~4,400 searches/mo