liquid_arc
Field note 002 / Base / Morpho / gas

A gas limit that looked like a liquidity cliff

liquid_arc / October 5, 2026
700,000
the fixed gas limit on the call
695,582
the gas the full-size call actually uses
$8,618
cleared at full size once the limit was raised

Summary

A simulated liquidation on Base failed at every size from 55% of the position upward, with no error message. It read as a hard limit on how much collateral the pool could absorb. It was the transaction's gas limit.

With the limit raised, the full position cleared $8,618 through a single route. The liquidator who really took that position made about $9,033.

What it looked like

The position was $79,623 of debt against cbXRP collateral on Morpho, in Base block 51,354,208 on September 15, 2026. The liquidation contract borrows the debt, repays it, takes the collateral, sells it through one route and keeps what is left.

Sized at half the position, the call passed. Sized at 55%, 75% or 100%, it failed, and the failing calls returned empty data. No reason, no custom error.

The obvious reading was a cliff in the pool: somewhere between 33,882 and 37,270 cbXRP, the route stopped being able to absorb the sale. That reading was wrong, and several rounds of work were built on it before it was caught.

What it was

The calls were running out of gas. The full-size call uses 695,582 gas, and the limit was a fixed 700,000. That looks like enough. It is not.

A contract can pass on at most 63/64 of its remaining gas when it calls another contract. A liquidation like this one nests several calls deep: the flash loan, its callback, the liquidation, the swaps and their callbacks. Each level keeps back a sliver, so the innermost call gets less than the arithmetic suggests.

When an inner call runs out, it does not explain itself. It returns nothing, and from outside that is indistinguishable from a revert with no message.

The measurement

The same calls at a 3,000,000 gas limit, against the same block:

Share of positioncbXRP seizedDebt repaidGas usedUSDC receivedKept after repaying
55%37,270$43,793620,794$48,972$5,179
100%67,764$79,623695,582$88,241$8,618

Both pass. The quote the contract was built from matches what it received to within 0.11%. The 75% size passes too.

How to tell them apart

  1. Look at what came back. A real revert from a contract carries a reason or a custom error. A call that ran out of gas inside a nested call often comes back with nothing at all.
  2. Run it again with far more gas. The same call, the same block, the limit raised from 700,000 to 3,000,000. If it now passes, the pool was never the problem.
  3. Trace it. debug_traceCall shows the inner frame that failed and why. Here it said out of gas, not a failed swap.
  4. Stop hardcoding the limit. Estimate each call, then add headroom. A margin of 15% over the measured use covers what the nesting takes.

Against the real liquidator

The liquidator who took this position on chain cleared about $9,033 by splitting the sale across several pools. One route, sized in full, gets $8,618, or 95% of that. The pool had the depth all along.

Limits

  • One position, one route, one day. The numbers are from a single large liquidation on September 15, 2026.
  • It needs a contract to rerun. Unlike report 001, this cannot be checked from an RPC endpoint alone. The pattern can: any call nested several levels deep will show it.
  • The 75% size is reported without its row. It passed at the higher limit, but its full figures were not kept.

Check your own

With your own contract and calldata, the test is two calls. Set the variables first.

# The same call at two limits, against the same block.
# Empty output at the first and a result at the second means gas, not liquidity.
cast call $CONTRACT $CALLDATA --from $OWNER --gas-limit 700000  --block $BLOCK --rpc-url $RPC
cast call $CONTRACT $CALLDATA --from $OWNER --gas-limit 3000000 --block $BLOCK --rpc-url $RPC

# What the call really uses, to size the limit from
cast estimate $CONTRACT $CALLDATA --from $OWNER --block $BLOCK --rpc-url $RPC
Back to liquid_arc
basechain connecting