// Certora Specification for BondManager
// Verifies economic security properties and state invariants

using BondManager as BM;

// Import required contracts
import "../contracts/bridge/trustless/BondManager.sol";

// ============================================================================
// INVARIANTS
// ============================================================================

// Invariant: Bond cannot be both slashed and released
invariant bondStateExclusive(uint256 depositId)
    BM.bonds(depositId).slashed == false || BM.bonds(depositId).released == false;

// Invariant: Total bonds tracking is consistent
invariant totalBondsConsistency(address relayer)
    BM.totalBonds(relayer) == sum(depositId => 
        BM.bonds(depositId).relayer == relayer && 
        !BM.bonds(depositId).slashed && 
        !BM.bonds(depositId).released ? 
        BM.bonds(depositId).amount : 0
    );

// ============================================================================
// RULES FOR postBond
// ============================================================================

// Rule: Bond calculation is correct
rule bondCalculationCorrect(uint256 depositId, uint256 depositAmount, address relayer) {
    env e;
    uint256 requiredBond = BM.getRequiredBond(depositAmount);
    uint256 calculatedBond = (depositAmount * BM.bondMultiplier()) / 10000;
    uint256 minBond = BM.minBond();
    
    // Bond must be at least the calculated amount or minimum
    assert requiredBond >= calculatedBond || requiredBond >= minBond;
    assert requiredBond == (calculatedBond > minBond ? calculatedBond : minBond);
    
    // If posting bond, amount must be >= required
    uint256 bondAmount = BM.postBond@withrevert(e, depositId, depositAmount, relayer);
    if (!lastReverted) {
        assert bondAmount >= requiredBond;
    }
}

// Rule: Bond cannot be posted twice for same depositId
rule noDuplicateBonds(uint256 depositId, uint256 depositAmount1, uint256 depositAmount2, address relayer) {
    env e1, e2;
    
    // First bond post succeeds
    BM.postBond(e1, depositId, depositAmount1, relayer);
    assume !lastReverted;
    
    // Second bond post must fail
    BM.postBond@withrevert(e2, depositId, depositAmount2, relayer);
    assert lastReverted;
}

// Rule: Total bonds updated correctly on post
rule totalBondsUpdatedOnPost(uint256 depositId, uint256 depositAmount, address relayer) {
    env e;
    uint256 totalBefore = BM.totalBonds(relayer);
    uint256 bondAmount = BM.postBond@withrevert(e, depositId, depositAmount, relayer);
    
    if (!lastReverted) {
        uint256 totalAfter = BM.totalBonds(relayer);
        assert totalAfter == totalBefore + bondAmount;
    }
}

// ============================================================================
// RULES FOR slashBond
// ============================================================================

// Rule: Bond cannot be slashed if already released
rule cannotSlashReleasedBond(uint256 depositId, address challenger) {
    env e1, e2;
    
    // Release bond first
    BM.releaseBond(e1, depositId);
    assume !lastReverted;
    
    // Slashing must fail
    BM.slashBond@withrevert(e2, depositId, challenger);
    assert lastReverted;
}

// Rule: Bond cannot be slashed if already slashed
rule cannotSlashTwice(uint256 depositId, address challenger1, address challenger2) {
    env e1, e2;
    
    // First slash succeeds
    BM.slashBond(e1, depositId, challenger1);
    assume !lastReverted;
    
    // Second slash must fail
    BM.slashBond@withrevert(e2, depositId, challenger2);
    assert lastReverted;
}

// Rule: Slashing splits 50/50 correctly
rule slashingSplitCorrect(uint256 depositId, address challenger) {
    env e;
    uint256 bondAmountBefore = BM.bonds(depositId).amount;
    
    (uint256 challengerReward, uint256 burnedAmount) = BM.slashBond@withrevert(e, depositId, challenger);
    
    if (!lastReverted) {
        // Challenger gets 50% (or slightly more for odd amounts)
        assert challengerReward == bondAmountBefore / 2 || challengerReward == bondAmountBefore / 2 + 1;
        // Burned amount is remainder
        assert challengerReward + burnedAmount == bondAmountBefore;
        // Bond marked as slashed
        assert BM.bonds(depositId).slashed == true;
    }
}

// Rule: Total bonds decreased on slash
rule totalBondsDecreasedOnSlash(uint256 depositId, address challenger) {
    env e;
    address relayer = BM.bonds(depositId).relayer;
    uint256 totalBefore = BM.totalBonds(relayer);
    uint256 bondAmount = BM.bonds(depositId).amount;
    
    BM.slashBond@withrevert(e, depositId, challenger);
    
    if (!lastReverted) {
        uint256 totalAfter = BM.totalBonds(relayer);
        assert totalAfter == totalBefore - bondAmount;
    }
}

// ============================================================================
// RULES FOR releaseBond
// ============================================================================

// Rule: Bond cannot be released if already slashed
rule cannotReleaseSlashedBond(uint256 depositId) {
    env e1, e2;
    
    // Slash bond first
    address challenger = address(0x1234);
    BM.slashBond(e1, depositId, challenger);
    assume !lastReverted;
    
    // Release must fail
    BM.releaseBond@withrevert(e2, depositId);
    assert lastReverted;
}

// Rule: Bond cannot be released twice
rule cannotReleaseTwice(uint256 depositId) {
    env e1, e2;
    
    // First release succeeds
    BM.releaseBond(e1, depositId);
    assume !lastReverted;
    
    // Second release must fail
    BM.releaseBond@withrevert(e2, depositId);
    assert lastReverted;
}

// Rule: Total bonds decreased on release
rule totalBondsDecreasedOnRelease(uint256 depositId) {
    env e;
    address relayer = BM.bonds(depositId).relayer;
    uint256 totalBefore = BM.totalBonds(relayer);
    uint256 bondAmount = BM.bonds(depositId).amount;
    
    BM.releaseBond@withrevert(e, depositId);
    
    if (!lastReverted) {
        uint256 totalAfter = BM.totalBonds(relayer);
        assert totalAfter == totalBefore - bondAmount;
        // Bond marked as released
        assert BM.bonds(depositId).released == true;
    }
}

// ============================================================================
// RULES FOR getRequiredBond
// ============================================================================

// Rule: Bond calculation formula is correct
rule requiredBondFormula(uint256 depositAmount) {
    uint256 requiredBond = BM.getRequiredBond(depositAmount);
    uint256 calculatedBond = (depositAmount * BM.bondMultiplier()) / 10000;
    uint256 minBond = BM.minBond();
    
    // Required bond is max of calculated and minimum
    assert requiredBond == (calculatedBond > minBond ? calculatedBond : minBond);
    assert requiredBond >= minBond;
}

// Rule: Bond multiplier constraint (>= 100%)
rule bondMultiplierConstraint() {
    assert BM.bondMultiplier() >= 10000;
}

// Rule: Minimum bond is positive
rule minBondPositive() {
    assert BM.minBond() > 0;
}

// ============================================================================
// REENTRANCY PROTECTION
// ============================================================================

// Rule: No reentrancy in postBond
rule noReentrancyPostBond(uint256 depositId, uint256 depositAmount, address relayer) {
    env e;
    // Certora will check that nonReentrant modifier prevents reentrancy
    BM.postBond(e, depositId, depositAmount, relayer);
}

// Rule: No reentrancy in slashBond
rule noReentrancySlashBond(uint256 depositId, address challenger) {
    env e;
    BM.slashBond(e, depositId, challenger);
}

// Rule: No reentrancy in releaseBond
rule noReentrancyReleaseBond(uint256 depositId) {
    env e;
    BM.releaseBond(e, depositId);
}

