// 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);
}

