// SPDX-License-Identifier: MIT pragma solidity 0.8.24; import { V3Fixtures } from "../helpers/V3Fixtures.sol"; import { AccountabilityBondV3Safe } from "../../contracts/bonds/AccountabilityBondV3Safe.sol"; import { BondStateV3 } from "../../contracts/bonds/BondStateV3.sol"; /// @notice Repository regression for the independent exact-active-end harness. contract IndependentBondBoundaryRegressionV3Test is V3Fixtures { AccountabilityBondV3Safe internal bond; function setUp() public { _setUpDependencies(); bond = new AccountabilityBondV3Safe( owner, address(0), address(consensus), address(0), 10 ether, 100 ether, address(0) ); } function test_endMinusOneCannotExpireAndLateEvidenceCannotRestartWindows() external { ( uint256 id, AccountabilityBondV3Safe.Terms memory terms, AccountabilityBondV3Safe.ObjectiveEvidence memory evidence ) = _activeEvidence(1, 1 ether); vm.warp(terms.activePeriodEnd - 1); evidence.observedAt = uint64(block.timestamp); evidence.signatureDeadline = uint64(block.timestamp); vm.expectRevert(AccountabilityBondV3Safe.DeadlinePassed.selector); bond.submitObjectiveEvidence(evidence, new address[](0), new bytes[](0)); vm.expectRevert(AccountabilityBondV3Safe.DeadlineNotReached.selector); bond.markExpired(id); } function test_exactEndEvidenceFirstStillGivesExpiryExclusivePrecedence() external { ( uint256 id, AccountabilityBondV3Safe.Terms memory terms, AccountabilityBondV3Safe.ObjectiveEvidence memory evidence ) = _activeEvidence(2, 1 ether); vm.warp(terms.activePeriodEnd); evidence.observedAt = uint64(block.timestamp); evidence.signatureDeadline = uint64(block.timestamp); vm.expectRevert(AccountabilityBondV3Safe.DeadlinePassed.selector); bond.submitObjectiveEvidence(evidence, new address[](0), new bytes[](0)); bond.markExpired(id); bond.forceExit(id); assertEq(bond.creditOf(address(0), counterparty), 0); assertEq(bond.creditOf(address(0), company), 1.5 ether); } function test_exactEndExpiryFirstPreventsEvidenceAndGivesSameFullRefund() external { ( uint256 id, AccountabilityBondV3Safe.Terms memory terms, AccountabilityBondV3Safe.ObjectiveEvidence memory evidence ) = _activeEvidence(3, 1 ether); vm.warp(terms.activePeriodEnd); bond.markExpired(id); bond.forceExit(id); vm.expectRevert( abi.encodeWithSelector(AccountabilityBondV3Safe.BadState.selector, BondStateV3.State.ForceExited) ); bond.submitObjectiveEvidence(evidence, new address[](0), new bytes[](0)); assertEq(bond.creditOf(address(0), counterparty), 0); assertEq(bond.creditOf(address(0), company), 1.5 ether); } function test_endPlusOneAcceptedEvidenceFinalizesDeterministically() external { ( uint256 id, AccountabilityBondV3Safe.Terms memory terms, AccountabilityBondV3Safe.ObjectiveEvidence memory evidence ) = _activeEvidence(4, 1 ether); bond.submitObjectiveEvidence(evidence, new address[](0), new bytes[](0)); assertEq(uint256(bond.bondOf(id).state), uint256(BondStateV3.State.SettlementRequested)); vm.warp(terms.activePeriodEnd + 1); vm.expectRevert(AccountabilityBondV3Safe.DeadlinePassed.selector); bond.advanceObjectiveEvidence(id); bond.markExpired(id); assertEq(uint256(bond.bondOf(id).state), uint256(BondStateV3.State.Settled)); assertEq(bond.creditOf(address(0), counterparty), 0.5 ether); assertEq(bond.creditOf(address(0), company), 1 ether); } function test_requiredByMinusOneActionIsTimelyAndCannotBeSlashed() external { ( uint256 id, AccountabilityBondV3Safe.Terms memory terms, AccountabilityBondV3Safe.ObjectiveEvidence memory evidence ) = _activeEvidence(5, 1 ether); vm.warp(evidence.requiredBy - 1); vm.prank(company); canonicalSource.record(evidence.canonicalSourceId, evidence.obligationId); vm.warp(evidence.requiredBy); evidence.recordedAt = evidence.requiredBy - 1; evidence.observedAt = evidence.requiredBy; vm.expectRevert(AccountabilityBondV3Safe.InvalidEvidence.selector); bond.submitObjectiveEvidence(evidence, new address[](0), new bytes[](0)); vm.warp(terms.activePeriodEnd); bond.markExpired(id); bond.forceExit(id); assertEq(bond.creditOf(address(0), company), 1.5 ether); } function test_requiredBySameBlockEvidenceFirstThenTimelyActionDismissesSlash() external { (uint256 id,, AccountabilityBondV3Safe.ObjectiveEvidence memory evidence) = _activeEvidence(6, 1 ether); bond.submitObjectiveEvidence(evidence, new address[](0), new bytes[](0)); vm.prank(company); canonicalSource.record(evidence.canonicalSourceId, evidence.obligationId); AccountabilityBondV3Safe.EnforcementState memory state = bond.enforcementStateOf(id); vm.warp(state.responseDeadline); bond.advanceObjectiveEvidence(id); assertEq(uint256(bond.bondOf(id).state), uint256(BondStateV3.State.Cancelled)); assertEq(bond.creditOf(address(0), counterparty), 0); assertEq(bond.creditOf(address(0), company), 1.5 ether); } function test_requiredBySameBlockActionFirstRejectsAbsenceEvidence() external { ( uint256 id, AccountabilityBondV3Safe.Terms memory terms, AccountabilityBondV3Safe.ObjectiveEvidence memory evidence ) = _activeEvidence(7, 1 ether); vm.prank(company); canonicalSource.record(evidence.canonicalSourceId, evidence.obligationId); vm.expectRevert(AccountabilityBondV3Safe.CanonicalSourceMismatch.selector); bond.submitObjectiveEvidence(evidence, new address[](0), new bytes[](0)); vm.warp(terms.activePeriodEnd); bond.markExpired(id); bond.forceExit(id); assertEq(bond.creditOf(address(0), company), 1.5 ether); } function test_requiredByPlusOneLateActionRemainsSlashableInEitherOrdering() external { (uint256 id,, AccountabilityBondV3Safe.ObjectiveEvidence memory evidence) = _activeEvidence(8, 1 ether); vm.warp(evidence.requiredBy + 1); vm.prank(company); canonicalSource.record(evidence.canonicalSourceId, evidence.obligationId); evidence.recordedAt = uint64(block.timestamp); evidence.observedAt = uint64(block.timestamp); bond.submitObjectiveEvidence(evidence, new address[](0), new bytes[](0)); _completeEnforcement(id); assertEq(bond.creditOf(address(0), counterparty), 0.5 ether); assertEq(bond.creditOf(address(0), company), 1 ether); } function test_pauseAtRequestedStateCannotEraseAcceptedSlashLiability() external { (uint256 id,, AccountabilityBondV3Safe.ObjectiveEvidence memory evidence) = _activeEvidence(9, 1 ether); bond.submitObjectiveEvidence(evidence, new address[](0), new bytes[](0)); _pauseAndFinalizeAtEnd(id); } function test_pauseCannotBlockExistingPositionEvidenceBeforeResponseDeadline() external { (uint256 id,, AccountabilityBondV3Safe.ObjectiveEvidence memory evidence) = _activeEvidence(12, 1 ether); AccountabilityBondV3Safe.EnforcementConfig memory enforcement = bond.enforcementOf(id); vm.prank(owner); bond.pause(); vm.warp(enforcement.requiredBy + enforcement.responseWindow - 1); evidence.observedAt = uint64(block.timestamp); evidence.signatureDeadline = uint64(block.timestamp); bond.submitObjectiveEvidence(evidence, new address[](0), new bytes[](0)); assertEq(uint256(bond.bondOf(id).state), uint256(BondStateV3.State.SettlementRequested)); vm.expectRevert( abi.encodeWithSelector(AccountabilityBondV3Safe.BadState.selector, BondStateV3.State.SettlementRequested) ); bond.submitObjectiveEvidence(evidence, new address[](0), new bytes[](0)); AccountabilityBondV3Safe.Terms memory terms = bond.bondOf(id).terms; vm.warp(terms.activePeriodEnd); bond.markExpired(id); assertEq(uint256(bond.bondOf(id).state), uint256(BondStateV3.State.Settled)); assertEq(bond.creditOf(address(0), counterparty), 0.5 ether); assertEq(bond.creditOf(address(0), company), 1 ether); } function test_pauseDoesNotExtendImmutableResponseDeadline() external { ( uint256 id, AccountabilityBondV3Safe.Terms memory terms, AccountabilityBondV3Safe.ObjectiveEvidence memory evidence ) = _activeEvidence(13, 1 ether); AccountabilityBondV3Safe.EnforcementConfig memory enforcement = bond.enforcementOf(id); vm.prank(owner); bond.pause(); vm.warp(enforcement.requiredBy + enforcement.responseWindow); evidence.observedAt = uint64(block.timestamp); evidence.signatureDeadline = uint64(block.timestamp); vm.expectRevert(AccountabilityBondV3Safe.DeadlinePassed.selector); bond.submitObjectiveEvidence(evidence, new address[](0), new bytes[](0)); vm.warp(terms.activePeriodEnd); bond.markExpired(id); bond.forceExit(id); assertEq(bond.creditOf(address(0), counterparty), 0); assertEq(bond.creditOf(address(0), company), 1.5 ether); } function test_pauseAtFinalizableStateCannotEraseAcceptedSlashLiability() external { (uint256 id,, AccountabilityBondV3Safe.ObjectiveEvidence memory evidence) = _activeEvidence(10, 1 ether); bond.submitObjectiveEvidence(evidence, new address[](0), new bytes[](0)); AccountabilityBondV3Safe.EnforcementState memory state = bond.enforcementStateOf(id); vm.warp(state.responseDeadline); bond.advanceObjectiveEvidence(id); _pauseAndFinalizeAtEnd(id); } function test_pauseAtFrozenStateCannotEraseAcceptedSlashLiability() external { (uint256 id,, AccountabilityBondV3Safe.ObjectiveEvidence memory evidence) = _activeEvidence(11, 1 ether); bond.submitObjectiveEvidence(evidence, new address[](0), new bytes[](0)); AccountabilityBondV3Safe.EnforcementState memory state = bond.enforcementStateOf(id); vm.warp(state.responseDeadline); bond.advanceObjectiveEvidence(id); vm.warp(state.challengeDeadline); bond.advanceObjectiveEvidence(id); _pauseAndFinalizeAtEnd(id); } function _activeEvidence(uint96 nonce, uint256 stake) private returns ( uint256 id, AccountabilityBondV3Safe.Terms memory terms, AccountabilityBondV3Safe.ObjectiveEvidence memory evidence ) { terms = _accountabilityTerms(nonce, stake); id = _createAccountability(bond, terms); _fundActiveAccountability(bond, id, stake); AccountabilityBondV3Safe.BondData memory data = bond.bondOf(id); AccountabilityBondV3Safe.EnforcementConfig memory enforcement = bond.enforcementOf(id); vm.warp(enforcement.requiredBy); evidence = AccountabilityBondV3Safe.ObjectiveEvidence({ bond: address(bond), chainId: block.chainid, bondId: id, termsHash: data.termsHash, ruleHash: data.terms.objectiveBreachRulesHash, canonicalSource: enforcement.canonicalSource, canonicalSourceId: enforcement.canonicalSourceId, obligationId: enforcement.obligationId, requiredBy: enforcement.requiredBy, recordedAt: 0, observedAt: uint64(block.timestamp), nonce: nonce, signatureDeadline: enforcement.requiredBy + enforcement.responseWindow }); } function _completeEnforcement(uint256 id) private { AccountabilityBondV3Safe.EnforcementState memory state = bond.enforcementStateOf(id); vm.warp(state.responseDeadline); bond.advanceObjectiveEvidence(id); vm.warp(state.challengeDeadline); bond.advanceObjectiveEvidence(id); vm.warp(state.decisionDeadline); bond.advanceObjectiveEvidence(id); } function _pauseAndFinalizeAtEnd(uint256 id) private { AccountabilityBondV3Safe.BondData memory data = bond.bondOf(id); AccountabilityBondV3Safe.EnforcementState memory enforcementState = bond.enforcementStateOf(id); vm.prank(owner); bond.pause(); if (data.state == BondStateV3.State.SettlementRequested) { vm.warp(enforcementState.responseDeadline); vm.expectRevert(AccountabilityBondV3Safe.Paused.selector); bond.advanceObjectiveEvidence(id); } else if (data.state == BondStateV3.State.Finalizable) { vm.warp(enforcementState.challengeDeadline); vm.expectRevert(AccountabilityBondV3Safe.Paused.selector); bond.advanceObjectiveEvidence(id); } else { vm.warp(enforcementState.decisionDeadline); vm.expectRevert(AccountabilityBondV3Safe.Paused.selector); bond.advanceObjectiveEvidence(id); } vm.warp(data.terms.activePeriodEnd); bond.markExpired(id); assertEq(uint256(bond.bondOf(id).state), uint256(BondStateV3.State.Settled)); assertEq(bond.creditOf(address(0), counterparty), 0.5 ether); assertEq(bond.creditOf(address(0), company), 1 ether); } }