Error: execution reverted (no data present; likely require(false) occurred)
"Execution reverted" only tells you that the EVM hit a REVERT opcode (or an equivalent failure) and undid every state change in the call. The actual reason, if there is one, is in the revert data returned with it. Most of the time the reason is there and your tooling just isn't decoding it.
Where you see it
- Before sending. Wallets and libraries call
eth_estimateGasfirst. If the simulated call reverts, you get "execution reverted" and nothing is broadcast. This is the most common case, and the cheapest to debug. - After mining. The transaction was included but the receipt has
status: 0. You paid for the gas used and nothing changed. The receipt doesn't contain the revert data, so you have to replay the transaction to see it. - On a read. An
eth_callto aviewfunction reverts, for example because of a bad argument or because there's no contract at that address on this chain.
What the revert data can contain
The data is ABI-encoded, and its first 4 bytes tell you which kind it is:
| Revert data starts with | Produced by | Contains |
|---|---|---|
0x08c379a0 | require(cond, "message"), revert("message") | Error(string): your message |
0x4e487b71 | Compiler-inserted checks | Panic(uint256): a numeric code |
| any other 4 bytes | revert MyError(...), require(cond, MyError(...)) | A custom error selector plus its arguments |
empty (0x) | require(cond), revert(), out of gas, calling code that doesn't exist | Nothing |
The panic codes are defined by Solidity. The ones you'll actually meet:
| Code | Meaning |
|---|---|
0x01 | assert failed |
0x11 | Arithmetic overflow or underflow outside unchecked |
0x12 | Division or modulo by zero |
0x21 | Invalid value converted to an enum |
0x31 | .pop() on an empty array |
0x32 | Array index out of bounds |
0x41 | Allocated too much memory |
Decode it with ethers v6
ethers can decode all three forms, but it can only name a custom error if the error is in the ABI you gave the contract. Include errors from inherited contracts and libraries (for example OpenZeppelin v5's ERC20InsufficientBalance) in the ABI; Hardhat and Foundry artifacts already do.
Simulate the call with staticCall so you get the error without spending gas:
import { ethers, isError } from "ethers";
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const signer = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);
const token = new ethers.Contract(tokenAddress, tokenAbi, signer);
try {
await token.transfer.staticCall(to, amount);
} catch (e) {
if (isError(e, "CALL_EXCEPTION")) {
console.log("reason:", e.reason); // string message or panic description
console.log("decoded:", e.revert); // { name, signature, args } or null
console.log("raw data:", e.data); // hex revert data
} else {
throw e;
}
}
If e.revert is null but e.data is not empty, the error isn't in your ABI. Decode it against an interface that has it:
const iface = new ethers.Interface([
"error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed)",
"error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed)",
]);
console.log(iface.parseError(e.data));
Decode it with Foundry
Compute a selector from a signature, or look up an unknown selector in the public signature database:
cast sig "ERC20InsufficientAllowance(address,uint256,uint256)"
cast 4byte 0x<first-4-bytes-of-revert-data>
For a transaction that already failed on-chain, replay it locally with a full call trace. cast run re-executes the earlier transactions in the same block first, so the state matches what the transaction actually saw:
cast run 0xFailedTxHash --rpc-url $RPC_URL
The trace shows every internal call, and the innermost frame that reverted is usually the real culprit. Replaying an older transaction needs an archive node or a provider that serves historical state. Against a verified contract, add --etherscan-api-key so the trace shows function and error names instead of raw selectors.
In local tests, forge test -vvvv prints the same kind of trace for failing tests, and Hardhat prints Solidity stack traces for transactions on its built-in network.
When the data is empty
No data means there's no message to decode, so reason from the mechanism:
require(cond)with no message, or a barerevert(). Read the function's conditions one by one.- The address has no code on this chain. A high-level call to it reverts with no data. Check with
cast code <address> --rpc-url $RPC_URL;0xmeans nothing is deployed there. This is often a wrong address, a wrong network, or a token that hasn't been deployed on that L2. - An inner call ran out of gas. Only 63/64 of the remaining gas is passed to a subcall. A subcall that runs out fails without data, and the caller may then revert without data too. Try again with a higher gas limit and see if the error changes.
- A low-level
callreturnedfalseand the contract reverted without forwarding the reason.
The usual suspects
Once you can see the reason, most reverts come down to a handful of causes:
- Missing or insufficient allowance before a
transferFrom(e.g. a DEX router or vault deposit). - Wrong
msg.sender: anonlyOwnerfunction called from a different account, or through a contract when the check expects the user. - Wrong
msg.value: sending ETH to a non-payablefunction, or too little for a mint. - Expired deadline or stale price in DeFi calls, e.g. slippage checks failing between simulation and inclusion.
- Paused contract or a time lock that hasn't elapsed.
- Wrong chain: the same address on a different network may hold a different contract, or nothing at all.
Checklist
- Reproduce with
staticCall/eth_callrather than sending a transaction. - Read
e.revert,e.reasonande.datafrom the ethersCALL_EXCEPTION. - Put all custom errors, including inherited ones, into the ABI you decode with.
- For a mined failure, use
cast run <txhash>to get a trace of the exact execution. - For empty data, check
cast code, gas limits and message-lessrequires. - Give your own
requires messages or, better, custom errors with arguments, so the next person can see why.
Get the weekly commit
New blockchain deep dives every week.