Expand description
PaymentPool revert decoding: the failure-class taxonomy for the
buyer-side openPool path (#966) and the pool-lifecycle reverts.
The buyer open_pool kernel (decdn-client) can fail three ways
that an operator must triage differently:
InsufficientDeposit— a misconfiguration: the node’s USDC balance or standing allowance cannot cover the deposit; or the deposit is zero — either as requested, or as the balance delta actually received under a fee-on-transfer token; or the credited deposit is below the governance-setminDepositSybil floor. (A zero deposit cannot be requested: both the daemon and the CLI validatebuyer_working_deposit_micro_usdc/ the--working-deposit-micro-usdcflag> 0at config load, so the reachable zero here is the received-delta one.) The fix is operator-side (fund or resize the deposit), not infrastructure.ContractRevert— any other deterministic on-chain revert (a paused contract, a future revert reason). The deposit was not escrowed; the cause is on-chain state, not this node’s wallet or RPC.RpcError— a transient transport/RPC fault (connectivity, a timed out receipt wait, a transaction-nonce blip — the sender’s tx sequence number, unrelated to a voucher). Retrying typically clears it; the fix is infrastructure-side.
Splitting these lets the node bump the matching
decdn_pool_open_failures_{insufficient_deposit,contract_revert,rpc_error}_total
sibling counter (a plain counter field carries no label dimension, so each class is its own
counter rather than one labeled {reason=…} series) and emit the same
reason token as a structured-log field, so a dashboard distinguishes “the
operator under-funded the gas/USDC wallet” from “the RPC endpoint is flaky”
— the whole point of #966. The classification is driven by the alloy
error’s revert data: a deterministic revert carries ABI-encoded error data
(caught at gas estimation, so it surfaces on send() before a receipt),
whereas a transport fault carries none. A present revert selector is decoded
against the known insufficient-deposit error signatures to split
InsufficientDeposit from a generic ContractRevert. A token that reverts
in the older Error(string) style carries no custom-error selector, so its
message is matched for the same shortfall — an under-funded wallet reads as
InsufficientDeposit whichever revert style the deployed USDC uses.
The module also decodes topUp’s PoolNotOpen (is_pool_not_open) and
the closePool / reclaim reverts (PoolLifecycleRevert).
Structs§
- Below
MinDeposit PaymentPool.openPoolreverts this when the credited deposit — the received balance delta — is below the governance-setminDepositSybil floor. The fix is operator-side: raisebuyer_working_deposit_micro_usdcto at least the network minimum. Custom error with signatureBelowMinDeposit(uint256,uint256)and selector0x0267cc62.- ERC20
Insufficient Allowance OpenZeppelinv5ERC20: the standing allowance is below the transfer amount. Surfaces when the one-timeapprovenever ran (or was set too low) for thePaymentPoolspender. Custom error with signatureERC20InsufficientAllowance(address,uint256,uint256)and selector0xfb8f41b2.- ERC20
Insufficient Balance OpenZeppelinv5ERC20: the spender’s balance is below the transfer amount.openPool’ssafeTransferFrombubbles this up verbatim when the node’s USDC balance cannot cover the deposit. Custom error with signatureERC20InsufficientBalance(address,uint256,uint256)and selector0xe450d38c.- Grace
Window Active PaymentPool.reclaimreverts this before the pool’s dispute deadline. Declared here only for its 4-byte selector. Custom error with signatureGraceWindowActive()and selector0xad1b69f7.- NotPool
Owner PaymentPool.topUpandclosePoolrevert this when the caller is not the pool owner. Declared here only for its 4-byte selector. Custom error with signatureNotPoolOwner()and selector0xcfa0ad4e.- Pool
NotClosing PaymentPool.reclaimreverts this when the pool is notClosing. Declared here only for its 4-byte selector. Custom error with signaturePoolNotClosing()and selector0x9a96814d.- Pool
NotOpen PaymentPool.topUpandclosePoolrevert this when the pool isClosingorClosed. Declared here only for its 4-byte selector. Custom error with signaturePoolNotOpen()and selector0x14356134.- Zero
Amount PaymentPool.openPoolreverts this when the requested deposit — or the balance delta actually received, under a fee-on-transfer token — is zero. Declared here only for its 4-byte selector, so the classifier needs no live contract binding.
Enums§
- Pool
Lifecycle Revert - A
PaymentPoolpool-lifecycle revert thatclosePoolorreclaimcan raise. Decoding the selector names the cause, so a caller does not have to guess it from the call that failed. - Pool
Open Failure Reason - Which class of failure aborted a buyer
openPoolattempt (#966). Carried through theanyhowerror chain as typed context so the metrics layer can bump the matchingdecdn_pool_open_failures_{reason}_totalsibling counter (one counter per class — a plain counter field carries no label dimension) and attach a structuredreasonlog field, while the human-readable message is preserved for logs.
Functions§
- is_
erc20_ allowance_ shortfall - Whether an ERC-20 revert is specifically an allowance shortfall
(
ERC20InsufficientAllowance) — the one class a just-in-timeapprovecan fix. An insufficient-balance revert, a zero-amount revert, or any other selector returnsfalse: anapprovecannot recover those, so the caller must treat them as terminal. A string-revert token spells the same shortfall asError("ERC20: insufficient allowance"), which counts too. Pass alloy’sError::as_revert_data(). - is_
pool_ not_ open - Whether a
topUprevert isPoolNotOpen: the pool isClosingorClosedand accepts no funds. Pass alloy’sError::as_revert_data().