managed-oracle: add partial-success SignedProposer batching - #72
Conversation
| calldata, client transaction-pool policy, and available block gas provide the practical bounds. | ||
| Each valid child executes by self-delegatecall with the original relayer as `msg.sender`. Ordinary | ||
| child reverts do not roll back successful siblings, but exhausting the transaction's gas while | ||
| executing a child or processing its revert data will revert the entire batch. |
There was a problem hiding this comment.
Under EIP-150 the outer frame always retains 1/64 of remaining gas after a child delegatecall, so a gas-exhausting child does not reliably revert the whole batch. Reproducing with RevertingSignedProposerOracle(exhaustGas=true) plus one valid child on this branch: at 1M gas the batch fully reverts (as the existing test shows), at 30M gas it succeeds returning [false, true], and at 8M gas it succeeds returning [false, false] - the valid sibling is reported via ProposalCallFailed with errorSelector 0x00000000 and revertDataHash keccak256(""), indistinguishable from a genuinely failing proposal. Consider rewording this to say the batch may either revert entirely or continue with later children failing from gas starvation, and adding a test for the continue-after-exhaustion regime so consumers of successes[] / ProposalCallFailed do not treat gas-starved children as definitively failed proposals.
There was a problem hiding this comment.
Good catch — you're right about the EIP-150 behavior and the ambiguity of empty failure metadata. I updated the NatSpec, README, and PR description to say that exhaustion can either revert the outer batch or return false and continue with later children potentially gas-starved; false now explicitly means only an unsuccessful execution attempt, not a definitively invalid proposal.
I also added tests for the exact 1M / 8M / 30M regimes (outer revert, [false,false], [false,true]), empty-revert vs OOG metadata ambiguity, gas-exhausting children in first/middle/last position, persistence of successful siblings, and rollback of earlier success when the outer frame eventually runs out of gas. A real Permit2 test now exhausts gas after nonce consumption, token transfer, allowance, and oracle bond pull, proving all failed-child state rolls back while a later valid sibling succeeds. The pinned suite is green: 132 non-fork and 16 Polygon fork tests.
What Changed
tryMulticall(bytes[]), gated byDELEGATED_PROPOSER_ROLE, for partial-success batches of ABI-encodedSignedProposer.proposecalls.delegatecallso the original relayer remainsmsg.senderand each proposal retains the existing Permit2, whitelist, refund, payment, reentrancy, and event behavior.bool[]; unsuccessful execution attempts emit boundedProposalCallFailed(index, callHash, errorSelector, revertDataHash)metadata.multicall(bytes[])unchanged and does not modify the execution worker, ManagedOptimisticOracleV2, or existing oracle interfaces.Why
tryMulticalllets valid proposals complete independently when the outer call retains enough gas, while providing deterministic correlation for each failed execution attempt.Impact
proposeand atomicmulticallintegrations remain compatible; delegated proposers may opt intotryMulticall.ProposalExecutedand Managed OOProposePriceevents.falsewhile the outer batch continues with its preserved 1/64 gas. Later children may then be gas-starved and also returnfalse. If the outer call cannot finish the loop or encode the result, the entire batch reverts and rolls back prior successes.falseresult orProposalCallFailedmeans only that the execution attempt failed; it does not prove the proposal itself is invalid. Empty failure metadata is ambiguous between an empty revert and out-of-gas.chrismaree/signed-proposerand depends on the SignedProposer introduction in PR managed-oracle: add audited SignedProposer #69.High risk Sections to review with detail
SignedProposer.proposeis accepted, selector validation happens before any child executes, the outer relayer remainsmsg.sender, and the existing per-proposalnonReentrantmodifier enters and exits independently.[false, false]continuation with a starved later child,[false, true]recovery, gas-exhausting child position, and outer-revert rollback regimes. There is intentionally no child gas cap.index,keccak256(calls[index]), first-four-byte selector handling, exact revert-data hashing, event order, and the documented ambiguity of empty failure metadata.81,683,818, and asserted 14/15 boundary. The 128 KiB behavior is also covered by Bor's oversized-transaction tests.multicallare unchanged.Validation
1.3.6:forge fmt --check1.3.6:forge build --sizes1.3.6:forge test -vvv --no-match-contract ".*Fork.*"— 132 passed1.3.6:forge test --match-contract ".*Fork.*" --fork-url polygon -vvv— 16 passed