Replace string errors with custom errors in OnChainProposer contracts#6206
Replace string errors with custom errors in OnChainProposer contracts#6206avilagaston9 wants to merge 1 commit intomainfrom
Conversation
…racts
and update the Rust side to detect revert reasons by their 4-byte ABI selector
from the RPC error `data` field instead of string-matching on `message`.
The non-based contract used opaque hex codes ("007", "00c") while the based
contract used full strings. This caused a bug where l1_proof_sender's
message.contains("Invalid TDX proof") never matched the non-based contract's
codes, so invalid proofs were never deleted. Both contracts now use identical
custom errors defined in their interfaces, which are cheaper (no string
storage), self-documenting, and programmatically decodable.
Solidity: define custom errors in both IOnChainProposer interfaces, replace
~60 require/revert string patterns with if/revert custom error patterns.
Rust: add data field to RpcRequestError::RPCError, propagate revert data
in all RPC error construction sites, define selector constants, and update
l1_proof_sender and l1_proof_verifier to match on selectors. Also fixes the
stale "00m" code match in l1_proof_verifier.
🤖 Kimi Code ReviewReview SummaryThis PR replaces string-based Issues Found1. Missing Custom Error DefinitionsFile: The contract uses custom errors like Fix: Add all the custom error definitions to the contract itself, not just the interface. 2. Inconsistent Error NamingFile: The error 3. Selector Mismatch RiskFile: The hardcoded selectors in Rust ( Fix: Consider generating these selectors programmatically from the ABI or at least add tests to verify they match the actual contract errors. 4. Potential Re-entrancy in verifyBatchAlignedFile: The external call to 5. Error Handling in Rust Could Be More RobustFile: The error matching logic only checks if the data starts with the selector, but doesn't fully parse the error. This could lead to false matches if the error data contains additional parameters. Fix: Consider using a proper ABI decoder to parse the full error data. Positive Aspects
Minor Suggestions
Automated review by Kimi (Moonshot AI) · custom prompt |
🤖 Codex Code ReviewSummary Findings
If you address those, the rest looks solid. Automated review by OpenAI Codex · custom prompt |
🤖 Claude Code ReviewBased on my thorough review of the PR changes, here is my code review: Code Review: PR 6206 - Replace string errors with custom errors in OnChainProposer contractsSummaryThis PR successfully addresses a critical bug where invalid proofs weren't being detected and deleted by the Rust side. The changes improve error handling consistency across both OnChainProposer contracts and make the error detection mechanism more robust. Overall, the implementation is solid with just a few minor concerns. Critical IssuesNone found - The core functionality appears correct. High Priority1. Verify error selector constants (crates/l2/sequencer/utils.rs:28-31) The error selector constants need verification. These are derived from Recommended verification: cast sig "InvalidRisc0Proof()"
cast sig "InvalidSp1Proof()"
cast sig "InvalidTdxProof()"
cast sig "AlignedProofVerificationFailed()"If these don't match the constants defined in the code, the error detection will silently fail (the original bug won't be fixed). 2. Logic change in based contract (crates/l2/contracts/src/l1/based/OnChainProposer.sol:520-525) The boolean logic was refactored from a complex nested condition to a simpler OR condition: Before: require(
(!REQUIRE_SP1_PROOF || verificationKeys[commitHash][SP1_VERIFIER_ID] != bytes32(0)) &&
(!REQUIRE_RISC0_PROOF || verificationKeys[commitHash][RISC0_VERIFIER_ID] != bytes32(0)),
"013"
);After: if (
(REQUIRE_SP1_PROOF && verificationKeys[commitHash][SP1_VERIFIER_ID] == bytes32(0)) ||
(REQUIRE_RISC0_PROOF && verificationKeys[commitHash][RISC0_VERIFIER_ID] == bytes32(0))
) revert MissingVerificationKeyForCommit();The new logic correctly applies De Morgan's laws and is equivalent. However, this differs from the non-based contract which uses separate if statements (lines 185-197 of OnChainProposer.sol). For consistency and clarity, consider using the same pattern in both contracts. Medium Priority3. Missing access control check on verifyBatch in based contract (crates/l2/contracts/src/l1/based/OnChainProposer.sol:301) The 4. Removed batch number progression check in based contract (crates/l2/contracts/src/l1/based/OnChainProposer.sol:301-560) The non-based contract's if (batchNumber != lastVerifiedBatch + 1) revert BatchAlreadyVerified();This check is missing in the based contract's Low Priority / Style5. Inconsistent error naming (crates/l2/contracts/src/l1/interfaces/IOnChainProposer.sol:767) The non-based interface includes 6. Gas optimization opportunity (Multiple locations) The refactored code uses multi-line if statements without braces for single-statement bodies. While valid Solidity, consider wrapping all if bodies in braces for consistency with Solidity style guides and to prevent future bugs: // Current
if (batchNumber != lastCommittedBatch + 1)
revert BatchNumberNotSuccessor();
// Suggested
if (batchNumber != lastCommittedBatch + 1) {
revert BatchNumberNotSuccessor();
}Positive Observations
Testing Recommendations
Rust Code QualityThe Rust changes are well-structured:
Recommendation: Approve with comments - Address items 1, 2, and 4 before merging. Automated review by Claude (Anthropic) · custom prompt |
Lines of code reportTotal lines added: Detailed view |
Motivation
The two OnChainProposer contracts use inconsistent error formats — the non-based contract uses opaque hex codes (
"007","00c") while the based contract uses full strings ("OnChainProposer: Invalid RISC0 proof failed proof verification"). This causes a real bug: the Rustl1_proof_senderdetects invalid proofs by string-matching onmessage.contains("Invalid TDX proof"), which works for the based contract but fails silently for the non-based contract. Invalid proofs are never deleted.Custom errors are cheaper (no string storage in bytecode), self-documenting (the error name IS the documentation), and can be decoded programmatically from the ABI.
Closes #4196, closes #6098
Description
Replace all
require(condition, "string")/revert("string")in both OnChainProposer contracts with Solidity custom errors defined in the interfaces. Both contracts now use identical error names.On the Rust side, add a
data: Option<String>field toRpcRequestError::RPCErrorto capture the revert data from JSON-RPC error responses (custom errors encode their identity as a 4-byte selector in thedatafield, not inmessage). Update proof sender and verifier to match on these selectors instead of string-matching.Solidity changes:
IOnChainProposerinterfaces"012"code that was used for two different errorsRust changes:
data: Option<String>toRpcRequestError::RPCErrorerror_response.error.datain all RPCError construction sitesInvalidRisc0Proof,InvalidSp1Proof,InvalidTdxProof, andAlignedProofVerificationFailedl1_proof_senderto match on selectors indatainstead of strings inmessagel1_proof_verifierto match onAlignedProofVerificationFailedselector instead of the stale"00m"codeChecklist
STORE_SCHEMA_VERSION(crates/storage/lib.rs) if the PR includes breaking changes to theStorerequiring a re-sync.