// Certora Specification for InboxETH // Verifies rate limiting, fee calculation, and access control using InboxETH as IE; // Import required contracts import "../contracts/bridge/trustless/InboxETH.sol"; // ============================================================================ // RULES FOR Rate Limiting // ============================================================================ // Rule: Minimum deposit enforced rule minimumDepositEnforced(uint256 depositId, address asset, uint256 amount, address recipient, bytes proof) { env e; if (amount < IE.MIN_DEPOSIT()) { IE.submitClaim@withrevert(e, depositId, asset, amount, recipient, proof); assert lastReverted; } } // Rule: Cooldown period enforced rule cooldownEnforced(uint256 depositId1, uint256 depositId2, address asset, uint256 amount, address recipient, bytes proof) { env e1, e2; address relayer = address(0x1234); // First claim succeeds IE.submitClaim(e1, depositId1, asset, amount, recipient, proof); assume !lastReverted; // Second claim within cooldown must fail e2.block.timestamp = e1.block.timestamp + IE.COOLDOWN_PERIOD() - 1; IE.submitClaim@withrevert(e2, depositId2, asset, amount, recipient, proof); assert lastReverted; } // Rule: Cooldown allows claim after period rule cooldownAllowsAfterPeriod(uint256 depositId1, uint256 depositId2, address asset, uint256 amount, address recipient, bytes proof) { env e1, e2; // First claim succeeds IE.submitClaim(e1, depositId1, asset, amount, recipient, proof); assume !lastReverted; // Second claim after cooldown succeeds e2.block.timestamp = e1.block.timestamp + IE.COOLDOWN_PERIOD() + 1; IE.submitClaim@withrevert(e2, depositId2, asset, amount, recipient, proof); // Should not revert due to cooldown } // Rule: Hourly rate limit enforced rule hourlyRateLimitEnforced(uint256[] depositIds, address asset, uint256 amount, address recipient, bytes[] proofs) { env e; // Submit MAX_CLAIMS_PER_HOUR claims for (uint i = 0; i < IE.MAX_CLAIMS_PER_HOUR(); i++) { e.block.timestamp = (e.block.timestamp / 3600) * 3600 + i * 61; // Within same hour IE.submitClaim(e, depositIds[i], asset, amount, recipient, proofs[i]); assume !lastReverted; } // Next claim in same hour must fail e.block.timestamp = (e.block.timestamp / 3600) * 3600 + 100; IE.submitClaim@withrevert(e, depositIds[IE.MAX_CLAIMS_PER_HOUR()], asset, amount, recipient, proofs[0]); assert lastReverted; } // Rule: Rate limit resets in new hour rule rateLimitResets(uint256 depositId1, uint256 depositId2, address asset, uint256 amount, address recipient, bytes proof) { env e1, e2; // Submit claim in hour 1 e1.block.timestamp = 1000; IE.submitClaim(e1, depositId1, asset, amount, recipient, proof); assume !lastReverted; // Submit claim in hour 2 should succeed e2.block.timestamp = 4600; // Next hour IE.submitClaim@withrevert(e2, depositId2, asset, amount, recipient, proof); // Should not revert due to rate limit } // ============================================================================ // RULES FOR Relayer Fees // ============================================================================ // Rule: Fee calculation is correct when enabled rule feeCalculationCorrect(uint256 depositId, address asset, uint256 amount, address recipient, bytes proof) { env e; uint256 feeBps = IE.relayerFeeBps(); if (feeBps > 0) { IE.submitClaim(e, depositId, asset, amount, recipient, proof); if (!lastReverted) { IE.RelayerFee memory fee = IE.getRelayerFee(depositId); uint256 expectedFee = (amount * feeBps) / 10000; assert fee.amount == expectedFee; } } } // Rule: Fee cannot be claimed before finalization rule feeClaimBeforeFinalization(uint256 depositId) { env e; // Try to claim fee before finalization IE.claimRelayerFee@withrevert(e, depositId); // Should fail if claim not finalized } // Rule: Fee can only be claimed by relayer rule feeClaimOnlyByRelayer(uint256 depositId, address nonRelayer) { env e; // Non-relayer cannot claim fee // This is enforced by contract logic } // Rule: Fee cannot be claimed twice rule feeClaimOnce(uint256 depositId) { env e1, e2; // First claim succeeds IE.claimRelayerFee(e1, depositId); assume !lastReverted; // Second claim must fail IE.claimRelayerFee@withrevert(e2, depositId); assert lastReverted; } // ============================================================================ // RULES FOR Claim Submission // ============================================================================ // Rule: No duplicate claims for same depositId rule noDuplicateClaims(uint256 depositId, address asset, uint256 amount, address recipient, bytes proof) { env e1, e2; // First claim succeeds IE.submitClaim(e1, depositId, asset, amount, recipient, proof); assume !lastReverted; // Second claim must fail IE.submitClaim@withrevert(e2, depositId, asset, amount, recipient, proof); assert lastReverted; } // Rule: Sufficient bond required rule sufficientBondRequired(uint256 depositId, address asset, uint256 amount, address recipient, bytes proof) { env e; // If insufficient bond sent, claim must fail // This is enforced by BondManager } // ============================================================================ // RULES FOR Batch Operations // ============================================================================ // Rule: Batch submission respects rate limits rule batchRateLimit(uint256[] depositIds, address[] assets, uint256[] amounts, address[] recipients, bytes[] proofs) { env e; // Batch submission should respect rate limits IE.submitClaimsBatch(e, depositIds, assets, amounts, recipients, proofs); // Rate limiting should be applied } // Rule: Batch size limit enforced rule batchSizeLimit() { env e; uint256[] memory depositIds = new uint256[](51); // Exceeds limit // Batch should fail if too large } // ============================================================================ // REENTRANCY PROTECTION // ============================================================================ // Rule: No reentrancy in submitClaim rule noReentrancySubmitClaim(uint256 depositId, address asset, uint256 amount, address recipient, bytes proof) { env e; IE.submitClaim(e, depositId, asset, amount, recipient, proof); } // Rule: No reentrancy in submitClaimsBatch rule noReentrancyBatch(uint256[] depositIds, address[] assets, uint256[] amounts, address[] recipients, bytes[] proofs) { env e; IE.submitClaimsBatch(e, depositIds, assets, amounts, recipients, proofs); } // Rule: No reentrancy in claimRelayerFee rule noReentrancyClaimFee(uint256 depositId) { env e; IE.claimRelayerFee(e, depositId); }