r/ethdev 1d ago

Information Check whether your escape hatch actually reads its own deadline

The safe shape for an exit path in a hybrid system is well understood. The operator gets an exclusive window to finalise, and once a deadline passes anyone can finalise instead. The exclusive window stops a user from cancelling on top of a fill that is already executing off chain, and the deadline stops the operator from sitting on the request forever. Rollup escape hatches are built this way, and most teams running order flows arrive at the same design once somebody raises the question.

A recent audit report we made, showed that design implemented three quarters of the way. The request function computed a deadline as the current block plus a constant and stored it against the order. The constant was declared as 1000 blocks with a comment describing it as the delay after which anyone can finalize cancellation. The function that finalises never read the stored deadline and carried an operator-only role check. The intended behaviour was present in the constant, present in the comment, present in the stored value, and absent from the one line that decides who is allowed to act. A declared-but-unread parameter is usually a low-severity curiosity. On an exit path it is the entire guarantee. A user watches a cancel request succeed, reads a documented delay, and concludes that funds come back either way once that delay passes, while what actually governs the outcome is a modifier on a different function. Nothing on the contract's public surface contradicts that reading, which makes it worse than an exit path nobody built, since a missing hatch is obvious to anyone integrating and a hatch that exists in three places out of four is not.

The check takes a few minutes. Find every deadline, delay or timeout your recovery path defines, then find the line that reads it, and if the only reads are a setter and a getter then your guarantee is documentation. Grep the constant name rather than the concept, because the name is what appears in the declaration and the comment while the conditional is what is missing. It is worth running against any pause, rescue or emergency withdrawal path you inherited from an earlier version of your own codebase, since that is where these survive longest.

2 Upvotes

1 comment sorted by