diff --git a/.gas-snapshot b/.gas-snapshot new file mode 100644 index 0000000..2324b64 --- /dev/null +++ b/.gas-snapshot @@ -0,0 +1,539 @@ +ActorAttributionTest:test_actorContext_isClearedAfterARoutedAction() (gas: 209522) +ActorAttributionTest:test_forgedHookData_cannotDisplaceTheAuthenticatedActor() (gas: 63768) +ActorAttributionTest:test_liquidityAddition_queriesLiquidityEligibilityForTheOriginatingUser() (gas: 173991) +ActorAttributionTest:test_liquidityPerimeter_cannotAuthorizeAnOrdinarySwap() (gas: 52016) +ActorAttributionTest:test_nestedRoutedAction_isRejected() (gas: 10067109) +ActorAttributionTest:test_ordinarySwap_attributesTheOriginatingUserAndNotThePerimeter() (gas: 236169) +ActorAttributionTest:test_ordinarySwap_isNotAuthorizedByMakingThePerimeterEligible() (gas: 87889) +ActorAttributionTest:test_ordinarySwap_queriesTraderEligibilityForTheOriginatingUser() (gas: 211214) +ActorAttributionTest:test_swapPerimeter_cannotAuthorizeALiquidityAction() (gas: 51036) +ActorAttributionTest:test_trustedPerimeter_exposesNoActorWithoutARoutedAction() (gas: 17621) +ActorAttributionTest:test_untrustedAttestor_cannotEstablishAnEconomicActor() (gas: 481244) +AggregateObligationFuzzTest:testFuzz_aggregateObligation_equalsIndependentReference(uint128[8],uint64[8],uint8[8],uint256,uint64) (runs: 1008, μ: 534461, ~: 535168) +AggregateObligationFuzzTest:testFuzz_aggregateObligation_ignoresUnreferencedCommitments(uint128,uint128,uint64,uint64) (runs: 1008, μ: 266454, ~: 266995) +AggregateObligationFuzzTest:testFuzz_aggregateObligation_neverExceedsTheReferencedRemainders(uint128[8],uint64[8],uint64) (runs: 1008, μ: 1002858, ~: 1005496) +AggregateObligationFuzzTest:testFuzz_aggregateObligation_releasesEverythingAfterTheLastExpiry(uint128[8],uint64[8]) (runs: 1008, μ: 1002360, ~: 1004979) +AggregateObligationTest:test_aggregateObligation_countsAnOccupiedHighSlotWithEmptyLowSlots() (gas: 167496) +AggregateObligationTest:test_aggregateObligation_countsCommitmentsBeforeTheirExerciseWindow() (gas: 167594) +AggregateObligationTest:test_aggregateObligation_countsOneBindingCommitment() (gas: 167451) +AggregateObligationTest:test_aggregateObligation_countsOnlyTheBindingCommitmentsInAMixedIndex() (gas: 526144) +AggregateObligationTest:test_aggregateObligation_derivesAcrossAFullyOccupiedIndex() (gas: 1957147) +AggregateObligationTest:test_aggregateObligation_dropsExhaustedReferences() (gas: 175220) +AggregateObligationTest:test_aggregateObligation_dropsExpiredReferencesWithoutAnExpiryTransaction() (gas: 179094) +AggregateObligationTest:test_aggregateObligation_isDerivedRatherThanCached() (gas: 184666) +AggregateObligationTest:test_aggregateObligation_isIndependentOfSlotOrdering() (gas: 11246375) +AggregateObligationTest:test_aggregateObligation_isUnaffectedByBeneficiaryEligibility() (gas: 188303) +AggregateObligationTest:test_aggregateObligation_isZeroWhenEverySlotIsEmpty() (gas: 50506) +AggregateObligationTest:test_aggregateObligation_sumsSeveralBindingCommitments() (gas: 413700) +CommitmentAdmissionFuzzTest:testFuzz_admission_acceptsExactlyTheAdmissibleWindows(uint64,uint64) (runs: 1008, μ: 156934, ~: 142761) +CommitmentAdmissionFuzzTest:testFuzz_admission_acceptsExactlyTheBackedEntitlements(uint128) (runs: 1008, μ: 240575, ~: 257499) +CommitmentAdmissionFuzzTest:testFuzz_admission_boundaryFollowsTheExistingObligation(uint128,uint128) (runs: 1008, μ: 370689, ~: 380917) +CommitmentAdmissionFuzzTest:testFuzz_admissions_produceUniqueMonotonicSinglyReferencedIdentities(uint8) (runs: 1008, μ: 1093648, ~: 848889) +CommitmentAdmissionFuzzTest:testFuzz_fullIndex_reusesAnyReclaimableSlot(uint8) (runs: 1008, μ: 2442678, ~: 2441615) +CommitmentAdmissionIntegrationTest:test_admissionDecision_followsAuthoritativePoolManagerState() (gas: 474511) +CommitmentAdmissionIntegrationTest:test_admissionMeasuresAgainstTheDerivedCurrentObligation() (gas: 444828) +CommitmentAdmissionIntegrationTest:test_admission_belowCapacity_isPermitted() (gas: 215348) +CommitmentAdmissionIntegrationTest:test_admission_boundaryAppliesToTheAggregate() (gas: 439429) +CommitmentAdmissionIntegrationTest:test_admission_exactlyAtCapacity_isPermitted() (gas: 222720) +CommitmentAdmissionIntegrationTest:test_admission_insertsIntoAnEmptyReferenceSlotAndBecomesVisibleToTheAggregate() (gas: 350526) +CommitmentAdmissionIntegrationTest:test_admission_oneUnitAboveCapacity_isRejected() (gas: 164566) +CommitmentAdmissionIntegrationTest:test_admittedCommitment_persistsTheCompleteAuthoritativeBasis() (gas: 209032) +CommitmentAdmissionIntegrationTest:test_boundedExhaustion_isDistinctFromInsufficientBacking() (gas: 2327430) +CommitmentAdmissionIntegrationTest:test_canonicalA1_firstAuthenticPositiveObligation() (gas: 300114) +CommitmentAdmissionIntegrationTest:test_fullIndexOfBindingCommitments_isRejectedOnBoundedCapacity() (gas: 2435190) +CommitmentAdmissionIntegrationTest:test_fullIndex_reusesAReclaimableReferenceWhilePreservingHistory() (gas: 2482783) +CommitmentAdmissionIntegrationTest:test_futureWindowCommitment_isValidAndBindingButNotExerciseQualified() (gas: 212936) +CommitmentAdmissionIntegrationTest:test_proposedCommitmentObligation_matchesAnEquivalentAdmittedCommitment() (gas: 442987) +CommitmentAdmissionIntegrationTest:test_successfulAdmissions_takeNoCustodyAndMoveNoPoolState() (gas: 527944) +CommitmentAdmissionTest:test_admittedCommitment_hasFullRemainingEntitlement() (gas: 206385) +CommitmentAdmissionTest:test_establishCommitment_byEstablishmentAuthority_isAdmitted() (gas: 207914) +CommitmentAdmissionTest:test_establishCommitment_byEveryOtherRole_isRejected() (gas: 604188) +CommitmentAdmissionTest:test_establishCommitment_byUnauthorizedCaller_isRejected() (gas: 139720) +CommitmentAdmissionTest:test_establishCommitment_doesNotAcceptTraderOrLiquidityEligibility() (gas: 202810) +CommitmentAdmissionTest:test_establishCommitment_forAnIneligibleBeneficiary_isRejected() (gas: 148368) +CommitmentAdmissionTest:test_establishCommitment_onAnUnconfiguredHook_isRejected() (gas: 50182662) +CommitmentAdmissionTest:test_establishCommitment_succeedsForTheSameBeneficiaryOnceEligible() (gas: 233148) +CommitmentAdmissionTest:test_establishCommitment_withAFutureWindow_isAdmitted() (gas: 204464) +CommitmentAdmissionTest:test_establishCommitment_withAWindowOpeningNow_isAdmitted() (gas: 204310) +CommitmentAdmissionTest:test_establishCommitment_withAnAlreadyOpenWindow_isAdmitted() (gas: 204425) +CommitmentAdmissionTest:test_establishCommitment_withValidUntilBeforeExercisableFrom_isRejected() (gas: 140677) +CommitmentAdmissionTest:test_establishCommitment_withValidUntilEqualToExercisableFrom_isRejected() (gas: 140523) +CommitmentAdmissionTest:test_establishCommitment_withValidUntilEqualToNow_isRejected() (gas: 140614) +CommitmentAdmissionTest:test_establishCommitment_withValidUntilInThePast_isRejected() (gas: 140692) +CommitmentAdmissionTest:test_establishCommitment_withZeroBeneficiary_isRejected() (gas: 140289) +CommitmentAdmissionTest:test_establishCommitment_withZeroEntitlement_isRejected() (gas: 140483) +CommitmentAdmissionTest:test_establishCommitment_withZeroExerciseAuthority_isRejected() (gas: 138353) +CommitmentAdmissionTest:test_failedAdmission_consumesNoIdentity() (gas: 413547) +CommitmentAdmissionTest:test_fixtureCapacityFarExceedsTheEntitlementUsedHere() (gas: 34395) +CommitmentAdmissionTest:test_rejectedAdmission_alongsideExistingCommitments_leavesNoResidue() (gas: 425681) +CommitmentAdmissionTest:test_revokingBeneficiaryEligibility_doesNotReleaseAnAdmittedObligation() (gas: 228935) +CommitmentAdmissionTest:test_successfulAdmission_emitsAdmissionEvidence() (gas: 208903) +CommitmentAdmissionTest:test_successiveAdmissions_receiveUniqueMonotonicIdentities() (gas: 470316) +CommitmentAdmissionTest:test_unadmittedIdentity_doesNotExist() (gas: 209797) +CommitmentDerivationFuzzTest:testFuzz_bindingAndTemporalQualification_areDistinct(uint128,uint64,uint64,uint256) (runs: 1002, μ: 5053, ~: 5053) +CommitmentDerivationFuzzTest:testFuzz_commitmentObligation_equalsIndependentReference(uint128,uint64,uint256) (runs: 1008, μ: 922, ~: 913) +CommitmentDerivationFuzzTest:testFuzz_commitmentObligation_isEitherTheWholeRemainderOrZero(uint128,uint64,uint256) (runs: 1008, μ: 822, ~: 808) +CommitmentDerivationFuzzTest:testFuzz_permanentNonBinding_equalsIndependentReference(uint128,uint64,uint256) (runs: 1008, μ: 827, ~: 828) +CommitmentDerivationFuzzTest:testFuzz_permanentRelease_coincidesWithZeroObligation(uint128,uint64,uint256) (runs: 1008, μ: 896, ~: 890) +CommitmentDerivationFuzzTest:testFuzz_positiveObligation_impliesValidityAndPositiveRemainder(uint128,uint64,uint256) (runs: 1008, μ: 788, ~: 656) +CommitmentDerivationFuzzTest:testFuzz_release_isMonotoneInTime(uint128,uint64,uint256,uint256) (runs: 1008, μ: 1171, ~: 1206) +CommitmentDerivationFuzzTest:testFuzz_temporalQualification_equalsIndependentReference(uint64,uint64,uint256) (runs: 1008, μ: 841, ~: 852) +CommitmentDerivationFuzzTest:testFuzz_temporalQualification_impliesValidity(uint64,uint64,uint256) (runs: 1008, μ: 593, ~: 587) +CommitmentDerivationFuzzTest:testFuzz_validity_equalsIndependentReference(uint64,uint256) (runs: 1008, μ: 543, ~: 543) +CommitmentDerivationTest:test_commitmentObligation_bindsBeforeTheExerciseWindowOpens() (gas: 109801) +CommitmentDerivationTest:test_commitmentObligation_expiresWithoutRewritingRemainingEntitlement() (gas: 115899) +CommitmentDerivationTest:test_commitmentObligation_isUnaffectedByBeneficiaryEligibility() (gas: 152295) +CommitmentDerivationTest:test_commitmentObligation_isUnaffectedByCallerIdentity() (gas: 115887) +CommitmentDerivationTest:test_commitmentObligation_isZeroOnceEntitlementIsExhausted() (gas: 112765) +CommitmentDerivationTest:test_commitmentObligation_matchesTheFrozenTemporalMatrix() (gas: 20740) +CommitmentDerivationTest:test_commitmentObligation_revertsForAnIdentityThatWasNeverAllocated() (gas: 13517) +CommitmentDerivationTest:test_derivationReads_areExposedByTheProductionHook() (gas: 47307) +CommitmentDerivationTest:test_permanentNonBinding_holdsExactlyOnExhaustionOrExpiry() (gas: 1276) +CommitmentDerivationTest:test_permanentNonBinding_isIrreversibleOnceExpired() (gas: 667) +CommitmentDerivationTest:test_temporalQualification_isIndependentOfRemainingEntitlement() (gas: 523) +CommitmentDerivationTest:test_temporalQualification_opensAtExercisableFromAndClosesAtValidUntil() (gas: 1227) +CommitmentDerivationTest:test_validity_doesNotDependOnTheExerciseWindow() (gas: 477) +CommitmentDerivationTest:test_validity_endsExactlyAtValidUntil() (gas: 856) +CommitmentRefsFuzzTest:testFuzz_boundedIndex_partitionsIntoOccupiedAndEmptySlots(uint16) (runs: 1008, μ: 151640, ~: 150884) +CommitmentRefsFuzzTest:testFuzz_firstEmptySlot_reportsTheLowestEmptySlot(uint16) (runs: 1008, μ: 139170, ~: 138316) +CommitmentRefsFuzzTest:testFuzz_slotOf_locatesAnyIdentityAtItsOwnSlot(uint8,uint256,uint256) (runs: 1008, μ: 61048, ~: 61075) +CommitmentRefsFuzzTest:testFuzz_slotOf_neverFindsTheSentinel(uint16) (runs: 1008, μ: 138374, ~: 137958) +CommitmentRefsTest:test_boundedIndex_hasExactlySixteenSlots() (gas: 621) +CommitmentRefsTest:test_emptyIndex_reportsSlotZeroAsTheFirstEmptySlot() (gas: 36046) +CommitmentRefsTest:test_firstEmptySlot_reportsTheLowestEmptySlot() (gas: 52008) +CommitmentRefsTest:test_fullIndex_reportsNoEmptySlot() (gas: 363062) +CommitmentRefsTest:test_slotOf_locatesAnIdentityAtItsOwnSlot() (gas: 57509) +CommitmentRefsTest:test_slotOf_neverFindsTheSentinel() (gas: 356654) +CommitmentStorageFuzzTest:testFuzz_recordCommitment_allocatesAMonotonicNonRecycledHistory(uint8) (runs: 1008, μ: 1204524, ~: 917117) +CommitmentStorageFuzzTest:testFuzz_recordCommitment_preservesArbitraryFactsExactly(bytes32,address,address,uint128,uint128,uint64,uint64) (runs: 1008, μ: 105274, ~: 105274) +CommitmentStorageFuzzTest:testFuzz_repeatedSlotReuse_preservesIndexIntegrityAndHistory(uint8,uint256) (runs: 1008, μ: 1735083, ~: 1284516) +CommitmentStorageFuzzTest:testFuzz_timePassing_neverAltersStoredFacts(uint256,uint64,uint64) (runs: 1008, μ: 109740, ~: 109740) +CommitmentStorageFuzzTest:testFuzz_unallocatedIdentity_isUniformlyRejected(uint8,uint256) (runs: 1008, μ: 296299, ~: 207439) +CommitmentStorageFuzzTest:testFuzz_writeEnforcementReference_occupiesOnlyTheAddressedSlot(uint8,uint256) (runs: 1008, μ: 169651, ~: 169952) +CommitmentStorageFuzzTest:testFuzz_writeEnforcementReference_rejectsAnyDuplicate(uint8,uint8) (runs: 1007, μ: 173045, ~: 173036) +CommitmentStorageFuzzTest:testFuzz_writeEnforcementReference_rejectsAnySlotOutsideTheBound(uint256,uint256) (runs: 1006, μ: 148477, ~: 148477) +CommitmentStorageFuzzTest:testFuzz_writeRemainingEntitlement_neverDisturbsAdmissionFixedFacts(uint256,uint128) (runs: 1008, μ: 107907, ~: 107907) +CommitmentStorageTest:test_commitmentIdentity_startsAtOneAndReservesZero() (gas: 22174) +CommitmentStorageTest:test_commitment_rejectsAnIdentityThatWasNeverAllocated() (gas: 110467) +CommitmentStorageTest:test_eligibilityChange_doesNotAlterStoredCommitmentFacts() (gas: 128730) +CommitmentStorageTest:test_enabledAdmissionCallbacks_failClosedWithoutAConfiguredService() (gas: 27176) +CommitmentStorageTest:test_enforcementReferenceReuse_neitherRecyclesIdentityNorErasesHistory() (gas: 3626042) +CommitmentStorageTest:test_enforcementReferenceWrite_doesNotRewriteCommitmentFacts() (gas: 372552) +CommitmentStorageTest:test_enforcementReferences_areSixteenAndStartEmpty() (gas: 51607) +CommitmentStorageTest:test_enforcementReferences_carryNoEconomicClassification() (gas: 268189) +CommitmentStorageTest:test_expiry_doesNotAlterStoredRemainingEntitlement() (gas: 109365) +CommitmentStorageTest:test_f4Storage_preservesTheF3ConfigurationBoundary() (gas: 296042) +CommitmentStorageTest:test_noCommitmentLifecycleProjectionIsExposed() (gas: 25007) +CommitmentStorageTest:test_productionHook_exposesNoCommitmentCreationOrMutationSurface() (gas: 63671) +CommitmentStorageTest:test_recordCommitment_allocatesNonzeroUniqueIncreasingIdentities() (gas: 773097) +CommitmentStorageTest:test_recordCommitment_keepsHistoricalRecordsIndependent() (gas: 206672) +CommitmentStorageTest:test_recordCommitment_preservesEveryFactExactly() (gas: 106215) +CommitmentStorageTest:test_writeEnforcementReference_acceptsRewritingASlotWithItsOwnCommitment() (gas: 170763) +CommitmentStorageTest:test_writeEnforcementReference_fillsSixteenSlotsAndNoMore() (gas: 2023427) +CommitmentStorageTest:test_writeEnforcementReference_occupiesExactlyTheAddressedSlot() (gas: 168854) +CommitmentStorageTest:test_writeEnforcementReference_rejectsADuplicateReference() (gas: 170271) +CommitmentStorageTest:test_writeEnforcementReference_rejectsASlotOutsideTheBound() (gas: 106476) +CommitmentStorageTest:test_writeEnforcementReference_rejectsAnIdentityThatWasNeverAllocated() (gas: 50984) +CommitmentStorageTest:test_writeRemainingEntitlement_changesOnlyRemainingEntitlement() (gas: 107944) +CommitmentStorageTest:test_writeRemainingEntitlement_rejectsAnIdentityThatWasNeverAllocated() (gas: 14038) +DerivationDecimalIndependenceTest:test_capacity_equalsIndependentReferenceForBothPrecisions() (gas: 50596) +DerivationDecimalIndependenceTest:test_capacity_isIndependentOfCurrencyDecimalPrecision() (gas: 58248) +DerivationDecimalIndependenceTest:test_matchedServices_differOnlyInDecimalPrecision() (gas: 72662) +DerivationGeneralizationTest:test_canonicalFixture_derivesEightyThousandProtectedOutputUnits() (gas: 50750) +DerivationGeneralizationTest:test_canonicalFixture_isBuiltOnAnAuthoritativeActivatedService() (gas: 23635) +DerivationGeneralizationTest:test_capacityRead_resolvesThroughTheSameKernelAsProspectiveMeasurement() (gas: 51715) +DerivationGeneralizationTest:test_capacity_isDenominatedInTheProtectedOutputCurrency() (gas: 52286) +DerivationGeneralizationTest:test_capacity_isPositiveAtThePOBoundary() (gas: 167085) +DerivationGeneralizationTest:test_capacity_refusesAPresentStateOutsideTheServiceDomain() (gas: 201689) +DerivationGeneralizationTest:test_capacity_revertsBeforeAServiceExists() (gas: 24158582) +DerivationGeneralizationTest:test_capacity_usesTheExactSlot0PriceRatherThanTheTickPrice() (gas: 176198) +DerivationGeneralizationTest:test_configurations_differInDirectionDecimalsTicksAndSpacing() (gas: 44426) +DerivationGeneralizationTest:test_everyActivatedConfiguration_liesWithinTheSupportedTraversalBound() (gas: 77524) +DerivationGeneralizationTest:test_everyConfiguration_derivesCapacityEqualToTheIndependentReference() (gas: 142211) +DeterministicEconomicFixtureTest:test_canonicalActiveLiquidity_isExactlyTheCanonicalAmount() (gas: 10396) +DeterministicEconomicFixtureTest:test_canonicalHookBoundPool_holdsNoLiquidityAtF1() (gas: 10270) +DeterministicEconomicFixtureTest:test_canonicalHookBoundPool_rejectsLiquidityAdditionBeforeServiceActivation() (gas: 48534) +DeterministicEconomicFixtureTest:test_canonicalLiquidityPosition_isEstablishedOverTheCanonicalRange() (gas: 18010) +DeterministicEconomicFixtureTest:test_canonicalLiquidityRange_enclosesTheServiceDomain() (gas: 469) +DeterministicEconomicFixtureTest:test_canonicalPoolKey_bindsOrderedCurrenciesFeeSpacingAndStandbyHook() (gas: 13867) +DeterministicEconomicFixtureTest:test_canonicalPool_isInitializedThroughRealPoolManagerAtTickZero() (gas: 11235) +DeterministicEconomicFixtureTest:test_canonicalProtectedDirection_isZeroForOneFromMockUSTBToMockUSDC() (gas: 9135) +DeterministicEconomicFixtureTest:test_deterministicDeployment_guaranteesOrderingFromAnyDeployerAddress() (gas: 1574027) +DeterministicEconomicFixtureTest:test_deterministicDeployment_predictsTheOrderedAddressesBeforeDeploying() (gas: 18927) +DeterministicEconomicFixtureTest:test_fixtureCurrencies_satisfyRequiredAddressOrdering() (gas: 4559) +DeterministicEconomicFixtureTest:test_fixtureCurrencies_useExactlySixDecimals() (gas: 12739) +DeterministicEconomicFixtureTest:test_geometryPool_isInitializedAtTickZeroWithCanonicalFeeAndSpacing() (gas: 13486) +DeterministicEconomicFixtureTest:test_independentReference_dependsOnActualLiquidity() (gas: 14740) +DeterministicEconomicFixtureTest:test_independentReference_derivesEightyThousandMockUSDCFromActualPoolState() (gas: 15074) +EligibilityRegistryFuzzTest:testFuzz_administratorAuthority_isScopedToItsOwnRegistry(address,address) (runs: 1008, μ: 282393, ~: 282396) +EligibilityRegistryFuzzTest:testFuzz_anyAccount_defaultsToIneligibleUnderEveryPredicate(address) (runs: 1008, μ: 14713, ~: 14713) +EligibilityRegistryFuzzTest:testFuzz_anyEligibilityCombination_isReflectedExactly(address,bool,bool,bool) (runs: 1008, μ: 57819, ~: 48245) +EligibilityRegistryFuzzTest:testFuzz_latestAuthorizedWrite_isAuthoritative(address,bool,bool) (runs: 1008, μ: 77265, ~: 96930) +EligibilityRegistryFuzzTest:testFuzz_singleDomainUpdate_leavesTheOtherDomainsUnchanged(address,bool,bool,bool,uint8,bool) (runs: 1008, μ: 62719, ~: 71920) +EligibilityRegistryFuzzTest:testFuzz_unauthorizedCaller_cannotWriteAnyDomain(address,address,bool,bool,bool,bool) (runs: 1008, μ: 64527, ~: 74537) +EligibilityRegistryFuzzTest:testFuzz_writes_areScopedToTheNamedAccount(address,address,bool,bool,bool) (runs: 1008, μ: 67782, ~: 58543) +EligibilityRegistryTest:test_admin_updatesAllThreeEligibilityCategories() (gas: 89886) +EligibilityRegistryTest:test_administratorAuthority_isScopedToItsOwnRegistry() (gas: 252054) +EligibilityRegistryTest:test_allPredicates_defaultToIneligible() (gas: 72270) +EligibilityRegistryTest:test_beneficiaryEligibility_isAdmittedAndRevokedByTheAdmin() (gas: 30568) +EligibilityRegistryTest:test_beneficiaryUpdates_leaveTraderAndLiquidityEligibilityUnchanged() (gas: 119239) +EligibilityRegistryTest:test_construction_rejectsAZeroAdministrator() (gas: 35889) +EligibilityRegistryTest:test_eligibilityWrites_areScopedToTheNamedAccount() (gas: 101862) +EligibilityRegistryTest:test_eligibilityWrites_emitDomainSpecificEvents() (gas: 72641) +EligibilityRegistryTest:test_liquidityEligibility_isAdmittedAndRevokedByTheAdmin() (gas: 30463) +EligibilityRegistryTest:test_liquidityUpdates_leaveBeneficiaryAndTraderEligibilityUnchanged() (gas: 119226) +EligibilityRegistryTest:test_oneAccount_holdsBeneficiaryEligibilityAlone() (gas: 44221) +EligibilityRegistryTest:test_oneAccount_holdsEveryCombinationOfTheThreePredicates() (gas: 205600) +EligibilityRegistryTest:test_oneAccount_holdsLiquidityEligibilityAlone() (gas: 44257) +EligibilityRegistryTest:test_oneAccount_holdsTraderEligibilityAlone() (gas: 44313) +EligibilityRegistryTest:test_registryAdmin_isTheConstructorAdministrator() (gas: 7739) +EligibilityRegistryTest:test_registry_answersThroughTheConsumedReadInterface() (gas: 44000) +EligibilityRegistryTest:test_repeatedWrites_reflectTheLatestAuthorizedWrite() (gas: 52184) +EligibilityRegistryTest:test_traderEligibility_isAdmittedAndRevokedByTheAdmin() (gas: 30672) +EligibilityRegistryTest:test_traderUpdates_leaveBeneficiaryAndLiquidityEligibilityUnchanged() (gas: 119357) +EligibilityRegistryTest:test_unauthorizedGrant_revertsInEveryCategoryAndChangesNothing() (gas: 27643) +EligibilityRegistryTest:test_unauthorizedRevocation_revertsAndLeavesEstablishedEligibilityUnchanged() (gas: 98438) +ExerciseAuthorizationFuzzTest:testFuzz_exerciseExtent_isAuthorizedExactlyOnTheHalfOpenRemainder(uint128,uint256) (runs: 1008, μ: 312463, ~: 376164) +ExerciseAuthorizationFuzzTest:testFuzz_maxInput_changesNoAuthorizationOutcome(uint256) (runs: 1008, μ: 409503, ~: 409503) +ExerciseAuthorizationFuzzTest:testFuzz_onlyTheCommitmentExerciseAuthority_mayAuthorize(uint256,bool) (runs: 1008, μ: 295641, ~: 221336) +ExerciseAuthorizationPerimeterTest:test_authorization_recoversTheOriginatorFromTheConfiguredRouter() (gas: 373384) +ExerciseAuthorizationPerimeterTest:test_configuredRouter_cannotAuthorizeOutsideARequest() (gas: 214997) +ExerciseAuthorizationPerimeterTest:test_directCaller_cannotAuthorizeExercise() (gas: 212238) +ExerciseAuthorizationPerimeterTest:test_exerciseRouterIdentity_neverSatisfiesCommitmentExerciseAuthority() (gas: 230832) +ExerciseAuthorizationPerimeterTest:test_exerciseRouter_exposesNoExerciserWithoutARequest() (gas: 17761) +ExerciseAuthorizationPerimeterTest:test_exerciserContext_isClearedAfterARequest() (gas: 374803) +ExerciseAuthorizationPerimeterTest:test_forgedAttribution_isRefusedBeforeItIsConsulted() (gas: 360993) +ExerciseAuthorizationPerimeterTest:test_maxInputAtTheTopOfTheDomain_authorizesIdentically() (gas: 414451) +ExerciseAuthorizationPerimeterTest:test_maxInputBelowTheRequestedQuantity_authorizesIdentically() (gas: 414477) +ExerciseAuthorizationPerimeterTest:test_maxInputOfZero_authorizesIdentically() (gas: 414469) +ExerciseAuthorizationPerimeterTest:test_maxInput_neitherRescuesNorCausesARefusal() (gas: 444911) +ExerciseAuthorizationPerimeterTest:test_ordinaryTransitionPerimeters_cannotAuthorizeExercise() (gas: 220797) +ExerciseAuthorizationPerimeterTest:test_originatingExerciser_isTheDirectCallerAndNotTheTransactionOrigin() (gas: 382953) +ExerciseAuthorizationPerimeterTest:test_unconfiguredExerciseRouter_cannotAuthorizeExercise() (gas: 217686) +ExerciseAuthorizationPerimeterTest:test_wrongOriginatingExerciser_isRejected() (gas: 387980) +ExerciseAuthorizationTest:test_afterValidUntil_releasesTheObligationWithoutChangingRemaining() (gas: 233201) +ExerciseAuthorizationTest:test_atExercisableFrom_isPermitted() (gas: 376083) +ExerciseAuthorizationTest:test_atValidUntil_isRejected() (gas: 224359) +ExerciseAuthorizationTest:test_beforeExercisableFrom_isRejected() (gas: 229151) +ExerciseAuthorizationTest:test_commitmentSubstitution_isRejected() (gas: 532199) +ExerciseAuthorizationTest:test_completedRequest_fulfilsNothingAndDeliversNothing() (gas: 463130) +ExerciseAuthorizationTest:test_failedAuthorizations_leaveNoUsableContext() (gas: 456196) +ExerciseAuthorizationTest:test_fullRemainingQuantity_isPermitted() (gas: 372491) +ExerciseAuthorizationTest:test_immediatelyBeforeValidUntil_isPermitted() (gas: 376161) +ExerciseAuthorizationTest:test_ineligibleBeneficiary_isRejectedAndReleasesNothing() (gas: 312956) +ExerciseAuthorizationTest:test_nonexistentCommitment_isRejected() (gas: 227455) +ExerciseAuthorizationTest:test_partialQuantity_isPermitted() (gas: 372566) +ExerciseAuthorizationTest:test_prospectiveBacking_permitsAtExactSufficiency() (gas: 489516) +ExerciseAuthorizationTest:test_prospectiveBacking_permitsWhenCapacityStrictlyExceedsRemainingObligation() (gas: 403387) +ExerciseAuthorizationTest:test_qualifiedRequest_bindsExactlyOneAuthorizedCausalContext() (gas: 408698) +ExerciseAuthorizationTest:test_quantityAboveRemaining_isRejected() (gas: 222521) +ExerciseAuthorizationTest:test_restoredBeneficiaryEligibility_restoresAuthorizability() (gas: 393225) +ExerciseAuthorizationTest:test_secondAuthorization_cannotOverwriteOrCoexist() (gas: 521258) +ExerciseAuthorizationTest:test_zeroQuantity_isRejected() (gas: 222389) +ExerciseAuthorizationTransactionScopeTest:test_authorization_doesNotSurviveItsTransaction() (gas: 9790) +ExerciseAuthorizationTransactionScopeTest:test_theNextTransaction_mayAuthorizeAfresh() (gas: 219377) +ExerciseAuthorizationUnitTest:test_authorizationInFlight_blocksAnotherAuthorization() (gas: 225553) +ExerciseAuthorizationUnitTest:test_commitmentOfAnotherService_isRejected() (gas: 135110) +ExerciseAuthorizationUnitTest:test_failedAuthorization_discardsItsInFlightClaim() (gas: 387723) +ExerciseAuthorizationUnitTest:test_insufficientProspectiveExerciseBacking_isRejected() (gas: 268488) +ExerciseAuthorizationUnitTest:test_prospectiveExerciseBacking_decidesAtTheExactUnit() (gas: 417913) +ExerciseAuthorizationUnitTest:test_refusedBackingCheck_reducesNoObligation() (gas: 259064) +ExerciseBackingFuzzTest:testFuzz_authorization_isAdmittedExactlyWhenTheCompleteExerciseStaysBacked(uint256,uint256,bool,int8) (runs: 1008, μ: 373241, ~: 412012) +ExerciseFinalizationFuzzTest:testFuzz_anyAdmissibleEntitlement_canBeExhaustedExactly(uint128) (runs: 1008, μ: 489791, ~: 489489) +ExerciseFinalizationFuzzTest:testFuzz_completedExercise_fulfilsExactlyTheExercisedQuantity(uint256) (runs: 1008, μ: 475699, ~: 475423) +ExerciseFinalizationFuzzTest:testFuzz_completedExercise_leavesNoReusableCausalEvidence(uint256) (runs: 1008, μ: 418553, ~: 418277) +ExerciseFinalizationFuzzTest:testFuzz_fulfilledService_remainsBackedByIndependentDerivation(uint256) (runs: 1008, μ: 478555, ~: 478279) +ExerciseFinalizationFuzzTest:testFuzz_sequentialExercises_produceTheExactRemainder(uint256,uint256) (runs: 1008, μ: 599353, ~: 599462) +ExerciseFinalizationTest:test_absentContext_finalizesNothing() (gas: 212692) +ExerciseFinalizationTest:test_authorizedButUnexecutedContext_finalizesNothing() (gas: 221155) +ExerciseFinalizationTest:test_canonicalSequence_reachesTheFrozenTerminalState() (gas: 580700) +ExerciseFinalizationTest:test_completedExercise_leavesNoReusableCausalEvidence() (gas: 418110) +ExerciseFinalizationTest:test_completedExercise_reducesRemainingByExactlyQ() (gas: 479065) +ExerciseFinalizationTest:test_crossCommitmentFinalization_isRefused() (gas: 356923) +ExerciseFinalizationTest:test_directFinalizationOutsideAnExercise_fulfilsNothing() (gas: 295166) +ExerciseFinalizationTest:test_directTransferToTheBeneficiary_fulfilsNothing() (gas: 311807) +ExerciseFinalizationTest:test_exactlyBackedResultingState_isAccepted() (gas: 257418) +ExerciseFinalizationTest:test_exerciseAtExactBacking_succeeds() (gas: 541108) +ExerciseFinalizationTest:test_exerciseAuthority_cannotFinalizeDirectly() (gas: 221129) +ExerciseFinalizationTest:test_exhaustedCommitment_cannotBeExercisedAgain() (gas: 489855) +ExerciseFinalizationTest:test_exhaustingExercise_leavesZeroRemainingAndZeroObligation() (gas: 498249) +ExerciseFinalizationTest:test_exhaustingProof_leavesZeroRemainingAndZeroObligation() (gas: 247257) +ExerciseFinalizationTest:test_finalBacking_usesActualCapacityRatherThanTheProspectivePrediction() (gas: 240991) +ExerciseFinalizationTest:test_finalization_cannotBeReplayed() (gas: 241951) +ExerciseFinalizationTest:test_finalization_consumesTheWholeCausalContext() (gas: 238926) +ExerciseFinalizationTest:test_inFlightContext_finalizesNothing() (gas: 221110) +ExerciseFinalizationTest:test_minimalExercise_fulfilsExactlyOneRawUnit() (gas: 475146) +ExerciseFinalizationTest:test_ordinarySwap_fulfilsNothing() (gas: 431726) +ExerciseFinalizationTest:test_partiallyFulfilledCommitment_admitsExactlyItsRemainder() (gas: 547794) +ExerciseFinalizationTest:test_proofExceedingTheCurrentRemainder_isRefused() (gas: 222949) +ExerciseFinalizationTest:test_reduction_followsTheProvenQuantity() (gas: 399110) +ExerciseFinalizationTest:test_refusedExercise_fulfilsNothing() (gas: 544881) +ExerciseFinalizationTest:test_sequentialPartialExercises_produceTheExactRemainder() (gas: 677504) +ExerciseFinalizationTest:test_unbackedResultingState_refusesFulfillment() (gas: 240001) +ExerciseFinalizationTest:test_unconfiguredRouter_cannotFinalize() (gas: 223106) +ExerciseFinalizationTest:test_undecidedContext_finalizesNothing() (gas: 221176) +ExerciseFinalizationTest:test_unpayableExercise_leavesNeitherDeliveryNorFulfillment() (gas: 439503) +ExerciseFinalizationTest:test_unrelatedAccount_cannotFinalize() (gas: 223097) +ExerciseSettlementCausalPrerequisiteTest:test_absentContext_authorizesNoResolution() (gas: 24387) +ExerciseSettlementCausalPrerequisiteTest:test_authorizedButUnexecutedContext_authorizesNoResolution() (gas: 38779) +ExerciseSettlementCausalPrerequisiteTest:test_inFlightContext_authorizesNoResolution() (gas: 38781) +ExerciseSettlementCausalPrerequisiteTest:test_undecidedContext_authorizesNoResolution() (gas: 38758) +ExerciseSettlementDerivationFuzzTest:testFuzz_authoritativeDebt_isTheExecutedDirectionsInputSide(bool,int128,int128) (runs: 1008, μ: 19555, ~: 20101) +ExerciseSettlementDerivationTest:test_creditSignedInputSide_isRefused() (gas: 17988) +ExerciseSettlementDerivationTest:test_debtOnTheWrongSide_isRefused() (gas: 17967) +ExerciseSettlementDerivationTest:test_minimumSignedDebt_convertsExactly() (gas: 15091) +ExerciseSettlementDerivationTest:test_oppositeDirection_spendsCurrency1AndProducesCurrency0() (gas: 19787) +ExerciseSettlementDerivationTest:test_protectedDirection_spendsCurrency0AndProducesCurrency1() (gas: 19790) +ExerciseSettlementDerivationTest:test_smallestSignedDebt_isSettleable() (gas: 15115) +ExerciseSettlementDerivationTest:test_zeroInputSide_isRefused() (gas: 17957) +ExerciseSettlementPerimeterTest:test_directSettlementCallback_isRefused() (gas: 213825) +ExerciseSettlementPerimeterTest:test_directSettlementCallback_isRefusedForTheExerciseAuthority() (gas: 211855) +ExerciseSettlementPerimeterTest:test_fundedRouter_neitherFundsNorReceives() (gas: 537609) +ExerciseSettlementPerimeterTest:test_fundedThirdParty_cannotBecomeThePayer() (gas: 353052) +ExerciseSettlementPerimeterTest:test_protectedOutput_movesOnlyFromThePoolManagerToTheBeneficiary() (gas: 450855) +ExerciseSettlementPerimeterTest:test_resolvedExercise_cannotBeResolvedAgain() (gas: 453945) +ExerciseSettlementPerimeterTest:test_thirdPartyApproval_doesNotFundAnAuthorizedExercise() (gas: 589451) +ExerciseSettlementQuantityFuzzTest:testFuzz_costBound_admitsExactlyTheAuthoritativeDebt(uint256) (runs: 1008, μ: 567307, ~: 546118) +ExerciseSettlementQuantityFuzzTest:testFuzz_exercise_settlesTheExactDebtAndDeliversExactlyQ(uint256) (runs: 1008, μ: 610037, ~: 609831) +ExerciseSettlementTest:test_authorizedExercise_settlesExactlyAndDeliversDirectly() (gas: 587776) +ExerciseSettlementTest:test_causalContext_remainsExecutedAfterSettlementAndDelivery() (gas: 398368) +ExerciseSettlementTest:test_costBoundAboveTheDebt_isAccepted() (gas: 587696) +ExerciseSettlementTest:test_costBoundBelowTheDebt_unwindsTheWholeExercise() (gas: 545148) +ExerciseSettlementTest:test_costBoundEqualToTheDebt_isAccepted() (gas: 587602) +ExerciseSettlementTest:test_costBound_leavesNoTraceInAuthoritativeState() (gas: 581656) +ExerciseSettlementTest:test_delivery_followsTheExercisedCommitmentsBeneficiary() (gas: 534527) +ExerciseSettlementTest:test_delivery_followsTheOtherCommitmentsBeneficiary() (gas: 534556) +ExerciseSettlementTest:test_exerciserWithoutFunds_unwindsTheWholeExercise() (gas: 575993) +ExerciseSettlementTest:test_insufficientAllowance_unwindsTheWholeExercise() (gas: 557351) +ExerciseSettlementTest:test_secondExerciseAfterSettlement_isRejected() (gas: 396558) +ExerciseSettlementTest:test_settledAndDeliveredExercise_fulfilsNothing() (gas: 457190) +ExerciseSettlementTest:test_settlementAmount_isTheAuthoritativeDebtRatherThanTheRequest() (gas: 529395) +MisdirectedFinalizationTest:test_correctlyDirectedFinalization_commitsTheSameExercise() (gas: 635024) +MisdirectedFinalizationTest:test_refusedFinalization_unwindsTheCompleteExercise() (gas: 625612) +O3AuthenticBackingEnforcementTest:test_afterTheDestructiveRefusal_aCompatibleRequestBySameTraderStillSucceeds() (gas: 481500) +O3AuthenticBackingEnforcementTest:test_aggregateObligation_ofMultipleAuthenticCommitments_isWhatEnforcementProtects() (gas: 835966) +O3AuthenticBackingEnforcementTest:test_aggregateObligation_shrinksByDerivationAsMembersExpire() (gas: 696138) +O3AuthenticBackingEnforcementTest:test_authenticObligation_arisesOnlyFromTheProductionAdmissionTransition() (gas: 709181) +O3AuthenticBackingEnforcementTest:test_beneficiaryIneligibility_neitherReducesTheObligationNorRemovesProtection() (gas: 338797) +O3AuthenticBackingEnforcementTest:test_canonicalA2_compatibleOrdinarySwap_succeedsAgainstAuthenticObligation() (gas: 509574) +O3AuthenticBackingEnforcementTest:test_canonicalA3_destructiveOrdinarySwap_isRejectedForInsufficientBacking() (gas: 605189) +O3AuthenticBackingEnforcementTest:test_destructiveLiquidityRemoval_afterEligibilityLoss_isStillRejectedForBacking() (gas: 253690) +O3AuthenticBackingEnforcementTest:test_destructiveLiquidityRemoval_isRejectedForInsufficientBacking() (gas: 349834) +O3AuthenticBackingEnforcementTest:test_exactBoundaryLiquidityRemoval_whereProspectiveCapacityEqualsTheObligation_isPermitted() (gas: 358768) +O3AuthenticBackingEnforcementTest:test_exactBoundarySwap_whereProspectiveCapacityEqualsTheObligation_isPermitted() (gas: 491851) +O3AuthenticBackingEnforcementTest:test_expiry_releasesTheObligationByDerivationWithoutChangingRemaining() (gas: 519557) +O3AuthenticBackingEnforcementTest:test_forgedHookData_stillCannotEstablishTheActorUnderAuthenticObligation() (gas: 241322) +O3AuthenticBackingEnforcementTest:test_liquidityAddition_byIneligibleProvider_isStillRejectedUnderAuthenticObligation() (gas: 312829) +O3AuthenticBackingEnforcementTest:test_liquidityAddition_withAnInteriorBoundary_isStillRejectedUnderAuthenticObligation() (gas: 239241) +O3AuthenticBackingEnforcementTest:test_liquidityAddition_withAuthenticObligation_isPermittedAndLeavesTheObligationAlone() (gas: 412248) +O3AuthenticBackingEnforcementTest:test_oneRawUnitBeyondTheBoundary_isRejectedForInsufficientBacking() (gas: 276891) +O3AuthenticBackingEnforcementTest:test_oppositeDirectionSwap_leavingTheServiceDomain_isRejectedForDomainNotBacking() (gas: 338830) +O3AuthenticBackingEnforcementTest:test_oppositeDirectionSwap_withAuthenticObligation_isPermittedAndIncreasesCapacity() (gas: 406135) +O3AuthenticBackingEnforcementTest:test_permissioningAndBackingRemainDistinctRefusals() (gas: 443997) +O3AuthenticBackingEnforcementTest:test_safeLiquidityRemoval_afterProviderEligibilityLoss_stillExecutes() (gas: 380885) +O3AuthenticBackingEnforcementTest:test_safeLiquidityRemoval_withAuthenticObligation_isPermitted() (gas: 396347) +O3AuthenticBackingEnforcementTest:test_successfulOrdinarySwaps_leaveEveryCommitmentFactIntact() (gas: 707202) +O3AuthenticBackingEnforcementTest:test_untrustedPerimeter_isStillRefusedUnderAuthenticObligation() (gas: 1345201) +O3AuthenticBackingFuzzTest:testFuzz_liquidityRemoval_isAdmittedExactlyWhenProspectiveCapacityCoversTheObligation(uint128,uint128,uint128,uint256,bool,int8,bool) (runs: 1008, μ: 648775, ~: 655238) +O3AuthenticBackingFuzzTest:testFuzz_protectedSwap_isAdmittedExactlyWhenProspectiveCapacityCoversTheObligation(uint128,uint128,uint128,uint256,bool,int8) (runs: 1008, μ: 696914, ~: 711335) +O3LiquidityEnforcementTest:test_activeLiquidityRemoval_matchesTheProspectiveDerivation() (gas: 203827) +O3LiquidityEnforcementTest:test_directLiquidityCallbackInvocation_isRejected() (gas: 21840) +O3LiquidityEnforcementTest:test_inactiveLiquidityRemoval_matchesTheProspectiveDerivation() (gas: 222625) +O3LiquidityEnforcementTest:test_liquidityAddition_againstAnUnconfiguredPool_isRejected() (gas: 82653) +O3LiquidityEnforcementTest:test_liquidityAddition_byAnEligibleTraderWithoutLiquidityEligibility_isRejected() (gas: 62983) +O3LiquidityEnforcementTest:test_liquidityAddition_byEligibleProvider_becomesAuthoritative() (gas: 234341) +O3LiquidityEnforcementTest:test_liquidityAddition_byIneligibleProvider_isRejected() (gas: 70408) +O3LiquidityEnforcementTest:test_liquidityAddition_entirelyOutsideTheServiceDomain_isPermitted() (gas: 187891) +O3LiquidityEnforcementTest:test_liquidityAddition_succeedsForTheSameActorOnceEligible() (gas: 208047) +O3LiquidityEnforcementTest:test_liquidityAddition_throughAnUntrustedPerimeter_cannotAuthorize() (gas: 1093489) +O3LiquidityEnforcementTest:test_liquidityAddition_withAnInteriorLowerBoundary_isRejected() (gas: 95006) +O3LiquidityEnforcementTest:test_liquidityAddition_withAnInteriorUpperBoundary_isRejected() (gas: 58725) +O3LiquidityEnforcementTest:test_liquidityAddition_withEndpointsOnTheServiceBoundaries_isPermitted() (gas: 226470) +O3LiquidityEnforcementTest:test_liquidityRemoval_afterEligibilityIsRevoked_stillExecutes() (gas: 269261) +O3LiquidityEnforcementTest:test_liquidityRemoval_byEligibleProvider_becomesAuthoritative() (gas: 241053) +O3LiquidityEnforcementTest:test_zeroDeltaLiquidityOperation_afterEligibilityIsRevoked_isPermitted() (gas: 272365) +O3LiquidityFuzzTest:testFuzz_liquidityAddition_isAdmittedExactlyWhenEligibleAndTopologyValid(bool,int24,int24,uint128) (runs: 1008, μ: 134107, ~: 114934) +O3LiquidityFuzzTest:testFuzz_liquidityRemoval_isIndependentOfCurrentEligibility(bool,int24,int24,uint128,uint256) (runs: 1008, μ: 340185, ~: 343978) +O3SwapEnforcementTest:test_afterSwap_completesWithoutTakingADelta() (gas: 22241) +O3SwapEnforcementTest:test_completedOrdinarySwap_leavesNoCommitmentOrFulfillmentState() (gas: 230746) +O3SwapEnforcementTest:test_directCallbackInvocation_isRejected() (gas: 24121) +O3SwapEnforcementTest:test_forgedHookData_cannotEstablishTheActor() (gas: 60817) +O3SwapEnforcementTest:test_oppositeDirectionSwap_byEligibleTrader_becomesAuthoritative() (gas: 232815) +O3SwapEnforcementTest:test_oppositeDirectionSwap_leavingTheServiceDomain_isRejectedWhileObligationIsZero() (gas: 128159) +O3SwapEnforcementTest:test_ordinarySwap_againstAnUnconfiguredPool_isRejected() (gas: 83299) +O3SwapEnforcementTest:test_ordinarySwap_throughAnUntrustedPerimeter_cannotAuthorize() (gas: 1105145) +O3SwapEnforcementTest:test_protectedDirectionSwap_byEligibleTrader_becomesAuthoritative() (gas: 271402) +O3SwapEnforcementTest:test_protectedDirectionSwap_byIneligibleTrader_isRejected() (gas: 71804) +O3SwapEnforcementTest:test_protectedDirectionSwap_isRejectedAfterEligibilityIsRevoked() (gas: 248644) +O3SwapEnforcementTest:test_protectedDirectionSwap_leavingTheServiceDomain_isRejectedWhileObligationIsZero() (gas: 99452) +O3SwapEnforcementTest:test_protectedDirectionSwap_succeedsForTheSameActorOnceEligible() (gas: 245080) +O3SwapEnforcementTest:test_protectedDirectionSwap_toTheCapacityBoundary_isPermitted() (gas: 229323) +O3SwapFuzzTest:testFuzz_acceptedOrdinarySwap_matchesTheProspectiveDerivation(bool,bool,uint256,bytes) (runs: 1008, μ: 245322, ~: 252295) +O3SwapFuzzTest:testFuzz_domainLeavingSwap_neverBecomesAuthoritative(bool,uint256) (runs: 1008, μ: 133029, ~: 129275) +O3SwapFuzzTest:testFuzz_ineligibleTrader_neverProducesAnAuthoritativeSwap(bool,bool,uint256,bytes) (runs: 1008, μ: 72884, ~: 73027) +O3SwapFuzzTest:testFuzz_untrustedPerimeter_neverProducesAnAuthoritativeSwap(bool,bool,uint256) (runs: 1008, μ: 1105993, ~: 1106246) +ProductionExerciseCompletionTest:test_unfinalizedExercise_cannotComplete() (gas: 462285) +ProductionExerciseCompletionTest:test_unfinalizedExercise_isRefusedWhateverTheRequest() (gas: 808797) +ProspectiveStateEquivalenceTest:test_prospectiveRemoval_activePositionMatchesRealExecution() (gas: 182328) +ProspectiveStateEquivalenceTest:test_prospectiveRemoval_fullActiveRemovalLeavesNoCapacity() (gas: 181020) +ProspectiveStateEquivalenceTest:test_prospectiveRemoval_inactivePositionChangesNothing() (gas: 231603) +ProspectiveStateEquivalenceTest:test_prospectiveRemoval_refusesANonRemoval() (gas: 13552) +ProspectiveStateEquivalenceTest:test_prospectiveSwap_acrossATickBitmapWordEdgeMatchesRealExecution() (gas: 303502) +ProspectiveStateEquivalenceTest:test_prospectiveSwap_derivationMutatesNothing() (gas: 109967) +ProspectiveStateEquivalenceTest:test_prospectiveSwap_exactInputProtectedDirectionMatchesRealExecution() (gas: 215310) +ProspectiveStateEquivalenceTest:test_prospectiveSwap_exactOutputProtectedDirectionMatchesRealExecution() (gas: 213679) +ProspectiveStateEquivalenceTest:test_prospectiveSwap_oppositeDirectionMatchesRealExecution() (gas: 197542) +ProspectiveStateEquivalenceTest:test_prospectiveSwap_reachingPQExactlyMatchesRealExecution() (gas: 215251) +ProspectiveStateEquivalenceTest:test_prospectiveSwap_refusesAPriceLimitUniswapWouldReject() (gas: 50645) +ProspectiveStateEquivalenceTest:test_prospectiveSwap_refusesCapacityForAPredictedStateOutsideTheDomain() (gas: 73854) +ProspectiveStateEquivalenceTest:test_prospectiveSwap_singleStepModelDisagreesWhereTheProductionDerivationDoesNot() (gas: 276177) +ProspectiveStateEquivalenceTest:test_prospectiveSwap_zeroAmountChangesNothing() (gas: 47705) +ProspectiveTraversalBoundTest:test_activation_acceptsADomainDemandingExactlyTheSupportedBound() (gas: 209024839) +ProspectiveTraversalBoundTest:test_activation_rejectsADomainDemandingOneStepBeyondTheBound() (gas: 209045195) +ProspectiveTraversalBoundTest:test_activation_rejectsAFarOverBoundDomainBeforeAnyPersistence() (gas: 209045064) +ProspectiveTraversalBoundTest:test_prospectiveCapacity_propagatesTheRuntimeBoundRefusal() (gas: 209593665) +ProspectiveTraversalBoundTest:test_prospectiveSwap_derivesTheWorstInDomainPathOfAnExactlyBoundedService() (gas: 209985615) +ProspectiveTraversalBoundTest:test_prospectiveSwap_derivesTheWorstUpwardInDomainPathOfAnExactlyBoundedService() (gas: 209876439) +ProspectiveTraversalBoundTest:test_prospectiveSwap_reachesTheRuntimeBoundOnlyOutsideTheServiceDomain() (gas: 209596229) +ProtectedExecutionDirectionTest:test_mirroredService_admitsOnlyTheMirroredOperation() (gas: 44939) +ProtectedExecutionDirectionTest:test_mirroredService_readsTheProtectedOutputFromCurrencyZero() (gas: 61779) +ProtectedExecutionEvidenceTest:test_evidenceForAnotherOperation_isRefused() (gas: 67531) +ProtectedExecutionEvidenceTest:test_exactProtectedOutput_establishesExecution() (gas: 46954) +ProtectedExecutionEvidenceTest:test_executionEvidence_requiresAnExecutionInFlight() (gas: 53612) +ProtectedExecutionEvidenceTest:test_malformedExecutionEvidence_isRefused() (gas: 133182) +ProtectedExecutionEvidenceTest:test_matchingProposal_reachesExecutingAndNotExecuted() (gas: 32469) +ProtectedExecutionEvidenceTest:test_onlyAnAuthorizedContext_admitsAProtectedExecution() (gas: 45600) +ProtectedExecutionFailureTest:test_failedExecution_unwindsTheExerciseAndCannotBeRetried() (gas: 622582) +ProtectedExecutionFailureTest:test_partialActualOutput_cannotProduceExecutionEvidence() (gas: 332046) +ProtectedExecutionFuzzTest:testFuzz_onlyExactlyQ_isExecutionEvidence(int128,int128) (runs: 1008, μ: 53825, ~: 54069) +ProtectedExecutionFuzzTest:testFuzz_onlyTheBoundRouter_mayProposeTheExecution(address) (runs: 1008, μ: 25368, ~: 25368) +ProtectedExecutionFuzzTest:testFuzz_onlyTheServiceDefinedOperation_isClassifiedO2(bool,int256,int24) (runs: 1008, μ: 38825, ~: 38713) +ProtectedExecutionPerimeterTest:test_authorizedExactOperation_isClassifiedO2() (gas: 376886) +ProtectedExecutionPerimeterTest:test_configuredRouterIdentityAlone_doesNotClassifyO2() (gas: 237216) +ProtectedExecutionPerimeterTest:test_exactInputSubstitution_isRejected() (gas: 277360) +ProtectedExecutionPerimeterTest:test_forgedCausalPayload_changesNothingAboutAnAuthorizedExecution() (gas: 392553) +ProtectedExecutionPerimeterTest:test_forgedCausalPayload_manufacturesNoAuthorization() (gas: 252823) +ProtectedExecutionPerimeterTest:test_secondAuthorization_isRejected() (gas: 250462) +ProtectedExecutionPerimeterTest:test_secondSwapInsideOneAuthorization_isRejected() (gas: 465240) +ProtectedExecutionPerimeterTest:test_substitutedDirection_isRejected() (gas: 277103) +ProtectedExecutionPerimeterTest:test_substitutedPool_isRejected() (gas: 304108) +ProtectedExecutionPerimeterTest:test_substitutedQualificationBoundary_isRejected() (gas: 277481) +ProtectedExecutionPerimeterTest:test_substitutedQuantity_isRejected() (gas: 343760) +ProtectedExecutionQuantityFuzzTest:testFuzz_authorizedExercise_producesExactlyQ(uint256) (runs: 1008, μ: 417416, ~: 417175) +ProtectedExecutionTest:test_authorizedExercise_executesExactlyQAndRecordsEvidence() (gas: 376944) +ProtectedExecutionTest:test_executedExercise_reachesTheF5PredictedPostState() (gas: 407957) +ProtectedExecutionTest:test_executionEvidence_fulfilsNothing() (gas: 393443) +ProtectedExecutionTest:test_exerciseOfTheWholeSupportingCapacity_executesExactly() (gas: 383733) +ProtectedExecutionTest:test_ordinarySwap_whileAnO2ContextIsUnresolved_isRejected() (gas: 397408) +ProtectedExecutionTest:test_ordinarySwaps_keepTheirO3TreatmentWithoutAnO2Context() (gas: 405030) +ProtectedExecutionTest:test_secondExercise_againstAnUnresolvedContext_isRejected() (gas: 375914) +ServiceDomainFuzzTest:testFuzz_containment_equalsIndependentReference(int24,int24,uint256) (runs: 1008, μ: 7616, ~: 7388) +ServiceDomainFuzzTest:testFuzz_containment_includesBothBoundaries(int24,int24) (runs: 1008, μ: 5337, ~: 5322) +ServiceDomainFuzzTest:testFuzz_directionConsistency_acceptsExactlyOneDirection(int24,int24) (runs: 1008, μ: 3410, ~: 3384) +ServiceDomainFuzzTest:testFuzz_interiorBoundary_equalsIndependentReference(int24,int24,int24,int24) (runs: 1008, μ: 6667, ~: 6450) +ServiceDomainFuzzTest:testFuzz_interiorBoundary_permitsAnyEnclosingPosition(int24,int24,int24) (runs: 1008, μ: 5636, ~: 5720) +ServiceDomainFuzzTest:testFuzz_numericBounds_areOrderIndependent(int24,int24) (runs: 1008, μ: 3762, ~: 3736) +ServiceDomainFuzzTest:testFuzz_traversalDemand_equalsIndependentReference(int24,int24,uint256) (runs: 1008, μ: 6626, ~: 6585) +ServiceDomainFuzzTest:testFuzz_traversalDemand_isBoundedForAMinimalDomain(int24,uint256) (runs: 1008, μ: 3691, ~: 3502) +ServiceDomainFuzzTest:testFuzz_traversalDemand_isIndependentOfBoundaryRoles(int24,int24,uint256) (runs: 1008, μ: 5528, ~: 5567) +ServiceDomainFuzzTest:testFuzz_traversalDemand_isMonotoneInDomainWidth(int24,int24,int24,uint256) (runs: 1008, μ: 7211, ~: 7276) +ServiceDomainTest:test_containsPrice_equalsIndependentReference() (gas: 15905) +ServiceDomainTest:test_containsPrice_excludesOneUnitBeyondEitherBoundary() (gas: 2560) +ServiceDomainTest:test_containsPrice_includesBothBoundaries() (gas: 2358) +ServiceDomainTest:test_directionConsistency_rejectsEqualBoundaries() (gas: 677) +ServiceDomainTest:test_directionConsistency_requiresQAboveOForProtectedOneForZero() (gas: 668) +ServiceDomainTest:test_directionConsistency_requiresQBelowOForProtectedZeroForOne() (gas: 624) +ServiceDomainTest:test_interiorBoundary_equalsIndependentReference() (gas: 6233) +ServiceDomainTest:test_interiorBoundary_flagsEitherEndpointStrictlyInside() (gas: 1452) +ServiceDomainTest:test_interiorBoundary_isIndependentOfWhichBoundaryIsPQ() (gas: 942) +ServiceDomainTest:test_interiorBoundary_permitsAPositionEnclosingTheDomain() (gas: 624) +ServiceDomainTest:test_interiorBoundary_permitsAPositionEntirelyOutsideTheDomain() (gas: 1122) +ServiceDomainTest:test_interiorBoundary_permitsEndpointsExactlyOnTheBoundaries() (gas: 647) +ServiceDomainTest:test_numericBounds_areTheMinimumAndMaximumRegardlessOfDirection() (gas: 1023) +ServiceDomainTest:test_sqrtBounds_areThePinnedPricesOfTheNumericBoundaries() (gas: 3798) +ServiceDomainTest:test_traversalDemand_chargesNoExtraStepForAWordAlignedTop() (gas: 1879) +ServiceDomainTest:test_traversalDemand_coversPositiveAndNegativeRegionsAlike() (gas: 1846) +ServiceDomainTest:test_traversalDemand_equalsIndependentReference() (gas: 18060) +ServiceDomainTest:test_traversalDemand_floorsWordIndicesInNegativeTickRegions() (gas: 2772) +ServiceDomainTest:test_traversalDemand_growsAsTickSpacingNarrows() (gas: 1858) +ServiceDomainTest:test_traversalDemand_isIndependentOfTheProtectedDirection() (gas: 1831) +ServiceDomainTest:test_traversalDemand_matchesTheCanonicalFixtureGeometry() (gas: 1102) +StandbyHookDeploymentTest:test_canonicalDeployment_producesTheDeterministicMinedAddress() (gas: 33147) +StandbyHookDeploymentTest:test_canonicalDeployment_usesHelperConfigResolvedPoolManager() (gas: 31411328) +StandbyHookDeploymentTest:test_deployedHookAddress_encodesExactlyTheRequiredPermissionBits() (gas: 4148) +StandbyHookDeploymentTest:test_deployedHookAddress_satisfiesPinnedHookValidator() (gas: 9092) +StandbyHookDeploymentTest:test_deployedHook_addressDependsOnTheConfiguredTrustBasis() (gas: 38082913) +StandbyHookDeploymentTest:test_deployedHook_bindsEveryIntendedTrustDependency() (gas: 17149) +StandbyHookDeploymentTest:test_deployedHook_isBoundToTheIntendedPoolManager() (gas: 7910) +StandbyHookDeploymentTest:test_deployedHook_keepsTheTwoTrustedPerimeterRolesDistinct() (gas: 151288229) +StandbyHookDeploymentTest:test_deployedHook_rejectsEveryEnabledCallbackFromNonPoolManager() (gas: 20056) +StandbyHookDeploymentTest:test_enabledCallbacks_failClosedWithoutAConfiguredService() (gas: 14417) +StandbyHookDeploymentTest:test_helperConfig_rejectsUnsupportedNetwork() (gas: 4350548) +StandbyHookDeploymentTest:test_hookDeployment_requiresNoEconomicFixture() (gas: 194379567) +StandbyHookDeploymentTest:test_permissionMask_isEquivalentAcrossDeclarationDeploymentAndAddress() (gas: 14696) +StandbyHookDeploymentTest:test_standbyHook_declaresExactlyTheAuthorizedCallbackPermissions() (gas: 9417) +StandbyHookDeploymentTest:test_standbyHook_declaresNoReturnDeltaPermissions() (gas: 8742) +StandbyServiceConfigurationTest:test_activatedService_bindsTheRegistryWithoutAdoptingItsAdministration() (gas: 225446) +StandbyServiceConfigurationTest:test_activatedService_cannotBeReinterpretedByASecondActivation() (gas: 471150) +StandbyServiceConfigurationTest:test_activatedService_keepsEveryTrustAndAuthorityRoleDistinct() (gas: 198901) +StandbyServiceConfigurationTest:test_activatedService_readsBackTheCompleteConfiguredBasis() (gas: 193658) +StandbyServiceConfigurationTest:test_activation_leavesTheAuthoritativePoolStateUnchanged() (gas: 187933) +StandbyServiceConfigurationTest:test_configureAndActivate_acceptsADomainDemandingExactlyTheSupportedBound() (gas: 215527) +StandbyServiceConfigurationTest:test_configureAndActivate_acceptsCurrentPriceExactlyAtTickO() (gas: 215818) +StandbyServiceConfigurationTest:test_configureAndActivate_acceptsCurrentPriceExactlyAtTickQ() (gas: 215783) +StandbyServiceConfigurationTest:test_configureAndActivate_acceptsProtectedOneForZeroWithItsOwnOrdering() (gas: 183774) +StandbyServiceConfigurationTest:test_configureAndActivate_activatesTheCanonicalService() (gas: 190582) +StandbyServiceConfigurationTest:test_configureAndActivate_leavesTheCanonicalDomainWellInsideTheTraversalBound() (gas: 185223) +StandbyServiceConfigurationTest:test_configureAndActivate_rejectsADomainDemandingOneStepBeyondTheBound() (gas: 92151) +StandbyServiceConfigurationTest:test_configureAndActivate_rejectsADynamicFeePool() (gas: 88483) +StandbyServiceConfigurationTest:test_configureAndActivate_rejectsAHookWithNoTrustedPositionManager() (gas: 427994963) +StandbyServiceConfigurationTest:test_configureAndActivate_rejectsAHookWithNoTrustedUniversalRouter() (gas: 57548126) +StandbyServiceConfigurationTest:test_configureAndActivate_rejectsAMisalignedServiceTick() (gas: 57330) +StandbyServiceConfigurationTest:test_configureAndActivate_rejectsAPoolKeyThatBindsADifferentStandbyHook() (gas: 70175934) +StandbyServiceConfigurationTest:test_configureAndActivate_rejectsAPoolKeyThatBindsNoHook() (gas: 80151) +StandbyServiceConfigurationTest:test_configureAndActivate_rejectsAPriceAboveTickOThatStillReportsTickO() (gas: 92191) +StandbyServiceConfigurationTest:test_configureAndActivate_rejectsAnAccountThatIsNotTheConfigurationAuthority() (gas: 44621) +StandbyServiceConfigurationTest:test_configureAndActivate_rejectsAnOutOfRangeServiceTick() (gas: 57373) +StandbyServiceConfigurationTest:test_configureAndActivate_rejectsAnOverBoundDomainInEitherProtectedDirection() (gas: 91997) +StandbyServiceConfigurationTest:test_configureAndActivate_rejectsAnUninitializedPool() (gas: 53215) +StandbyServiceConfigurationTest:test_configureAndActivate_rejectsAnUnsetEligibilityRegistry() (gas: 57859) +StandbyServiceConfigurationTest:test_configureAndActivate_rejectsAnUnsetEstablishmentAuthority() (gas: 57940) +StandbyServiceConfigurationTest:test_configureAndActivate_rejectsAnUnsetExerciseRouter() (gas: 57878) +StandbyServiceConfigurationTest:test_configureAndActivate_rejectsCurrentPriceAboveTheServiceDomain() (gas: 92151) +StandbyServiceConfigurationTest:test_configureAndActivate_rejectsCurrentPriceBelowTheServiceDomain() (gas: 92094) +StandbyServiceConfigurationTest:test_configureAndActivate_rejectsEqualServiceBoundaries() (gas: 57640) +StandbyServiceConfigurationTest:test_configureAndActivate_rejectsEveryOtherStandbyRole() (gas: 70737) +StandbyServiceConfigurationTest:test_configureAndActivate_rejectsInvertedDomainForProtectedOneForZero() (gas: 57630) +StandbyServiceConfigurationTest:test_configureAndActivate_rejectsInvertedDomainForProtectedZeroForOne() (gas: 57618) +StandbyServiceConfigurationTest:test_configureAndActivate_stillAcceptsTheCanonicalServiceAfterAnOverBoundRejection() (gas: 242676) +StandbyServiceConfigurationTest:test_configureAndActivate_succeedsExactlyOnce() (gas: 184805) +StandbyServiceConfigurationTest:test_hook_exposesNoPostActivationServiceMutationSurface() (gas: 198302) +StandbyServiceConfigurationTest:test_serviceId_isDerivedFromThePersistedPoolKey() (gas: 185088) +StandbyServiceGeometryFuzzTest:testFuzz_activation_acceptsAnyAlignedDomainContainingTheCurrentPrice(uint256,int256,int256,int256,bool) (runs: 1008, μ: 224742, ~: 224825) +StandbyServiceGeometryFuzzTest:testFuzz_activation_rejectsADomainBeyondTheSupportedTraversalBound(uint256,int256,int256,bool) (runs: 1008, μ: 99083, ~: 99247) +StandbyServiceGeometryFuzzTest:testFuzz_activation_rejectsBoundariesMisalignedToTheTickSpacing(uint256,int256,int256,uint256,bool) (runs: 1008, μ: 93822, ~: 93988) +StandbyServiceGeometryFuzzTest:testFuzz_activation_rejectsBoundariesOrderedAgainstTheProtectedDirection(uint256,int256,int256,int256,bool) (runs: 1008, μ: 94508, ~: 94727) +StandbyServiceGeometryFuzzTest:testFuzz_activation_rejectsBoundariesOutsideTheValidTickRange(int256,bool) (runs: 1008, μ: 89181, ~: 89127) +StandbyServiceGeometryFuzzTest:testFuzz_activation_rejectsCurrentPriceOutsideTheServiceDomain(uint256,int256,int256,int256,bool) (runs: 1008, μ: 98991, ~: 99264) +StandbyServiceLiquidityPreconditionTest:test_configureAndActivate_acceptsTheSameHookAndPoolBeforeLiquidityExists() (gas: 188628) +StandbyServiceLiquidityPreconditionTest:test_configureAndActivate_rejectsAPoolThatAlreadyHoldsLiquidity() (gas: 337931) +SupportingCapacityFuzzTest:testFuzz_capacity_isMonotoneInLiquidity(int24,int24,uint256,uint128,uint128,bool) (runs: 1008, μ: 9825, ~: 9835) +SupportingCapacityFuzzTest:testFuzz_capacity_isZeroAtPQForAnyLiquidity(int24,int24,uint128,bool) (runs: 1008, μ: 5531, ~: 5782) +SupportingCapacityFuzzTest:testFuzz_capacity_isZeroWithoutLiquidity(int24,int24,uint256,bool) (runs: 1008, μ: 7808, ~: 7740) +SupportingCapacityFuzzTest:testFuzz_generatedBases_areDirectionConsistentAndInsideTheDomain(int24,int24,uint256,bool) (runs: 1008, μ: 8583, ~: 8510) +SupportingCapacityFuzzTest:testFuzz_oneForZero_capacityEqualsIndependentReference(int24,int24,uint256,uint128) (runs: 1008, μ: 9916, ~: 10175) +SupportingCapacityFuzzTest:testFuzz_zeroForOne_capacityEqualsIndependentReference(int24,int24,uint256,uint128) (runs: 1008, μ: 9406, ~: 9543) +SupportingCapacityTest:test_capacity_decreasesMonotonicallyTowardPQ() (gas: 18825) +SupportingCapacityTest:test_capacity_doesNotManufactureCapacityFromReversedGeometry() (gas: 13750) +SupportingCapacityTest:test_capacity_isExactlyZeroAtPQ() (gas: 4581) +SupportingCapacityTest:test_capacity_isPositiveAtPO() (gas: 4560) +SupportingCapacityTest:test_capacity_isZeroWithoutLiquidity() (gas: 11283) +SupportingCapacityTest:test_capacity_neverExceedsTheRoundedUpAmount() (gas: 103301) +SupportingCapacityTest:test_capacity_refusesAPriceBeyondPQ() (gas: 17032) +SupportingCapacityTest:test_capacity_roundsDownWhereTheConventionsDiffer() (gas: 4957) +SupportingCapacityTest:test_directions_coincideOnlyAtPriceParity() (gas: 3633) +SupportingCapacityTest:test_directions_measureDifferentProtectedOutputs() (gas: 4260) +SupportingCapacityTest:test_oneForZero_interiorCapacityEqualsIndependentReference() (gas: 19056) +SupportingCapacityTest:test_zeroForOne_interiorCapacityEqualsIndependentReference() (gas: 17288) +V4InfrastructureTest:test_vanillaV4Infrastructure_oneForZero() (gas: 401886) +V4InfrastructureTest:test_vanillaV4Infrastructure_zeroForOne() (gas: 406788) \ No newline at end of file diff --git a/docs/project-status.md b/docs/project-status.md index 120a569..d59a032 100644 --- a/docs/project-status.md +++ b/docs/project-status.md @@ -1,10 +1,10 @@ # Standby — Project Status -**Last Updated:** September 7, 2026 +**Last Updated:** September 8, 2026 **Project Phase:** Solidity / Reference Implementation -**Current Implementation Slice:** F8D — O2 Causal Finalization / Remaining Entitlement Reduction (NEXT AUTHORIZED / NOT STARTED) -**Last Closed Gate:** G8C — Authoritative Settlement / Delivery Gate (CLOSED / PASS) -**Status:** F0 COMPLETE — F1 COMPLETE — F2 COMPLETE — F3 COMPLETE — F4 COMPLETE — F5 COMPLETE — F6A COMPLETE — F7 COMPLETE — F6B COMPLETE — F8A COMPLETE — F8B COMPLETE — F8C COMPLETE — F8D NOT STARTED +**Current Implementation Slice:** GI — Full Stateful Invariant Verification (NEXT AUTHORIZED / NOT STARTED) +**Last Closed Gate:** G8D — Causal Finalization / Economic Finality Gate (CLOSED / PASS) +**Status:** F0 COMPLETE — F1 COMPLETE — F2 COMPLETE — F3 COMPLETE — F4 COMPLETE — F5 COMPLETE — F6A COMPLETE — F7 COMPLETE — F6B COMPLETE — F8A COMPLETE — F8B COMPLETE — F8C COMPLETE — F8D COMPLETE — GI NOT STARTED --- @@ -41,15 +41,15 @@ Implementation proceeds through the verification-gated F0–F10 ladder defined i The next authorized slice is: -> **F8D — O2 Causal Finalization / Remaining Entitlement Reduction** +> **GI — Full Stateful Invariant Verification** The last closed gate is: -> **G8C — Authoritative Settlement / Delivery Gate** +> **G8D — Causal Finalization / Economic Finality Gate** -F0, F1, F2, F3, F4, F5, F6A, F7, F6B, F8A, F8B and F8C have been implemented, verified, and explicitly gate-closed. The targeted G3 revalidation required by the F5 prospective-derivability correction also passed, and G3 remains closed. +F0, F1, F2, F3, F4, F5, F6A, F7, F6B, F8A, F8B, F8C and F8D have been implemented, verified, and explicitly gate-closed. The targeted G3 revalidation required by the F5 prospective-derivability correction also passed, and G3 remains closed. -F8C was reviewed independently and G8C was explicitly closed. F8D is the next authorized implementation slice and has not been started. +F8D was reviewed independently and G8D was explicitly closed. GI is the next authorized implementation slice and has not been started. --- @@ -71,8 +71,8 @@ Current implementation sequence: | F8A | O2 Authorization | **COMPLETE — G8A CLOSED** | | F8B | O2 Exact-Output Execution + Causal Evidence | **COMPLETE — G8B CLOSED** | | F8C | O2 Input Settlement + Direct Beneficiary Delivery | **COMPLETE — G8C CLOSED** | -| F8D | O2 Causal Finalization | **NEXT / NOT STARTED** | -| GI | Full Stateful Invariant Verification | NOT STARTED | +| F8D | O2 Causal Finalization | **COMPLETE — G8D CLOSED** | +| GI | Full Stateful Invariant Verification | **NEXT / NOT STARTED** | | F9 | Canonical Acceptance | NOT STARTED | | F10 | Demo Instrumentation | NOT STARTED | | F9T | Public Testnet Evidence | OPTIONAL / OFF CRITICAL PATH | @@ -218,7 +218,7 @@ F7 adds the production O1 commitment-admission transition. F8A adds the production O2 authorization transition and its Hook-owned transaction-scoped causal context. F8B adds the production O2 execution classification and execution evidence. F8C adds the production O2 -input settlement and direct Beneficiary delivery. Finalization remains unimplemented. +input settlement and direct Beneficiary delivery. F8D adds the production O2 causal finalization. ### `script/DeployStandbyHook.s.sol` @@ -371,14 +371,14 @@ G2 verified predicate independence, administrator authority, read fidelity, cros ## 10. Current Blocker -There is no unresolved F0, F1, F2, F3, F4, F5, F6A, F7, F6B, F8A, F8B or F8C implementation responsibility and no open gate. +There is no unresolved F0, F1, F2, F3, F4, F5, F6A, F7, F6B, F8A, F8B, F8C or F8D implementation responsibility and no open gate. The next unstarted responsibility is: -> **F8D — O2 Causal Finalization / Remaining Entitlement Reduction.** +> **GI — Full Stateful Invariant Verification.** -F8D is authorized as the next implementation slice but has not been started. No further downstream slice — -GI — is authorized. +GI is authorized as the next implementation slice but has not been started. No further downstream slice — +F9 — is authorized. Known limitations carried forward from F0, none of which blocked G0: @@ -389,7 +389,7 @@ Known limitations carried forward from F0, none of which blocked G0: node with `--broadcast`; - the four enabled Hook callbacks now enforce ordinary O3 admission, and refuse everything while no Protected Execution Service is configured. `StandbyHook` must still not be attached to a live pool - before its service is activated, and O2 finalization remains unimplemented. + before its service is activated. --- @@ -581,11 +581,11 @@ The root `CLAUDE.md` will define the repository operating rules and document aut ## 18. Next Action -G8C has been reviewed and closed, so the next action is: +G8D has been reviewed and closed, so the next action is: -> **F8D — O2 Causal Finalization / Remaining Entitlement Reduction.** +> **GI — Full Stateful Invariant Verification.** -F8D may begin when explicitly tasked. Its own verification gate governs advancement beyond the F8D boundary. +GI may begin when explicitly tasked. Its own verification gate governs advancement beyond the GI boundary. --- @@ -607,16 +607,16 @@ F8D may begin when explicitly tasked. Its own verification gate governs advancem - F8A O2 Authorization / Hook-Owned Causal Context is complete and G8A is closed. - F8B O2 Exact-Output Execution / Execution Evidence is complete and G8B is closed. - F8C O2 Input Settlement / Direct Beneficiary Delivery is complete and G8C is closed. -- No O2 finalization behavior has been introduced. +- F8D O2 Causal Finalization / Remaining Entitlement Reduction is complete and G8D is closed. **Current Gate** -- G8C — CLOSED / PASS. No gate is currently open. +- G8D — CLOSED / PASS. No gate is currently open. **Next Blocker** -- F8D — O2 Causal Finalization / Remaining Entitlement Reduction has not been started. +- GI — Full Stateful Invariant Verification has not been started. **Immediate Next Step** -- Begin F8D when explicitly tasked. +- Begin GI when explicitly tasked. diff --git a/docs/prompts/retrospective/session-14-chatgpt-record.md b/docs/prompts/retrospective/session-14-chatgpt-record.md new file mode 100644 index 0000000..8e69ea2 --- /dev/null +++ b/docs/prompts/retrospective/session-14-chatgpt-record.md @@ -0,0 +1,1743 @@ +# Session 14 — ChatGPT Reasoning Record + +## F8D — O2 Causal Finalization / Remaining Reduction + +**Project:** Standby +**Session:** 14 +**Implementation slice:** F8D — O2 Causal Finalization / Remaining Reduction +**Final gate:** G8D — PASS / CLOSED +**Next implementation frontier:** GI — Full Stateful Invariant Verification +**Status:** Non-normative retrospective evidence + +--- + +## 1. Purpose of This Record + +This document is a **non-normative curated record of the substantive user ↔ ChatGPT reasoning associated with F8D**. + +It preserves the reasoning that materially affected: + +- reconstruction of the F8D responsibility; +- separation of F8D from F8A, F8B, F8C, F7, and F6B; +- derivation of final backing verification; +- authoritative fulfillment mutation; +- causal-context consumption; +- replay, substitution, stale-context, nested-call, and atomicity analysis; +- derivation of G8D; +- preparation and clean-rule review of the Claude implementation prompt; +- independent review of Claude's actual implementation and tests; +- review of the Foundry gas-limit infrastructure correction; +- independent G8D closure; +- post-F8D / pre-GI gas and coverage baseline; +- interpretation of the transition from implementation slices to full stateful invariant verification. + +This record does not modify or supersede the frozen Standby artifacts. + +Where exact conversational wording is not reproduced, the material is preserved as a **recovered decision record**. + +--- + +# 2. Starting State + +Session 14 began after independent completion of F8C. + +The implementation ladder entering the session was: + +```text +F0 Foundation COMPLETE +F1 Deterministic Economic Fixture COMPLETE +F2 Eligibility Registry COMPLETE +F3 StandbyHook Trust + PES Configuration COMPLETE +F4 Commitment Storage / Bounded References COMPLETE +F5 Authoritative Derivation Kernel COMPLETE +F6A Preliminary O3 Enforcement COMPLETE +F7 O1 Commitment Admission COMPLETE +F6B O3 Bounded Enforcement COMPLETE +F8A Exercise Authorization COMPLETE +F8B Protected PoolManager Execution COMPLETE +F8C Authoritative Settlement / Direct Delivery COMPLETE + +F8D O2 Causal Finalization / Remaining Reduction NEXT +GI Full Stateful Invariant Verification PENDING +F9 Canonical Acceptance PENDING +F10 Demo Instrumentation PENDING +``` + +The immediately preceding gate was: + +> **G8C — PASS / CLOSED** + +The central question for Session 14 was therefore not how an exercise becomes authorized, how the protected swap executes, or how settlement and delivery occur. + +Those responsibilities already had normative owners. + +The remaining question was: + +> **What exact authoritative consequence converts a causally proven, executed, settled, and directly delivered O2 exercise into durable fulfillment of the corresponding commitment?** + +--- + +# 3. Responsibility Inherited from F8A–F8C + +The F8D derivation began by reconstructing what the previous three O2 slices had already established. + +## 3.1 F8A — Exercise Authorization + +F8A established Hook-owned transaction-scoped causal context binding the exercise to: + +- service / pool identity; +- commitment identity; +- configured ExerciseRouter; +- authenticated originating exerciser; +- authoritative Beneficiary; +- exact exercise quantity `q`; +- causal lifecycle state. + +The lifecycle was conceptually: + +```text +EMPTY + ↓ +AUTHORIZED + ↓ +EXECUTING + ↓ +EXECUTED + ↓ +consumed / EMPTY +``` + +F8A owned authorization and prospective exercisability. + +It did **not** own fulfillment reduction. + +--- + +## 3.2 F8B — Protected PoolManager Execution + +F8B established the exact protected execution path. + +It required an `AUTHORIZED` context and performed the canonical protected exact-output swap using the bound: + +- pool; +- protected direction; +- quantity `q`; +- supporting boundary; +- ExerciseRouter path. + +The Hook transitioned the causal context through: + +```text +AUTHORIZED + ↓ +EXECUTING + ↓ +EXECUTED +``` + +The authoritative callback evidence proved that the exact protected output quantity had executed. + +F8B did **not** own: + +- settlement; +- Beneficiary delivery; +- commitment fulfillment. + +--- + +## 3.3 F8C — Settlement and Direct Delivery + +F8C began from the proven `EXECUTED` context. + +It: + +- derived the actual input debt from `BalanceDelta`; +- enforced the exerciser's `maxInput`; +- used the bound exerciser as payer; +- settled the exact input debt to PoolManager; +- proved the input-side delta was zero; +- caused PoolManager to transfer the exact protected output `q` directly to the bound Beneficiary; +- proved the output-side delta was zero. + +After F8C: + +```text +protected execution proven +input settlement complete +Beneficiary delivery complete +causal context EXECUTED +Remaining unchanged +``` + +This exposed the exact remaining F8D responsibility. + +--- + +# 4. The F8C Completion Barrier and Why F8D Was Necessary + +A particularly important observation entering F8D was the existing production completion barrier. + +The top-level exercise path could not successfully return while the Hook-owned causal context remained unconsumed. + +Therefore, F8C by itself produced an intentionally incomplete transaction: + +```text +execute + ↓ +settle + ↓ +deliver + ↓ +context still EXECUTED + ↓ +top-level completion barrier fails + ↓ +whole transaction reverts +``` + +This was not treated as an F8C defect. + +It was evidence that fulfillment had not yet occurred. + +F8D needed to complete the causal chain such that: + +```text +execute + ↓ +settle + ↓ +deliver + ↓ +finalize + ↓ +Remaining reduced + ↓ +context consumed + ↓ +completion barrier passes naturally +``` + +The barrier therefore did not need to be weakened, bypassed, or redesigned. + +F8D needed to satisfy it. + +--- + +# 5. Exact F8D Responsibility + +The resulting compact responsibility statement was: + +> **F8D converts one transaction-local, causally proven, executed + settled + directly delivered O2 exercise into exactly one durable fulfillment consequence by verifying actual post-execution backing, reducing authoritative commitment Remaining exactly by the context-bound `q`, and consuming the causal evidence exactly once.** + +This formulation became the anchor for the rest of the derivation. + +--- + +# 6. Authoritative Starting State + +F8D must begin from the Hook-owned causal context in exactly: + +```text +EXECUTED +``` + +It must not accept: + +```text +EMPTY +AUTHORIZED +EXECUTING +``` + +as sufficient evidence. + +`EXECUTED` means that the exact protected PoolManager execution already occurred through the F8B-owned authoritative path. + +F8C then ensures settlement and Beneficiary delivery occur before finalization is invoked in the production router sequence. + +Thus F8D does not independently reconstruct the entire exercise from caller claims. + +It consumes already-established causal evidence. + +--- + +# 7. Caller-Supplied Facts Were Minimized + +A major design question was what information finalization should accept from the caller. + +The preferred conceptual surface was: + +```solidity +finalizeExercise(uint256 commitmentId) +``` + +The reasoning was that finalization should **not** accept independently caller-supplied versions of: + +- `q`; +- Beneficiary; +- service identity; +- pool identity; +- exerciser. + +Those facts already exist in the Hook-owned causal context. + +The supplied `commitmentId` acts only as the requested target and must equal the commitment identity already proven by the context. + +Therefore: + +```text +caller says which commitment it intends to finalize + ↓ +Hook verifies that identity against causal context + ↓ +all economic facts come from authoritative state/context +``` + +This materially reduces substitution surface. + +--- + +# 8. Final Backing Must Use Actual Post-Execution State + +One of the central F8D derivations concerned backing. + +F8A had already checked prospective backing before authorization. + +That was insufficient for finalization. + +By F8D, the protected execution has actually happened. + +Therefore finalization must use **actual post-execution authoritative PoolManager state**. + +The derived variables were: + +```text +q = exact quantity bound in causal context +Rcurrent = current authoritative Remaining +Ocurrent = current authoritative aggregate obligation +Sactual = current post-execution Supporting Capacity +``` + +Then: + +```text +require q <= Rcurrent + +Ofinal = Ocurrent - q + +require Sactual >= Ofinal +``` + +Equality must pass. + +The resulting final backing relation is: + +```text +Sactual >= Ocurrent - q +``` + +This is deliberately different from F8A's prospective backing check. + +--- + +# 9. Why Prospective Backing Could Not Be Reused + +Several alternatives were rejected as insufficient: + +```text +F8A prospective S' +cached pre-execution capacity +router estimate +F8B output proof alone +caller-supplied backing information +``` + +The reason was temporal authority. + +F8A asks: + +> Is this exercise safe to authorize prospectively? + +F8D asks: + +> After the actual protected execution and delivery sequence, is the authoritative system state still sufficiently backed once this exact fulfillment is recognized? + +Those are different questions at different causal positions. + +The final check therefore belongs to current authoritative post-state. + +--- + +# 10. Exact Fulfillment Mutation + +The persistent economic mutation derived for F8D was intentionally minimal: + +```text +Remaining_after = Remaining_before - q +``` + +after all finalization checks pass. + +The original commitment quantity remains unchanged. + +No independent fulfilled amount is required because it is derivable: + +```text +Fulfilled = Original - Remaining +``` + +No separate completion flag is required because completion is derivable: + +```text +Completed ⇔ Remaining == 0 +``` + +No additional aggregate-obligation storage is required because aggregate obligation remains derived from binding Remaining values. + +This preserves the existing fact-and-consequence state model. + +--- + +# 11. Aggregate Obligation Is a Consequence, Not a Second Mutation + +A key semantic distinction was preserved: + +F8D does not perform: + +```text +Remaining -= q +aggregateObligation -= q +``` + +as two independent authoritative mutations. + +Instead: + +```text +Remaining -= q + ↓ +aggregate O is re-derived + ↓ +aggregate O falls by q as a consequence +``` + +This prevents duplicated economic truth. + +The authoritative persistent fact remains commitment Remaining. + +--- + +# 12. Partial and Full Fulfillment + +The same rule handles both cases. + +## Partial + +```text +Rbefore > q + +Rafter = Rbefore - q +Rafter > 0 +``` + +The commitment remains binding. + +A subsequent exercise requires a fresh F8A authorization and fresh causal context. + +## Full + +```text +Rbefore == q + +Rafter = 0 +``` + +The commitment is exhausted. + +No additional completed state is needed. + +This was important for preserving state minimality. + +--- + +# 13. Causal Evidence Must Be Consumed Exactly Once + +Successful finalization must also consume the Hook-owned causal context. + +Conceptually: + +```text +EXECUTED + ↓ +final backing verified + ↓ +Remaining reduced + ↓ +context consumed + ↓ +EMPTY +``` + +The finalization transaction must not durably contain only one of: + +```text +Remaining reduction +context consumption +``` + +Because both occur in the same EVM transaction, any later revert unwinds both. + +This gave F8D exact-once semantics without introducing another persistent replay registry. + +--- + +# 14. Replay Analysis + +After successful finalization: + +```text +context = EMPTY +``` + +Therefore replay cannot reuse the prior proof. + +A second finalization attempt has no valid `EXECUTED` context. + +This provides the exact-once causal boundary. + +The system does not need a separate: + +```text +finalized[exerciseId] +``` + +mapping merely to prevent replay. + +The causal evidence itself is consumable. + +--- + +# 15. Substitution Analysis + +The derivation explicitly examined several substitution threats. + +## Quantity substitution + +Rejected because `q` comes from Hook-owned causal context. + +## Beneficiary substitution + +Rejected because Beneficiary is already bound in context and F8C delivers directly to that Beneficiary. + +## Commitment substitution + +Rejected because the requested commitment identity must equal the context commitment identity. + +## Service / pool substitution + +Rejected because those facts are already causally bound. + +## Exerciser substitution + +Rejected because the authenticated originating exerciser is bound earlier and is not reintroduced by the finalizer. + +The finalization surface therefore does not create a second independent source of exercise facts. + +--- + +# 16. Stale Context and Sequential Exercise + +A successful top-level exercise cannot leave the context active. + +F8D consumes it. + +The existing ExerciseRouter completion barrier independently requires the exercise to finish without lingering causal state. + +Therefore a successful exercise cannot carry stale causal evidence into another successful top-level exercise. + +For a partially fulfilled commitment: + +```text +exercise q1 + ↓ +finalize q1 + ↓ +consume context + ↓ +Remaining = R0 - q1 + ↓ +new top-level exercise + ↓ +fresh F8A authorization +``` + +The second exercise cannot reuse the first exercise's causal proof. + +--- + +# 17. Nested Exercise / Reentrancy Reasoning + +The transaction-scoped causal lifecycle also constrains overlapping exercises. + +A non-empty causal context represents an exercise already in progress. + +A nested or overlapping exercise cannot simply overwrite that context and establish an unrelated authorization. + +This preserved the one-causal-exercise-at-a-time discipline already established by F8A and the ExerciseRouter path. + +F8D therefore did not need to invent another reentrancy mechanism. + +--- + +# 18. Whole-O2 Atomicity + +The complete O2 production path after F8D becomes: + +```text +authorize + ↓ +protected PoolManager execution + ↓ +settle exact input debt + ↓ +direct Beneficiary delivery + ↓ +verify final actual backing + ↓ +Remaining -= q + ↓ +consume causal context + ↓ +top-level completion barrier + ↓ +success +``` + +Any failure during finalization reverts the enclosing transaction. + +Therefore the system cannot durably retain: + +```text +delivery without fulfillment +``` + +or: + +```text +fulfillment without delivery +``` + +for the production O2 path. + +This completed the economic atomicity requirement. + +--- + +# 19. Responsibility-Leakage Gate + +Before implementation, F8D was explicitly checked against neighboring responsibilities. + +## F8A retains ownership of + +- exercise authority; +- eligibility; +- validity window; +- Remaining availability at authorization; +- prospective backing; +- causal-context establishment. + +## F8B retains ownership of + +- exact protected PoolManager swap; +- direction; +- quantity execution; +- execution callback proof; +- transition to `EXECUTED`. + +## F8C retains ownership of + +- actual input debt; +- `maxInput`; +- payer identity; +- PoolManager settlement; +- direct Beneficiary delivery; +- delta closure. + +## F7 retains ownership of + +- O1 commitment admission. + +## F6B retains ownership of + +- ordinary O3 bounded backing enforcement. + +F8D owns none of those behaviors. + +Its responsibility begins only after the causally proven protected execution and ends after exact fulfillment plus context consumption. + +The responsibility-leakage gate passed. + +--- + +# 20. G8D Derivation + +Before Claude implementation, G8D was derived as six independent evidence families. + +## G8D-A — Causal Authority + +Required evidence that: + +1. finalization is Hook-owned; +2. only the authorized active O2 path may finalize; +3. context must be exactly `EXECUTED`; +4. requested commitment must equal context commitment; +5. `q` comes from context; +6. caller cannot substitute Beneficiary, service, pool, exerciser, or quantity; +7. commitment state is reread authoritatively. + +--- + +## G8D-B — Final Economic Backing + +Required: + +```text +q <= current Remaining + +Sactual = actual current post-execution Supporting Capacity + +Ocurrent = current authoritative aggregate obligation + +Ofinal = Ocurrent - q + +Sactual >= Ofinal +``` + +with equality accepted. + +Prospective backing could not substitute for this check. + +--- + +## G8D-C — Exact Fulfillment + +Required: + +```text +Remaining_after = Remaining_before - q +``` + +with: + +- Original unchanged; +- partial fulfillment exact; +- full fulfillment reaching zero; +- completion derived from Remaining; +- no independent completion flag; +- aggregate obligation changing only as a consequence of Remaining. + +--- + +## G8D-D — Exact-Once Consumption + +Required: + +- successful finalization consumes `EXECUTED`; +- no reusable causal context remains; +- replay fails; +- cross-commitment finalization fails; +- stale context cannot survive a successful top-level exercise; +- non-O2 activity cannot manufacture valid finalization evidence. + +--- + +## G8D-E — Atomicity + +Required any finalization failure to unwind: + +- protected execution; +- settlement; +- Beneficiary delivery; +- Remaining mutation; +- context mutation. + +No durable delivery without fulfillment and no durable fulfillment without delivery. + +--- + +## G8D-F — Integration / Regression + +Required evidence for: + +- partial fulfillment; +- full fulfillment; +- equality backing; +- failure paths; +- replay; +- natural satisfaction of the existing production completion barrier; +- preservation of F8A/F8B/F8C behavior; +- existing regression suite; +- fuzz/integration evidence; +- canonical terminal state. + +The canonical A4 terminal state remained: + +```text +S = 15,000 +O = 0 +Remaining = 0 +Beneficiary +50,000 +``` + +--- + +# 21. Claude Prompt and Clean-Rule Review + +A slice-specific Claude implementation prompt was prepared. + +The user then explicitly challenged the prompt against the established clean rule: + +> **CLAUDE.md owns permanent operating behavior.** +> +> **.claude/rules/\* owns permanent Solidity/testing conventions.** +> +> **Session prompts own only slice-specific objective, scope, requirements, prohibitions, file boundaries, gate evidence, and completion boundary.** + +The prompt was reviewed for leakage. + +Generic testing/configuration instructions were reduced or delegated to their permanent owners. + +The resulting Session 14 prompt remained responsible only for: + +- F8D objective; +- F8D semantic boundary; +- required authoritative facts; +- final backing requirement; +- exact fulfillment mutation; +- causal consumption; +- F8D-specific replay/atomicity constraints; +- G8D evidence; +- F8D-specific prohibitions; +- authorized scope; +- completion boundary. + +This preserved the clean separation between permanent repository behavior and slice-local implementation authority. + +--- + +# 22. Claude Implementation Result + +Claude implemented F8D primarily in: + +```text +src/StandbyHook.sol +src/ExerciseRouter.sol +``` + +with corresponding test/harness changes. + +The production ExerciseRouter sequence became conceptually: + +```text +unlock + ↓ +execute protected swap + ↓ +resolve settlement and delivery + ↓ +request Hook finalization + ↓ +existing completion barrier +``` + +The barrier was not weakened. + +F8D instead made the valid production exercise satisfy it naturally. + +--- + +# 23. Independent Review — Hook Finalization + +ChatGPT independently reviewed the actual changed production code rather than accepting Claude's advisory gate assessment. + +The reviewed Hook finalization behavior: + +1. reads the Hook-owned exercise authorization context; +2. requires state `EXECUTED`; +3. requires `msg.sender` to equal the bound ExerciseRouter; +4. requires requested commitment identity to equal the context commitment; +5. rereads current commitment Remaining; +6. requires context `q <= Remaining`; +7. verifies final actual backing; +8. writes: + +```text +Remaining := Remaining - q +``` + +9. consumes the causal context; +10. emits an observational finalization event. + +The event does not create independent economic state. + +--- + +# 24. Independent Review — Final Backing + +The reviewed backing helper derives: + +```text +actualCapacity = current Supporting Capacity + +finalObligation = current aggregate obligation - q +``` + +and rejects: + +```text +actualCapacity < finalObligation +``` + +Thus equality passes. + +This uses actual current state rather than F8A's prospective derivation. + +--- + +# 25. Structural Observation: `_aggregateObligation() - q` + +During independent review, particular attention was given to the implementation's direct subtraction: + +```solidity +_aggregateObligation() - q +``` + +There is no separate explicit: + +```text +q <= aggregate O +``` + +check immediately before this subtraction. + +The review concluded this was not a gate defect because successful finalization first establishes: + +```text +q <= current Remaining +``` + +for the authentic context-bound commitment. + +That still-binding commitment contributes its Remaining to authoritative aggregate obligation. + +Therefore: + +```text +q <= Remaining_of_binding_commitment <= aggregate O +``` + +under the authoritative commitment model. + +Additionally, Solidity checked arithmetic would fail closed if the structural assumption were violated. + +This was preserved as a useful implementation-review observation rather than treated as a reason to duplicate a derived check. + +--- + +# 26. Independent Review — Context Consumption + +The context-consumption implementation clears all transaction-scoped causal fields and returns the lifecycle to `EMPTY`. + +This means successful finalization leaves no reusable exercise proof. + +The existing top-level completion barrier therefore succeeds naturally after valid finalization. + +Replay attempts cannot reuse the prior context. + +This matched the F8D derivation. + +--- + +# 27. Independent Review — Test Architecture + +The F8D test changes included purpose-built harnesses to preserve responsibility boundaries. + +Notably: + +```text +NonFinalizingExerciseRouter +``` + +preserved a production-like exercise path without finalization so prior F8C behavior could still be tested. + +```text +MisdirectedFinalizationRouter +``` + +made finalization failure constructible behind a real exercise path, allowing atomic unwind to be verified. + +This was preferable to weakening production behavior merely to make failure cases testable. + +--- + +# 28. Independent Review — F8D Evidence + +The reviewed tests covered, among other things: + +- partial fulfillment; +- full fulfillment; +- minimal one-unit fulfillment; +- sequential partial exercises; +- exact Remaining reduction; +- canonical A1→A4 sequence; +- equality backing; +- actual post-state backing; +- replay refusal; +- absent causal context; +- wrong finalizer; +- cross-commitment finalization; +- ordinary swaps not causing fulfillment; +- direct token transfer not causing fulfillment; +- refused exercises leaving no residue; +- unpayable exercises leaving no residue; +- misdirected finalization unwinding the complete exercise; +- context consumption; +- fuzzing over admissible exercise quantities; +- sequential remainder behavior. + +Repository verification reported: + +```text +58 suites +539 tests passed +0 failed +0 skipped +``` + +with the CI profile also passing. + +--- + +# 29. Foundry / HookMiner Infrastructure Issue + +During full-suite verification, the deterministic fixture encountered a local Forge gas-budget failure in the pinned Hook address-mining procedure. + +The cause was not Standby protocol execution. + +The fixture's `HookMiner.find` repeatedly hashes Hook creation code while searching for a salt producing the required Uniswap v4 Hook permission bits. + +As the Hook creation code grew through implementation, the deterministic search required more local execution budget. + +A diagnostic run with a larger Forge gas limit allowed the deterministic fixture to complete successfully. + +Claude therefore introduced an elevated local Foundry execution gas limit. + +--- + +# 30. Independent Review of the Gas-Limit Change + +Because this change was adjacent to F8D rather than part of F8D semantics, it received a separate responsibility review. + +The conclusion was: + +> **Infrastructure/configuration change — ACCEPTABLE.** + +The setting affects the local Foundry / Forge execution budget used by tests and local script simulation. + +It does not change: + +- Standby protocol semantics; +- Solidity source behavior; +- generated contract semantics; +- deployed-chain block gas limits; +- deployed contract gas rules; +- F8D responsibility. + +The change was necessary to keep the deterministic regression environment executable as Hook bytecode evolved. + +It was therefore accepted as verification infrastructure rather than responsibility leakage. + +--- + +# 31. Gas-Limit Wording Precision + +A wording issue was identified during that review. + +Calling the setting simply a: + +```text +test gas limit +``` + +was too narrow because Forge execution configuration may also apply to local script simulation. + +The accepted terminology became: + +```text +local Forge execution gas limit +``` + +or equivalently: + +```text +local Foundry execution budget used by Forge tests and local script simulation +``` + +A narrowly scoped Claude follow-up corrected the documentation/comment wording without changing configuration values or behavior. + +A final stale phrase in the Session 14 log was later corrected from: + +```text +test gas limit +``` + +to: + +```text +local Forge execution gas limit +``` + +No verification rerun was required for this documentation-only correction. + +--- + +# 32. Independent G8D Determination + +After independent review of the actual implementation, tests, causal behavior, atomicity, and adjacent infrastructure correction: + +```text +G8D-A — Causal Authority PASS +G8D-B — Final Economic Backing PASS +G8D-C — Exact Fulfillment PASS +G8D-D — Exact-Once Consumption PASS +G8D-E — Atomicity PASS +G8D-F — Integration / Regression PASS +``` + +Therefore: + +> **G8D — PASS / CLOSED** + +and: + +> **F8D — O2 Causal Finalization / Remaining Reduction: COMPLETE** + +This determination was independent of Claude's advisory assessment. + +--- + +# 33. Post-F8D / Pre-GI Engineering Baseline + +After G8D closure, the user raised when gas snapshots and coverage should be captured. + +The conclusion was that the immediate post-F8D checkpoint was particularly valuable because F8D completes the planned O1/O2/O3 production path. + +The sequence chosen was: + +```text +F8D implementation complete + ↓ +independent G8D closure + ↓ +post-F8D / pre-GI engineering baseline + ↓ +project-status update + ↓ +Session 14 retrospective / closeout + ↓ +GI +``` + +The baseline was deliberately captured before GI altered the verification surface. + +--- + +# 34. Separate Engineering Reports + +The user asked whether gas and coverage results should live only in the Claude session log or in separate reports. + +The conclusion was to create: + +```text +docs/reports/gas-snapshot.md +docs/reports/coverage-summary.md +``` + +while retaining only audit chronology in: + +```text +docs/prompts/session-14-log.md +``` + +The distinction is: + +```text +session-14-log.md + = chronological implementation/evidence record + +gas-snapshot.md + = human-readable gas baseline + +coverage-summary.md + = human-readable coverage baseline + +.gas-snapshot + = machine-comparable Foundry gas baseline +``` + +Both Markdown reports explicitly identify themselves as: + +> **Non-normative engineering evidence.** + +This preserved artifact responsibility. + +--- + +# 35. Baseline Prompt and Clean Rule + +The first baseline prompt was reviewed against the same clean rule used for implementation prompts. + +It was intentionally simplified. + +General operating instructions were removed. + +The final prompt owned only: + +- post-F8D / pre-GI measurement objective; +- authorized reports; +- reproducibility evidence; +- requested gas and coverage measurements; +- interpretation boundary; +- prohibitions; +- authorized files; +- Session 14 log update; +- completion report; +- completion boundary. + +It explicitly prohibited: + +- production-code changes; +- test changes; +- harness changes; +- gas optimization; +- coverage-driven test additions; +- configuration changes; +- frozen-artifact changes; +- project-status changes; +- GI work. + +This was treated as a Session 14 evidence follow-up rather than another implementation slice. + +--- + +# 36. Gas Baseline Result + +The post-F8D / pre-GI gas report successfully captured: + +```text +forge snapshot +``` + +with: + +```text +58 suites +539 tests passed +0 failed +0 skipped +``` + +and preserved: + +```text +.gas-snapshot +``` + +as the machine-comparable artifact. + +The baseline correctly recorded that the measured state was the current uncommitted Session 14 F8D working tree rather than falsely attributing the results to the preceding F8C `HEAD`. + +The report also captured deployed contract size. + +At this checkpoint: + +```text +StandbyHook runtime size = 21,996 bytes +remaining EIP-170 margin = 2,580 bytes +``` + +This was recorded as engineering evidence, not as an optimization requirement. + +--- + +# 37. `--gas-report` Measurement Limitation + +A supplementary attempt using: + +```text +forge test --gas-report +``` + +did not provide a valid whole-path O2 measurement. + +The isolated execution behavior used for gas attribution caused transaction-scoped EIP-1153 causal evidence to disappear between calls that logically belong to one exercise transaction. + +The resulting failures were therefore measurement artifacts rather than Standby defects. + +The invalid per-function gas tables were not used. + +No production or test changes were made to accommodate the measurement tool. + +This was considered the correct response: preserve the limitation rather than distort the implementation to satisfy instrumentation. + +--- + +# 38. Why `.gas-snapshot` Is Version-Controlled + +The user explicitly asked whether `.gas-snapshot` should be committed or ignored. + +The conclusion was: + +> **Commit `.gas-snapshot`.** + +It is not being treated as disposable generated output. + +It is the machine-comparable representation of the post-F8D / pre-GI baseline. + +The repository therefore preserves: + +```text +.gas-snapshot +docs/reports/gas-snapshot.md +docs/reports/coverage-summary.md +``` + +The snapshot creates no automatic gas-regression gate. + +Future changes may be compared against it without treating every delta as a defect. + +--- + +# 39. Coverage Baseline Result + +Coverage completed successfully under instrumentation: + +```text +58 suites +539 tests passed +0 failed +0 skipped +``` + +Repository-wide Foundry results were recorded separately by dimension rather than collapsed into a single percentage. + +The more relevant protocol-core aggregate was: + +```text +Lines 99.42% +Statements 98.33% +Branches 91.35% +Functions 100.00% +``` + +These figures were explicitly not treated as semantic proof. + +The coverage report preserved the distinction: + +```text +coverage measurement + ≠ semantic correctness + +high coverage + ≠ invariant proof + +uncovered code + ≠ automatic protocol defect +``` + +This distinction became an important process result. + +--- + +# 40. Coverage as Diagnostic Evidence, Not a Gate + +The user and ChatGPT explicitly rejected the idea that coverage percentage should become a new verification gate merely because it had now been measured. + +Coverage was instead used diagnostically to expose potentially interesting stateful verification surfaces. + +The report identified several uncovered or partially covered production areas. + +Importantly, it did not automatically classify those paths as defects. + +This preserved the hierarchy: + +```text +frozen invariant + ↓ +derived verification obligation + ↓ +test evidence +``` + +rather than reversing it into: + +```text +coverage hole + ↓ +invent new protocol requirement +``` + +--- + +# 41. Potential GI Inputs Surfaced by Coverage + +The coverage baseline identified potential areas for later GI examination, including: + +1. untrusted-perimeter liquidity removal; +2. nested exercise attribution; +3. causal-position refusal on an authorization read surface; +4. extreme tick-bound clamping in prospective swap derivation; +5. the unresolved exercise-delta fail-closed guard. + +The report explicitly classified these as: + +> **potential GI inputs, not established GI requirements** + +and deferred their interpretation to GI's own derivation against the frozen invariants and testing strategy. + +This was accepted as the correct responsibility boundary. + +No GI derivation was performed during Session 14. + +--- + +# 42. Transition Question: Is Production Implementation Now Complete? + +Immediately before this retrospective, the user asked: + +> If GI is the full stateful invariant gate, does that mean it would only include tests, and our Hook contract is basically completed pending any significant issues found during the testing? + +The answer was essentially yes, with an important qualification. + +F0–F8D now complete the planned production behavior for the canonical Standby realization. + +Therefore the intended transition is: + +```text +F0–F8D +production behavior implementation + ↓ +G8D PASS / CLOSED + ↓ +planned production semantics complete + ↓ +GI +full stateful invariant verification +``` + +GI is not intended to be another feature-development slice. + +Its planned work should primarily be verification-side infrastructure such as: + +- stateful invariant handlers; +- adversarial action generation; +- ghost/accounting state where justified; +- bounds/selectors; +- invariant assertions; +- sequence-sensitive verification. + +--- + +# 43. GI Does Not Own New Protocol Semantics + +A key responsibility principle emerged from this transition: + +> **GI is a verification gate, not a new semantic owner.** + +The default expectation entering GI is therefore: + +```text +production code is feature-complete +``` + +not: + +```text +GI is where remaining Hook behavior will be designed +``` + +GI should attempt to break the completed realization against the already-frozen invariants. + +It should not introduce new production behavior merely because an invariant test would be easier to write that way. + +--- + +# 44. What Happens If GI Finds a Real Defect? + +Production code is not declared immune from correction. + +If GI discovers a genuine counterexample, the appropriate sequence is: + +```text +GI discovers counterexample + ↓ +identify violated frozen invariant / responsibility + ↓ +trace defect to existing normative owner + ↓ +correct that implementation + ↓ +rerun affected gate/regression evidence + ↓ +continue GI +``` + +The correction belongs to the responsibility that was implemented incorrectly. + +GI itself does not become the semantic owner of the fix. + +This distinction prevents stateful testing from becoming an uncontrolled redesign phase. + +--- + +# 45. Methodology Observation — Implementation Convergence + +F8D provided another strong example of the previously frozen Implementation Convergence Principle: + +> **Implementation Convergence = Semantic Completeness + Responsibility Clarity + Bounded Implementation Discretion + Verification-Gated Dependencies.** + +By the time Claude received F8D: + +- the authoritative starting state was known; +- causal facts had one owner; +- final backing had one authoritative derivation; +- persistent mutation was exactly defined; +- context consumption was defined; +- neighboring responsibilities were excluded; +- G8D was already derived. + +As a result, the implementation required comparatively little semantic discretion. + +The independent review found implementation choices that closely matched the pre-implementation derivation. + +--- + +# 46. Methodology Observation — Causal Evidence as Consumable Authority + +F8D reinforced a useful architectural pattern: + +> Transaction-scoped causal evidence can serve not merely as authorization context but as **consumable proof authority** for a later consequence in the same economic transaction. + +The lifecycle: + +```text +AUTHORIZED + ↓ +EXECUTING + ↓ +EXECUTED + ↓ +CONSUMED +``` + +allowed the system to prove: + +- which exercise was authorized; +- which protected execution occurred; +- which fulfillment consequence may occur; +- that the proof cannot be reused. + +This avoided introducing an independent persistent replay ledger for exercise finalization. + +--- + +# 47. Methodology Observation — Actual-State Finalization + +F8D also reinforced a temporal distinction in authoritative derivation: + +```text +prospective safety + ≠ +post-execution final safety +``` + +A prospective authorization proof does not eliminate the need for actual-state verification when durable economic consequences are committed after execution. + +The correct authoritative source depends on the causal position at which the question is being asked. + +This was central to choosing `Sactual` rather than cached or prospective capacity. + +--- + +# 48. Methodology Observation — Verification Infrastructure Is Not Protocol Semantics + +The HookMiner gas-limit issue provided a useful process example. + +A verification environment may require configuration changes as the implementation evolves. + +Such a change should be reviewed for responsibility leakage rather than automatically accepted or rejected. + +The relevant question is: + +> Does this change alter the protocol realization, or does it merely allow the intended verification environment to execute the unchanged realization? + +For the local Forge execution gas limit, the answer was the latter. + +The change was therefore acceptable infrastructure. + +--- + +# 49. Methodology Observation — Measurement Is Not Verification + +The gas and coverage baseline produced another useful distinction: + +```text +measurement + ≠ +requirement + +coverage + ≠ +correctness + +gas delta + ≠ +regression + +high coverage + ≠ +invariant proof +``` + +Engineering measurements are useful because they expose characteristics and potential investigation targets. + +They should not silently acquire normative authority. + +This is why the baseline reports were explicitly classified as non-normative engineering evidence. + +--- + +# 50. Methodology Observation — Coverage Should Feed Derivation, Not Replace It + +The post-F8D coverage report surfaced several paths that may be interesting during GI. + +The correct process is: + +```text +coverage observation + ↓ +GI responsibility derivation + ↓ +compare against frozen invariants/testing strategy + ↓ +determine whether stateful evidence is actually required +``` + +not: + +```text +uncovered branch + ↓ +automatically add invariant test +``` + +This preserves specification-first verification. + +--- + +# 51. Methodology Observation — Verification Gates Need Not Own Implementation + +The transition into GI sharpens another useful distinction. + +Earlier gates corresponded closely to implementation slices. + +GI is different. + +Its purpose is to verify the integrated completed realization through adversarial state sequences. + +Therefore a verification gate can legitimately own: + +```text +evidence generation +counterexample discovery +stateful adversarial exploration +``` + +without owning new protocol semantics. + +This is an important boundary for the next session. + +--- + +# 52. Final Session 14 State + +At Session 14 closeout: + +```text +F8D — O2 Causal Finalization / Remaining Reduction +STATUS: COMPLETE + +G8D +STATUS: PASS / CLOSED +``` + +The production realization now contains the complete planned O1/O2/O3 path through durable O2 fulfillment. + +The post-F8D / pre-GI engineering baseline has been captured: + +```text +Gas baseline CAPTURED +Coverage baseline CAPTURED +.gas-snapshot PRESERVED +``` + +The project status has been advanced to: + +```text +GI — Full Stateful Invariant Verification +next authorized implementation slice / current blocker +``` + +GI has **not** been started. + +--- + +# 53. Handoff Principle for GI + +The next ChatGPT session should begin from the following premise: + +> **F0–F8D complete the planned production semantics of the canonical Standby realization. GI is a full stateful invariant verification gate, not a new production-semantic owner. Its responsibility must be derived from the frozen invariants and testing strategy before any invariant implementation prompt is given to Claude.** + +The next session should therefore first derive: + +- the exact GI verification responsibility; +- which frozen invariants require stateful evidence; +- which actors/actions belong in the state machine explored by handlers; +- what authoritative state and ghost state may be observed; +- which action sequences must be adversarially generated; +- what must remain outside GI; +- how coverage-baseline observations should or should not influence GI; +- the exact GI gate. + +Only after that derivation is accepted should Claude receive the GI implementation prompt. + +--- + +# 54. Session 14 Final Determination + +Session 14 closes with: + +```text +F8D implementation COMPLETE +G8D independent review PASS / CLOSED +O2 causal finalization COMPLETE +Remaining reduction AUTHORITATIVE +causal proof consumption EXACT-ONCE +whole-O2 atomicity VERIFIED +post-F8D gas baseline CAPTURED +post-F8D coverage baseline CAPTURED +planned production semantics COMPLETE +GI NEXT / NOT STARTED +``` + +The project has therefore transitioned from **planned production-path implementation** to **integrated adversarial stateful verification**. diff --git a/docs/prompts/session-14-f8d-02-causal-finalization.md b/docs/prompts/session-14-f8d-02-causal-finalization.md new file mode 100644 index 0000000..42550db --- /dev/null +++ b/docs/prompts/session-14-f8d-02-causal-finalization.md @@ -0,0 +1,996 @@ +# Standby — Session 14 + +## F8D — O2 Causal Finalization / Remaining Entitlement Reduction + +You are implementing the next authorized Standby Solidity slice: + +> **F8D — O2 Causal Finalization / Remaining Entitlement Reduction** + +The preceding implementation slices are complete and independently gate-closed: + +```text +F0 — Foundation COMPLETE +F1 — Deterministic Economic Fixture COMPLETE +F2 — Eligibility Registry COMPLETE +F3 — StandbyHook Trust + PES Configuration COMPLETE +F4 — Commitment Storage / Bounded References COMPLETE +F5 — Authoritative Derivation Kernel COMPLETE +F6A — Preliminary O3 Enforcement COMPLETE +F7 — O1 Commitment Admission COMPLETE +F6B — O3 Bounded Enforcement COMPLETE +F8A — Exercise Authorization COMPLETE +F8B — Protected PoolManager Execution COMPLETE +F8C — Authoritative Settlement / Direct Delivery COMPLETE +F8D — O2 Causal Finalization / Remaining Reduction CURRENT +GI — Full Stateful Invariant Verification NOT AUTHORIZED +F9 — Canonical Acceptance NOT AUTHORIZED +F10 — Demo Instrumentation NOT AUTHORIZED +``` + +The last closed gate is: + +> **G8C — PASS / CLOSED** + +F8D is the only authorized implementation slice. + +--- + +# 1. Clean Rule / Authority Boundary + +Apply the established ownership rule: + +> **`CLAUDE.md` owns permanent operating behavior.** +> +> **`.claude/rules/*` owns permanent Solidity/testing conventions.** +> +> **This session prompt owns only F8D-specific objective, scope, requirements, prohibitions, file boundaries, gate evidence, and completion boundary.** + +Where permanent operating, Solidity, or testing behavior is already defined in those artifacts, follow it rather than restating or modifying it here. + +--- + +# 2. Working Model + +Use the established division of responsibility. + +## ChatGPT + +ChatGPT is the normative derivation and independent-review partner. + +Before Claude implementation, ChatGPT has: + +1. reconstructed the exact F8D responsibility from the frozen artifacts and completed implementation slices; +2. identified the authoritative causal and current-state facts F8D must consume; +3. derived the exact post-execution backing requirement; +4. derived the Remaining Entitlement mutation boundary; +5. derived the aggregate-obligation consequence; +6. derived the exact causal-context consumption rule; +7. analyzed replay, substitution, stale-context, nested-call, and relevant reentrancy threats; +8. performed the responsibility-leakage gate; +9. derived G8D before implementation; +10. obtained user acceptance of the F8D boundary and G8D. + +After Claude implementation, ChatGPT will: + +11. independently review the actual changed production code and tests; +12. verify implementation against G8D; +13. identify any semantic leakage or regression; +14. close G8D only if independently justified; +15. authorize the status-only project-status update only after gate closure. + +Claude's completion report and gate assessment are advisory only. + +## Claude + +Claude is the implementation partner. + +For F8D: + +1. inspect the current production implementation and tests; +2. implement only the accepted F8D responsibility; +3. preserve already-gated F0–F8C semantics; +4. add the minimum tests required to provide G8D evidence; +5. update the Session 14 implementation log; +6. run the repository-standard verification required by the permanent instructions; +7. stop at the F8D completion boundary. + +Claude does not independently redefine F8D semantics or close G8D. + +--- + +# 3. Authoritative Sources + +Before modifying production code, inspect the relevant frozen artifacts: + +```text +docs/context.md +docs/economic-agreement.md +docs/mechanism.md +docs/spec.md +docs/architecture.md +docs/state-machine.md +docs/invariants.md +docs/testing-strategy.md +docs/uniswap-v4-realization.md +docs/implementation-plan.md +``` + +Inspect: + +```text +docs/project-status.md +``` + +for current status only. + +For already completed implementation slices, the current production code and tests are implementation truth. + +Before implementing F8D, inspect the current F8A, F8B, and F8C production implementation and tests because they define the already-gated causal and execution surface that F8D must consume. + +If a material discrepancy exists between the frozen artifacts and current implementation, surface it rather than silently redefining protocol semantics. + +--- + +# 4. F8D Objective + +The accepted F8D responsibility is: + +> **F8D converts one transaction-local, causally proven, executed + settled + directly delivered O2 exercise into exactly one durable fulfillment consequence by verifying actual post-execution backing, reducing the authoritative commitment Remaining Entitlement by exactly the context-bound `q`, and consuming that causal evidence exactly once.** + +F8D does not establish: + +- exercise authorization; +- protected swap execution; +- exact protected output; +- exerciser input settlement; +- Beneficiary delivery. + +Those are already owned by F8A, F8B, and F8C. + +F8D makes the already-proven O2 transaction count as durable fulfillment. + +Conceptually: + +```text +F8A +authorization + causal binding + ↓ +F8B +exact protected PoolManager execution + ↓ +F8C +authoritative settlement + direct delivery + ↓ +F8D +actual post-state backing + ↓ +Remaining -= q + ↓ +causal evidence consumed + ↓ +durable fulfillment +``` + +--- + +# 5. Required F8D Starting State + +F8D begins only from the exact active Hook-owned causal lifecycle state: + +```text +EXECUTED +``` + +The F8A/F8B/F8C causal context already binds authoritative facts including: + +- service / pool identity; +- commitment identity; +- configured ExerciseRouter; +- authenticated originating exerciser; +- authoritative Beneficiary; +- exact authorized exercise quantity `q`; +- causal lifecycle state. + +At F8D entry: + +```text +execution proven yes +input settlement complete +Beneficiary delivery complete +Remaining unchanged +Original Entitlement unchanged +context EXECUTED +durable fulfillment not yet established +``` + +F8D must consume these authoritative causal facts rather than allow caller substitution. + +--- + +# 6. Finalization Boundary + +Finalization is Hook-owned. + +A conceptual interface is: + +```solidity +finalizeExercise(uint256 commitmentId) +``` + +Use the actual repository architecture when determining the concrete implementation. + +Prefer not to pass any semantic fact that already exists authoritatively in Hook-owned causal context. + +Do not require caller-supplied: + +- `q`; +- Beneficiary; +- exerciser; +- service identity; +- pool identity; +- execution amount; +- settlement amount. + +If `commitmentId` is supplied, it is only the requested finalization target and must exactly match the Hook-bound causal commitment identity. + +The active finalization path must remain constrained to the configured ExerciseRouter / active O2 causal path. + +F8D must not create a generally usable public fulfillment transition. + +--- + +# 7. Authoritative Fact Ownership + +Preserve the distinction between causally bound facts and current economic facts. + +## Causally bound facts + +Recover from the active Hook context: + +```text +commitmentId +q +service / pool identity where required +originating exerciser where required +Beneficiary where required +``` + +Do not accept caller substitution for these facts. + +## Current authoritative economic facts + +Reload or rederive at finalization: + +```text +current Remaining +current aggregate O +actual post-execution PoolManager state +actual Supporting Capacity S +``` + +F8D requires current authoritative state where final economic safety depends on the post-execution result. + +--- + +# 8. Authoritative Final-Backing Derivation + +The finality derivation is: + +```text +q = exact exercise quantity from Hook causal context +Rcurrent = current authoritative Remaining +Ocurrent = current authoritative Aggregate Capacity Obligation +Sactual = Supporting Capacity from actual post-execution PoolManager state +``` + +Require: + +```text +q <= Rcurrent +``` + +Then derive: + +```text +Ofinal = Ocurrent - q +``` + +and require: + +```text +Sactual >= Ofinal +``` + +Equality must pass. + +Only after all final checks succeed may: + +```text +Rnew = Rcurrent - q +``` + +be persisted. + +--- + +# 9. Actual Post-Execution Supporting Capacity + +F8D must use the existing Hook-owned authoritative derivation layer. + +`Sactual` must reflect the actual current post-execution PoolManager state. + +Do not substitute: + +- F8A prospective `S′`; +- cached pre-execution capacity; +- Router estimates; +- F8B output proof; +- a new duplicate Supporting Capacity formula. + +The semantic distinction is: + +```text +F8A: +Would this proposed exercise be prospectively safe? + +F8D: +After the exact exercise actually occurred, +is the resulting authoritative state safe after +releasing exactly q of obligation? +``` + +Preserve production derivation singularity. + +--- + +# 10. Aggregate Obligation Consequence + +Reload or rederive: + +```text +Ocurrent +``` + +from authoritative current commitment state. + +Before F8D persists fulfillment, the delivered `q` still contributes to the current obligation because Remaining has not yet been reduced. + +Therefore: + +```text +Ofinal = Ocurrent - q +``` + +After: + +```text +Remaining := Remaining - q +``` + +the normal authoritative aggregate-obligation derivation should naturally reflect the same obligation release. + +Do not introduce an independently stored aggregate-obligation decrement unless the existing frozen model explicitly requires one. + +Avoid duplicate authoritative economic state. + +--- + +# 11. Persistent Fulfillment Consequence + +F8D owns exactly one persistent economic mutation: + +```text +newRemaining = oldRemaining - q +``` + +This mutation occurs only after all final checks pass. + +Preserve: + +```text +Original Entitlement = unchanged +``` + +Do not add an independent: + +```text +fulfilled +completed +settled +delivered +expired +``` + +or equivalent lifecycle flag. + +Completion is derived from: + +```text +Remaining == 0 +``` + +Partial fulfillment must produce the exact remaining entitlement. + +Exact exhaustion must produce zero Remaining. + +Historical commitment identity and immutable facts remain preserved. + +Reference membership does not need to be synchronously removed merely because Remaining becomes zero unless already required by the frozen/current model. + +--- + +# 12. Causal Context Consumption + +Successful F8D finalization consumes the active `EXECUTED` causal evidence exactly once. + +Conceptually: + +```text +EXECUTED + ↓ successful F8D +EMPTY / consumed +``` + +One active executed context may cause at most: + +```text +one Remaining reduction of exactly q +``` + +After a successfully completed top-level exercise, no reusable O2 causal evidence may remain. + +Do not introduce a separate persistent `FINALIZED` or success flag unless unavoidable under the existing architecture and clearly reported. + +Successful consumption of the existing causal context is the preferred completion evidence. + +--- + +# 13. Required Finalization Sequence + +Preserve the following semantic dependency: + +```text +require active EXECUTED context + ↓ +authenticate permitted finalization path + ↓ +require matching commitment identity + ↓ +recover q from causal context + ↓ +reload authoritative commitment + ↓ +require q <= current Remaining + ↓ +derive actual post-execution S + ↓ +derive current aggregate O + ↓ +derive Ofinal = Ocurrent - q + ↓ +require Sactual >= Ofinal + ↓ +Remaining -= q + ↓ +consume causal context exactly once + ↓ +success +``` + +Concrete internal code ordering may follow the existing implementation architecture, but observable semantics must preserve these dependencies. + +No persistent fulfillment consequence may survive failed finalization. + +--- + +# 14. Economic Atomicity + +The complete O2 transaction remains economically atomic. + +If F8D finalization fails for any reason, including final-backing failure, the full transaction must revert. + +No durable effects may survive from: + +- protected PoolManager execution; +- exerciser input settlement; +- Beneficiary delivery; +- Remaining mutation; +- causal-context mutation. + +Required property: + +> **No durable state may exist where delivery survives but Standby finalization fails, or entitlement reduction survives without delivery.** + +Do not introduce asynchronous delivery/finalization semantics. + +--- + +# 15. Production Completion Barrier + +Preserve the existing production top-level completion barrier established through F8C. + +Before F8D: + +```text +R4 settlement + delivery succeeds internally + ↓ +context remains EXECUTED + ↓ +top-level completion barrier fails + ↓ +entire transaction reverts +``` + +After successful F8D: + +```text +R4 settlement + delivery + ↓ +F8D final backing + ↓ +Remaining -= q + ↓ +context consumed + ↓ +existing completion barrier passes naturally + ↓ +top-level exercise succeeds +``` + +Do not weaken, remove, or bypass the existing completion barrier. + +Prefer successful causal-context consumption to satisfy that barrier naturally. + +Do not introduce an independent F8D success boolean unless unavoidable and explicitly reported. + +--- + +# 16. Replay, Substitution, and Stale-Evidence Requirements + +F8D must reject or make impossible the following. + +## Replay + +A consumed context cannot reduce Remaining again. + +## Cross-commitment substitution + +If a commitment ID is supplied: + +```text +suppliedCommitmentId == context.commitmentId +``` + +must hold. + +## Quantity substitution + +Caller must not choose `q`. + +Use: + +```text +q = context.q +``` + +## Beneficiary substitution + +F8D performs no Beneficiary selection or delivery. + +Caller cannot replace the Beneficiary associated with the causally proven exercise. + +## Service / pool substitution + +Use the authoritative configured service and Hook-owned causal facts. + +Do not accept arbitrary service or pool substitution. + +## Stale causal evidence + +A successfully completed O2 transaction must leave no reusable `EXECUTED` context. + +## Non-O2 fulfillment + +The following alone must never reduce Remaining: + +- ordinary swaps; +- liquidity actions; +- direct token transfers; +- settlement-only activity; +- delivery-only activity; +- failed O2 attempts; +- expiry alone; +- eligibility changes alone. + +Durable fulfillment requires the exact causally attributable O2 path. + +--- + +# 17. Nested-Call / Reentrancy Requirements + +Preserve the causal exclusion properties already established by the F8A–F8C lifecycle. + +Verify that F8D does not permit unsafe: + +- nested exercise; +- nested authorization; +- nested finalization; +- callback reentrancy; +- replacement of active context; +- reuse of one `EXECUTED` context; +- multiple Remaining reductions from one execution; +- finalization outside the valid active ExerciseRouter / PoolManager causal operation where the current architecture requires that boundary. + +Do not create an independent parallel lifecycle mechanism when the existing Hook-owned causal lifecycle already owns this responsibility. + +--- + +# 18. Responsibility Leakage / Out of Scope + +F8D must not absorb responsibilities from earlier slices. + +## F8A remains owner of + +- exercise authority; +- originating exerciser authentication; +- Beneficiary eligibility; +- validity / time admission; +- authorization-time `q <= Remaining`; +- prospective backing admission; +- ExerciseRouter authorization semantics. + +## F8B remains owner of + +- protected-operation identity; +- canonical exact-output swap construction; +- protected PoolManager execution; +- exact protected-output proof. + +## F8C remains owner of + +- actual input derivation; +- input-side sign handling; +- `maxInput`; +- payer identity; +- PoolManager input settlement; +- direct Beneficiary delivery; +- PoolManager delta closure. + +## F7 remains owner of + +- commitment admission. + +## F6B remains owner of + +- ordinary O3 bounded enforcement. + +F8D consumes authoritative results from those responsibilities rather than revalidating or redefining them. + +Also out of scope: + +- GI; +- F9; +- F10; +- frontend/demo work; +- unrelated refactoring; +- unrelated documentation changes; +- redesign of already-gated protocol semantics. + +--- + +# 19. G8D — F8D Gate + +Implementation and tests must provide evidence for all six sub-gates. + +Claude may give an advisory assessment but does not close G8D. + +## G8D-A — Causal Authority + +Prove: + +1. finalization is Hook-owned; +2. finalization is reachable only through the authorized active O2 path; +3. exact `EXECUTED` context is required; +4. commitment identity is exact; +5. `q` is sourced from Hook-owned causal context; +6. caller cannot substitute `q`, Beneficiary, service, pool, or exerciser; +7. authoritative commitment state is re-read. + +## G8D-B — Final Economic Backing + +Prove: + +```text +q <= current Remaining +``` + +Prove that: + +```text +Sactual +``` + +is derived from actual post-execution authoritative PoolManager state. + +Prove that: + +```text +Ocurrent +``` + +is the authoritative current aggregate obligation. + +Derive: + +```text +Ofinal = Ocurrent - q +``` + +and require: + +```text +Sactual >= Ofinal +``` + +Equality must pass. + +Authorization-time prospective capacity must not substitute for actual final capacity. + +## G8D-C — Exact Fulfillment Consequence + +On successful finalization prove: + +```text +Remaining_after = Remaining_before - q +``` + +Also prove: + +- no Remaining mutation occurs before final checks pass; +- Original Entitlement remains unchanged; +- partial fulfillment produces the exact remainder; +- exact exhaustion produces zero; +- completion is derived from `Remaining == 0`; +- no independent fulfillment/completion flag is introduced; +- aggregate O changes only through the authoritative commitment/Remaining derivation. + +## G8D-D — Exact-Once Causal Consumption + +Prove: + +- successful finalization consumes `EXECUTED` exactly once; +- one causal context causes at most one Remaining reduction; +- successful top-level O2 leaves no reusable causal evidence; +- replayed finalization fails; +- cross-commitment substitution fails; +- quantity substitution is impossible; +- stale context cannot fulfill; +- ordinary swaps cannot fulfill; +- direct transfers cannot fulfill; +- settlement alone cannot fulfill; +- delivery alone cannot fulfill. + +## G8D-E — Economic Atomicity + +Prove that F8D failure, especially final-backing failure, unwinds the complete O2 transaction including: + +- protected AMM execution; +- input settlement; +- Beneficiary delivery; +- Remaining mutation; +- causal-context effects. + +There must be: + +```text +no durable delivery without fulfillment +``` + +and: + +```text +no durable fulfillment without delivery +``` + +## G8D-F — Integration / Regression + +Prove through the appropriate production paths: + +- partial fulfillment; +- full fulfillment; +- final-backing equality; +- final-backing failure; +- replay rejection; +- production top-level `exercise()` succeeds only after successful F8D finalization; +- the existing completion barrier passes naturally after context consumption; +- F8A semantics remain preserved; +- F8B semantics remain preserved; +- F8C semantics remain preserved; +- existing F0–F8C behavior remains passing; +- meaningful boundary/fuzz evidence exists; +- authoritative post-execution PoolManager state is used where required. + +Also preserve the canonical terminal acceptance relationship from the frozen implementation plan, including the applicable A4 terminal state: + +```text +S = 15,000 +O = 0 +Remaining = 0 +Beneficiary increase = 50,000 +``` + +where that scenario belongs to the existing canonical fixture. + +--- + +# 20. F8D Gate Evidence Requirements + +Use the testing architecture and conventions already owned by `.claude/rules/*`. + +For G8D specifically, ensure the evidence demonstrates the F8D semantic boundaries above. + +Relevant cases include: + +```text +q = 1 +q = Remaining +0 < q < Remaining +Sactual = Ofinal +Sactual < Ofinal +multiple sequential partial exercises +``` + +where those cases are valid under the existing fixture and architecture. + +Production-path evidence for successful finalization and economic atomicity must use the actual production components required by the existing test strategy. + +Harness-only seeded state is not sufficient evidence for the production causal transition where real PoolManager state is semantically required. + +Do not weaken production semantics to simplify testing. + +--- + +# 21. F8D Scope / Implementation Prohibitions + +Implement the minimum conforming F8D change. + +Do not: + +- duplicate authoritative Supporting Capacity derivation; +- duplicate authoritative aggregate-obligation derivation; +- introduce redundant economic state; +- introduce an independent fulfillment lifecycle flag; +- introduce caller-controlled economic truth; +- broaden finalization authority; +- bypass the Hook-owned causal lifecycle; +- weaken the existing production completion barrier; +- redesign F8A, F8B, or F8C; +- refactor unrelated production code; +- begin later implementation slices. + +Reuse existing authoritative F5/F7/F8A/F8B/F8C state and derivation surfaces where responsibility ownership permits. + +--- + +# 22. Verification Evidence + +Run the repository-standard verification required by: + +```text +CLAUDE.md +.claude/rules/* +``` + +In addition, run whatever focused F8D verification is required to establish G8D-A through G8D-F. + +The completion report must state the actual commands executed and their actual results. + +Do not claim verification that was not executed. + +--- + +# 23. Session Evidence / Post-Project Retrospective + +Maintain: + +```text +docs/prompts/session-14-log.md +``` + +as the Claude implementation-process record for F8D. + +Preserve materially relevant: + +- implementation decisions; +- files changed; +- test decisions; +- ambiguities encountered; +- corrections; +- verification commands and results; +- deviations, if any; +- advisory G8D assessment. + +This log is non-normative. + +Do not update: + +```text +docs/project-status.md +``` + +during F8D implementation. + +The status-only update will occur only after independent ChatGPT review and explicit G8D closure. + +A separate ChatGPT retrospective record will be produced after independent review: + +```text +docs/prompts/retrospective/session-14-chatgpt-record.md +``` + +Claude does not create or modify that file. + +--- + +# 24. Completion Report + +When F8D implementation is complete, report: + +1. files changed; +2. production behavior implemented; +3. tests added or changed; +4. verification commands and actual results; +5. contract-size result where applicable; +6. advisory G8D-A assessment; +7. advisory G8D-B assessment; +8. advisory G8D-C assessment; +9. advisory G8D-D assessment; +10. advisory G8D-E assessment; +11. advisory G8D-F assessment; +12. unresolved concerns, deviations, or discrepancies. + +Do not declare G8D closed. + +Use wording equivalent to: + +```text +Claude advisory assessment: G8D requirements appear satisfied. + +Final G8D closure requires independent ChatGPT review of the actual +changed production code and tests. +``` + +--- + +# 25. Accepted F8D Semantic Statement + +Before editing, preserve this accepted responsibility: + +> **Successful O2 delivery remains transaction-local evidence until F8D proves the actual post-execution state remains backed after releasing exactly the delivered obligation. Only then may the Hook reduce Remaining by the context-bound `q` and consume the causal evidence, making fulfillment durable.** + +The intended implementation shape is: + +```text +Final Backing + + +Exact Persistent Consequence + + +Exact-Once Causal Consumption +``` + +Implement that responsibility and nothing broader. + +--- + +# 26. Completion Boundary + +The F8D completion boundary is: + +```text +F8D production implementation complete +F8D gate evidence complete +repository verification complete +session-14-log.md updated +``` + +Stop at the F8D completion boundary. diff --git a/docs/prompts/session-14-log.md b/docs/prompts/session-14-log.md new file mode 100644 index 0000000..7f766b7 --- /dev/null +++ b/docs/prompts/session-14-log.md @@ -0,0 +1,484 @@ +# Session 14 — Material Prompt Log + +Initiating prompt: `docs/prompts/session-14-f8d-02-causal-finalization.md` + +This file is an implementation-process audit artifact. It is not a normative +specification and does not define protocol semantics. + +--- + +## Material Follow-Up Prompts + +### Prompt 1 — Correct the descriptive wording of the Foundry `gas_limit` configuration + +**Instruction.** The `gas_limit` configuration introduced in this session has been independently reviewed +and accepted, and neither its value nor any behavior may change. Its wording is too narrow: it is described +as a **test-execution budget only**, when it is in fact a **local Foundry / Forge execution budget used by +Forge tests and local script simulation**. Correct the comment associated with the existing `gas_limit` +configuration in `foundry.toml` and the corresponding explanatory wording in `docs/setup.md` so that the +setting is no longer described as exclusively a test-execution setting, and so that it is explicit that it +does not change Solidity compilation, generated contract bytecode, the EVM target, Standby protocol +semantics, deployed-chain block gas limits, or deployed Standby contract execution limits. Preserve the +existing explanation of why the elevated local execution budget is required by the pinned `HookMiner.find` +search. Add no broader Foundry documentation or unrelated commentary. Documentation and comments only — no +Solidity production code, tests, harnesses, `gas_limit` values, compiler/EVM/optimizer configuration, frozen +normative artifacts, `docs/project-status.md`, F8D behavior, or downstream slice may be modified, and no new +implementation or design work is authorized. + +**Consequence.** Wording only was corrected, in three files. + +- `foundry.toml`: the explanatory comment above `gas_limit` in `[profile.default]` now reads "Local Forge + execution gas budget; used by tests and local script simulation", followed by the explicit non-effects + (Solidity compilation, generated bytecode, EVM target, deployed-chain gas limits, deployed Standby + contract execution limits, Standby protocol semantics). The `[profile.ci]` comment was given the same + characterization ahead of its existing cross-reference. The `HookMiner.find` explanation beneath it is + unchanged. +- `docs/setup.md`: the validated-baseline bullet was retitled from "Test gas limit" to "Local Forge + execution gas limit"; the section heading was retitled to match; and its opening sentence now states that + `gas_limit` is a local Foundry execution budget used by Forge tests and local script simulation, and is + not a compiler, bytecode, EVM-target, deployed-contract, protocol, or deployed-chain gas-limit setting. + Both the `HookMiner.find` explanation and the paragraph recording that Standby's bounded execution is + enforced by explicit protocol bounds and reverts are unchanged. +- `docs/prompts/session-14-log.md`: this entry, and a clarifying note added to Decision 8. + +The configured value `gas_limit = 10000000000` is unchanged in both profiles. No Solidity, test, harness, +compiler, EVM, optimizer, dependency, or normative artifact was touched, and no production semantics or test +behavior changed. The configuration itself remains as independently reviewed and accepted; only how it is +described was corrected. + +### Prompt 2 — Capture the post-F8D / pre-GI gas and coverage baseline + +**Instruction.** Issued as its own session prompt, `docs/prompts/session-14-post-f8d-engineering-baseline.md`, +after G8D was reviewed and closed. Before `docs/project-status.md` is updated and before GI begins, capture +the first repository-wide engineering baseline for gas usage and test coverage, as a measurement and +evidence task only: run `forge snapshot` and `forge coverage`, preserve the machine-readable gas snapshot, +and create `docs/reports/gas-snapshot.md` and `docs/reports/coverage-summary.md` with the reproducibility +context, the actual measured values, the interpretation boundary, and — for coverage — materially uncovered +production areas and potential GI inputs. No production code, test, harness, gas, compiler, EVM, optimizer, +or dependency change is authorized; `docs/project-status.md` may not be modified; GI, F9 and F10 may not be +started; and if a baseline cannot be captured without such a change, record the blocker instead of making +the change. + +**Consequence.** Both baselines were captured. Recorded below under +[Post-F8D / Pre-GI Engineering Baseline](#post-f8d--pre-gi-engineering-baseline). + +### Prompt 3 — Synchronize `docs/project-status.md` with the reviewed F8D result + +**Instruction.** Update **status only** in `docs/project-status.md` to reflect the independently reviewed +F8D result: F8D — O2 Causal Finalization / Remaining Reduction **COMPLETE**, G8D **PASS**, and GI — Full +Stateful Invariant Verification as the **next authorized implementation slice / current blocker**. No +implementation details, design commentary, test summaries, gas or coverage results, engineering-baseline +observations, gate reasoning, retrospective observations, or other new descriptive content may be added to +that document; only the minimum edits needed to bring existing status fields, roadmap/ladder entries, +current-blocker/next-action fields, and now-stale status statements into consistency. This session log is to +record the instruction and the resulting status-only action as audit chronology, without changing or +expanding its normative content, implementation report, previously recorded evidence, or post-F8D / pre-GI +engineering-baseline evidence. No other file may be modified. + +**Consequence.** `docs/project-status.md` was updated in place, status-only: the header last-updated date, +current slice and last-closed-gate fields and the status ladder; the §2 next-authorized-slice, +last-closed-gate, gate-closure and next-slice statements; the §3 ladder rows for F8D and GI; the §7 line +that still said finalization was unimplemented; the §10 current blocker, its downstream-authorization +statement and the carried-forward limitation wording; the §18 next action; and the §19 handoff +validated-state, current-gate, next-blocker and next-step entries. No new descriptive content was +introduced, no gas, coverage, or baseline material was carried into that document, no other file was +changed, and no implementation, test, or verification state changed. + +--- + +## Implementation Chronology + +1. Read `CLAUDE.md`, `.claude/rules/solidity-style.md`, `.claude/rules/testing.md`, + `docs/project-status.md` and the Session 14 prompt. +2. Read the governing slice `docs/implementation-plan.md` §17 (F8D / G8D) and the upstream + realization requirements `docs/uniswap-v4-realization.md` §15 (RR-O2-16 … RR-O2-22) and §16 + (RR-O2-4, RR-O2-5). +3. Inspected the already-gated F8A/F8B/F8C production surface — `src/StandbyHook.sol`, + `src/ExerciseRouter.sol` — and the test surface that consumes it: + `test/harness/UnfinalizedExerciseRouter.sol`, `test/harness/ExerciseDeltaClosureRouter.sol`, + `test/harness/AdversarialExerciseRouter.sol`, `test/harness/StandbyHookHarness.sol`, + `test/shared/BaseExerciseAuthorizationTest.t.sol`, + `test/shared/BaseExerciseSettlementTest.t.sol`, + `test/shared/BaseUnbackedExerciseAuthorizationTest.t.sol`, + `test/integration/ExerciseSettlement.t.sol`, `test/periphery/ExerciseSettlementPerimeter.t.sol`, + `test/unit/ExerciseSettlementDerivation.t.sol` and `test/fuzz/ExerciseSettlementFuzz.t.sol`. +4. Inspected the pinned Uniswap v4 `SafeCast` rather than assuming its API. +5. Implemented F8D finalization in `src/StandbyHook.sol` and the finalization request in + `src/ExerciseRouter.sol`. +6. Restructured the exercise-router harness hierarchy so the already-gated F8C evidence keeps a + constructible failing case now that production finalizes, then built the F8D unit, integration and fuzz + evidence. +7. Ran the full repository suite, which surfaced a `HookMiner` gas exhaustion in an unrelated F1 fixture; + isolated it, confirmed the diagnosis, and recorded the resulting configuration change in `foundry.toml` + and `docs/setup.md` (Decision 8). + +--- + +## Implementation Decisions + +### Decision 1 — Finalization is a Hook-owned external transition taking only a request target + +`StandbyHook.finalizeExercise(uint256 _commitmentId)` matches the conceptual interface in +`implementation-plan.md` §17.2. The parameter is the finalization target the request names and is never +authority: it must equal the commitment the Hook's own causal context is bound to, and the mutation uses +`context.commitmentId` regardless. Every economic fact — `q`, the Beneficiary, the exerciser, the service — +is recovered from the context, so the surface carries no field through which any of them could be +substituted. + +### Decision 2 — The router names the commitment its own request was for + +`ExerciseRouter.exercise` now carries `_commitmentId` alongside `_maxInput` through the unlock, and +`_finalizeExercise` names that value. The identity check is therefore a real end-to-end check of the +request the caller made against the Hook's causal binding, rather than a tautology built from a value read +back out of the Hook. The payload is decoded after the PoolManager authentication, so a direct caller is +still refused whatever it presents. + +### Decision 3 — Finalization happens inside the same PoolManager unlock + +The F8C production documentation already stated the intent that finalization would consume the context +inside the same unlock, and the sequence there is load-bearing: nothing may be settled before the execution +is proven, nothing finalized before the Beneficiary has been delivered to. Nothing in `unlockCallback` is +caught, so a refused finalization unwinds the delivery, the settlement, the swap and the authorization with +it. + +### Decision 4 — Consumption clears the whole context, and needs no new lifecycle position + +`EMPTY` is the consumed position, so `ExerciseAuthorizationState` is unchanged. Consumption writes every +one of the seven context fields back to zero rather than only the position, because a context reporting +`EMPTY` while still carrying bindings would be exactly the reusable evidence consumption exists to destroy. + +### Decision 5 — One persistent write, no fulfillment flag, no reference clearing + +The only persistent effect is `Remaining -= q`, through the existing `_writeRemainingEntitlement` mechanic. +No `fulfilled` / `completed` / `settled` flag is introduced, no aggregate-obligation decrement is stored, +and the bounded enforcement reference is left in place — completion is `Remaining == 0` and the released +obligation follows from the derivation over the remainder that just changed. + +### Decision 6 — An `ExerciseFinalized` event + +The one durable Standby economic transition emits observational evidence, as O1 admission does. It exists +because the causal context that attributes a fulfillment is transaction-scoped: what persists afterwards is +a reduced remainder, which does not say which exercise, exerciser, or delivery discharged it. Nothing +enforces against the event. + +### Decision 7 — The harness router hierarchy was split rather than repurposed + +Production now finalizes, so `UnfinalizedExerciseRouter` — which previously only lifted the completion +barrier — would have started finalizing and would have invalidated the F8C evidence that settlement and +delivery fulfil nothing. It was rebased on a new `NonFinalizingExerciseRouter` (production minus the +finalization request, barrier intact), and the F8C `ProductionExerciseCompletionTest` was pointed at that +router so the production completion barrier keeps a failing case to be verified against. No F8C assertion +was weakened or removed. + +### Decision 8 — Environment: the pinned HookMiner needs more than Foundry's default gas limit + +Discovered while running the full suite: `test/integration/DeterministicEconomicFixture.t.sol` began failing +in `setUp` with `MemoryOOG` inside `HookMiner.find`. The pinned miner rehashes the whole Hook creation code +and allocates a fresh buffer on each of its `MAX_LOOP` = 160,444 candidate salts, so its cost is quadratic +in the iteration count and Foundry's default `1073741824` call gas limit stops the search after roughly +125,000 of its own iterations. Which salt is valid changes with every edit to the Hook, so under the default +limit any Solidity change can make an unrelated fixture fail to deploy its Hook for want of gas rather than +for want of a salt. Confirmed by re-running the same suite unchanged under `--gas-limit 20000000000`, where +all 15 tests pass. + +`gas_limit = 10000000000` was added to both `foundry.toml` profiles and recorded in `docs/setup.md`. It is a +local Foundry execution budget used by Forge tests and local script simulation — not a compiler, bytecode, +EVM-target, optimizer, dependency, deployed-contract, protocol, or deployed-chain gas-limit change — and +Standby's own bounded execution is enforced by explicit protocol bounds and reverts +(`MAX_PROSPECTIVE_SWAP_STEPS`, `MAX_LIVE_COMMITMENTS`) rather than by exhausting a gas limit, so it weakens +no verification. + +> Wording correction, recorded as Prompt 1 above: this decision originally described the setting as a +> "test-execution budget only", which was too narrow. The configuration was independently reviewed and +> accepted and its value did not change; only its description was corrected. + +--- + +## Required Task Completion Report + +### Files Inspected + +Governing and upstream artifacts: `CLAUDE.md`, `.claude/rules/solidity-style.md`, +`.claude/rules/testing.md`, `docs/project-status.md`, +`docs/prompts/session-14-f8d-02-causal-finalization.md`, `docs/implementation-plan.md` §17 (F8D / G8D) and +§18, `docs/uniswap-v4-realization.md` §15 (RR-O2-16 … RR-O2-22) and §16 (RR-O2-4, RR-O2-5), +`docs/architecture.md` (A7 / atomic economic finalization), `docs/setup.md`. + +Production: `src/StandbyHook.sol`, `src/ExerciseRouter.sol`, `script/helpers/StandbyFixtureConfig.sol`, +`script/helpers/BaseStandbyServiceTest` consumers. + +Tests and harnesses: `test/harness/UnfinalizedExerciseRouter.sol`, +`test/harness/ExerciseDeltaClosureRouter.sol`, `test/harness/AdversarialExerciseRouter.sol`, +`test/harness/StandbyHookHarness.sol`, `test/shared/BaseActorAwareStandbyTest.t.sol`, +`test/shared/BaseStandbyServiceTest.t.sol`, `test/shared/BaseCommitmentAdmissionTest.t.sol`, +`test/shared/BaseAuthenticBackingTest.t.sol`, `test/shared/BaseExerciseAuthorizationTest.t.sol`, +`test/shared/BaseUnbackedExerciseAuthorizationTest.t.sol`, `test/shared/BaseExerciseSettlementTest.t.sol`, +`test/integration/ExerciseSettlement.t.sol`, `test/integration/DeterministicEconomicFixture.t.sol`, +`test/periphery/ExerciseSettlementPerimeter.t.sol`, `test/unit/ExerciseSettlementDerivation.t.sol`, +`test/fuzz/ExerciseSettlementFuzz.t.sol`. + +Pinned dependency: `lib/v4-hooks-public/lib/v4-core/src/libraries/SafeCast.sol`, +`lib/v4-hooks-public/src/utils/HookMiner.sol`. + +### Files Changed + +Production: + +- `src/StandbyHook.sol` — added the `finalizeExercise` transition, the `ExerciseFinalized` event, five + finalization errors, `_requireFinalExerciseBacking`, `_consumeExerciseAuthorization`, and the + documentation updates the slice makes true. +- `src/ExerciseRouter.sol` — the unlock payload now carries the request's commitment alongside `maxInput`; + `unlockCallback` finalizes after resolving; `_finalizeExercise` requests finalization from the Hook. + +Configuration and setup: + +- `foundry.toml` — `gas_limit` on both profiles (see Decision 8). +- `docs/setup.md` — records the local Forge execution gas limit and why it exists. + +Test infrastructure: + +- `test/harness/NonFinalizingExerciseRouter.sol` (new) — production router minus the finalization request. +- `test/harness/UnfinalizedExerciseRouter.sol` — rebased on the above; still lifts the completion barrier. +- `test/harness/MisdirectedFinalizationRouter.sol` (new) — production router whose finalization request + names a chosen commitment, so a finalization failure is constructible behind a real committed exercise. +- `test/shared/BaseExerciseFinalizationTest.t.sol` (new) — the F8C fixture configured with the complete + production `ExerciseRouter`, plus the fulfillment assertions. + +Tests: + +- `test/unit/ExerciseFinalization.t.sol` (new) — 16 tests. +- `test/integration/ExerciseFinalization.t.sol` (new) — 16 tests across two suites. +- `test/fuzz/ExerciseFinalizationFuzz.t.sol` (new) — 5 fuzz properties. +- `test/integration/ExerciseSettlement.t.sol` — `ProductionExerciseCompletionTest` now configures + `NonFinalizingExerciseRouter`; the two barrier tests were renamed to what they now isolate. No assertion + was removed or weakened. + +### Requirements Implemented + +`implementation-plan.md` §17.2 (Hook-owned finalization), §17.3 (all eight prerequisites), §17.4 (the single +persistent mutation, no flag, reference not cleared), §17.5 (exact-once consumption and replay resistance), +§17.6 (economic finality atomicity); `uniswap-v4-realization.md` RR-O2-18 (finalization from bound causal +context), RR-O2-20 (actual post-execution backing confirmation), RR-O2-4 (proof consumed exactly once), +RR-O2-5 (one finalization per top-level exercise). + +### Tests Added or Changed + +Unit — `test/unit/ExerciseFinalization.t.sol` (harness fixture, harness-written causal contexts): + +- four position tests (`EMPTY`, `AUTHORIZING`, `AUTHORIZED`, `EXECUTING`) — a proven execution is required; +- three caller tests (outsider, exercise authority, identical unconfigured router) — the bound coordinator + is required; +- cross-commitment finalization — the named target is checked, not obeyed; +- proven-quantity reduction (1 and 20,000) and exhaustion to zero — `q` comes from the proof; +- proof exceeding the current remainder — authoritative commitment state is re-read; +- unbacked resulting state, exactly-backed resulting state, and actual-versus-prospective capacity — the + final backing comparison; +- whole-context consumption and replay refusal — exact-once consumption. + +Integration — `test/integration/ExerciseFinalization.t.sol` (complete production path): + +- exact reduction with the emitted attribution, minimal one-unit fulfillment, three sequential partials; +- exhaustion to zero remainder / zero commitment obligation / zero aggregate with the reference retained; +- exhausted and partially fulfilled commitments admit exactly their remainder and no more; +- exercise at exact `S == O` backing, checked against both independent oracles; +- no reusable causal evidence after completion; direct finalization outside an exercise fulfils nothing for + the configured router, the exercise authority, or an outsider; +- ordinary swap and direct Beneficiary transfer fulfil nothing; refused and unpayable exercises fulfil + nothing and leave no residue; +- the canonical A1–A4 sequence terminating at S = 15,000, O = 0, Remaining = 0, Beneficiary +50,000; +- `MisdirectedFinalizationTest` — a refused finalization unwinds a real executed, settled and delivered + exercise completely, with the correctly directed control committing the same exercise. + +Fuzz — `test/fuzz/ExerciseFinalizationFuzz.t.sol`: exact fulfillment across the whole admissible quantity +domain; sequential exercises against the produced remainder; exhaustion across the admissible entitlement +domain up to the full canonical Supporting Capacity; consumption; and continued backing under both +independent reconstructions. + +### Commands Run + +```bash +git status +forge fmt +forge fmt --check +forge build --sizes +forge test --match-path test/integration/ExerciseSettlement.t.sol +forge test --match-path test/unit/ExerciseFinalization.t.sol +forge test --match-path test/integration/ExerciseFinalization.t.sol +forge test --match-path test/fuzz/ExerciseFinalizationFuzz.t.sol +forge test +FOUNDRY_PROFILE=ci forge test +forge test --gas-limit 20000000000 --match-path test/integration/DeterministicEconomicFixture.t.sol +``` + +### Results + +- `forge fmt --check` — clean. +- `forge build --sizes` — `StandbyHook` 21,996 runtime / 23,022 init, 2,580 bytes of runtime margin; + `ExerciseRouter` 4,284; `StandbyHookHarness` 22,936 (1,640 margin). +- `forge test` — 58 suites, **539 passed, 0 failed, 0 skipped**. +- `FOUNDRY_PROFILE=ci forge test` — 58 suites, **539 passed, 0 failed, 0 skipped** (fuzz 10,008 runs). +- The `--gas-limit` run was the diagnostic that isolated the HookMiner issue in Decision 8; it is not part + of the standing verification. + +### Gate Evidence + +**Implemented and verified** + +- G8D-A — finalization is Hook-owned, requires the exact `EXECUTED` position, requires the bound + ExerciseRouter, requires the bound commitment identity, sources `q` from the context, exposes no field + for `q` / Beneficiary / exerciser / service / pool, and re-reads authoritative commitment state. +- G8D-B — `q <= current Remaining` re-checked; `Sactual` from actual post-execution PoolManager state + through the single production derivation; `Ocurrent` from the authoritative aggregate; + `Ofinal = Ocurrent - q`; `Sactual >= Ofinal` with equality passing at both unit and production level; the + authorization-time prospective value shown not to be substituted. +- G8D-C — `Remaining_after == Remaining_before - q`; no mutation on any refused path; Original Entitlement + unchanged; exact partial remainder; exact exhaustion to zero; completion derived from `Remaining == 0`; no + fulfillment flag introduced; aggregate O changes only through the remainder derivation. +- G8D-D — whole-context consumption; one proof causes one reduction; replay refused; cross-commitment + refused; quantity substitution structurally impossible; ordinary swaps, direct transfers and direct + finalization attempts fulfil nothing; settlement-and-delivery-alone fulfils nothing (the F8C suite against + `UnfinalizedExerciseRouter`). +- G8D-E — a refused finalization behind a real executed, settled and delivered exercise unwinds the pool + state, the payment, the delivery, both commitments' facts, the obligation and the causal context. +- G8D-F — partial, full, equality, replay, and the production top-level `exercise()` succeeding only after + finalization; the F8C completion barrier passing naturally on context consumption and still refusing an + unfinalized exercise; F8A/F8B/F8C suites unchanged and passing; unit + fuzz + integration; the canonical + A4 terminal relationship. + +**Structural rather than re-checked inside finalization** + +"No durable fulfillment without delivery" is preserved by ordering rather than by a delivery check inside +`finalizeExercise`, which is what the frozen boundary requires: F8D performs no Beneficiary selection or +delivery (`implementation-plan.md` §17, RR-O2-18), and delivery precedes finalization inside the same +uncaught unlock. + +**Still unverified** + +Stateful invariant verification over arbitrary transition sequences is GI and is not authorized. + +### Known Limitations / Blockers + +- The final-backing refusal and the short-remainder refusal are unreachable from production state, because + `S >= O` is the invariant every earlier transition maintains. Both are verified through the F8A unbacked + harness fixture, following the precedent that gate already established. +- Foundry's default call gas limit is insufficient for the pinned `HookMiner` search at the current Hook + size (Decision 8). Raised in `foundry.toml` and recorded in `docs/setup.md`. This is an environment + change discovered during F8D, not a protocol change, and it is flagged for explicit review. +- Carried forward from F0: `HelperConfig` resolves infrastructure only for chain id `31337`, and the + broadcasting `run()` path is verified in script simulation rather than against a live node. + +### Scope Check + +Work remained within F8D. No downstream slice was started: no invariant handler, no acceptance fixture, and +no frontend. + +Three changes are adjacent to the slice and are called out explicitly: + +1. the harness-router split and the `ProductionExerciseCompletionTest` router change, required because + production finalization makes the previous F8C fixture semantics false — no F8C assertion was weakened; +2. the `foundry.toml` / `docs/setup.md` gas-limit change (Decision 8); +3. documentation updates inside the two production files the slice changes. + +`docs/project-status.md` was not modified. + +### Proposed Gate Assessment + +**PASS (advisory).** + +All six G8D sub-gates have implementation and passing verification evidence on the required paths, with the +production-path claims made against the complete production `ExerciseRouter` and the real pinned +PoolManager, and the unreachable-state claims isolated through the harness fixture the F8A gate already +established for exactly that purpose. + +This is an advisory assessment only. Final G8D closure requires independent ChatGPT review of the actual +changed production code and tests. + +### Recommended Next Step + +Independent review of the changed production code and tests against G8D, with particular attention to the +`foundry.toml` gas-limit change and the harness-router restructuring. If G8D closes, the smallest coherent +next responsibility is GI — full stateful invariant verification — which is not currently authorized. + +### Prompt Audit + +The session was initiated from `docs/prompts/session-14-f8d-02-causal-finalization.md`. **Three material +follow-up instructions** were issued during the session and are recorded above: + +- **Prompt 1** — a documentation/comment precision correction to the description of the accepted Foundry + `gas_limit` configuration. It changed no value, no production semantics, and no test behavior. +- **Prompt 2** — the post-F8D / pre-GI engineering baseline, issued as its own prompt after G8D closed. It + was a measurement task and changed no production code, test, harness, or configuration. +- **Prompt 3** — the status-only synchronization of `docs/project-status.md` with the independently + reviewed F8D result. It recorded already-authorized state and changed no implementation, test, or + verification state. + +None affects the implementation report, verification evidence, or proposed gate assessment above, which +remain as recorded when F8D implementation completed. + +--- + +## Post-F8D / Pre-GI Engineering Baseline + +Captured under Prompt 2, after G8D was reviewed and closed and before GI. **Non-normative engineering +evidence:** neither report defines protocol semantics nor independently establishes or closes a gate. + +The detailed evidence lives in the two reports and is deliberately not duplicated here: + +```text +docs/reports/gas-snapshot.md +docs/reports/coverage-summary.md +``` + +### Measured state + +Branch `feat/f8d-o2-causal-finalization`, `HEAD` = `8938a85bb8036424c02c7cae57fc994371573933`, Foundry +`1.3.5-stable`, `default` profile. **The Session 14 F8D work was uncommitted when both measurements were +taken**, so neither baseline describes the state of commit `8938a85`; both reports enumerate the +working-tree delta they actually measured. + +### Gas + +```bash +forge snapshot +``` + +Completed successfully — 58 suites, 539 tests passed, 0 failed. Wrote the machine-readable artifact +`.gas-snapshot` (539 entries, 85 of them fuzz means), which is committed per the repository's existing +`.gitignore` convention. `forge build --sizes` was used for the deployed-size figures only. + +### Coverage + +```bash +forge coverage +forge coverage --report debug +``` + +Both completed successfully — 539 tests passed, 0 failed, under coverage instrumentation. The debug +invocation was used only to locate individual uncovered items and produced no repository artifact. + +Repository-level: lines 95.52%, statements 94.72%, branches 79.47%, functions 97.03%. Production and +per-file figures are in the coverage report. + +### Measurement limitations and anomalies + +1. **`forge test --gas-report` does not complete in this repository** — 68 of 538 tests fail under it. + `forge test --isolate` reproduces the same failures, which identifies the mechanism: isolated execution + makes every top-level call its own transaction and therefore discards EIP-1153 transient storage between + calls, and Standby's O2 causal context and the router's originating-exerciser attribution are + deliberately transaction-scoped (`uniswap-v4-realization.md` §16). This is a measurement artefact, not a + defect; no per-function gas figures from that run were used, and nothing was changed in response. +2. **`forge coverage` disables the optimizer and `via_ir`**, so the instrumented binary is not the binary + the gates were verified against. +3. **Foundry's coverage `Total` row aggregates `src/`, `script/` and `test/`** and is therefore not a + production figure; the production aggregates in the report are computed from the per-file rows. +4. The largest `.gas-snapshot` entries measure the pinned `HookMiner` address search performed in-test, not + Standby execution — the same characteristic already recorded in `docs/setup.md`. + +### Scope confirmation + +No production code, test, harness, fixture, gas, compiler, optimizer, EVM, or dependency configuration was +changed. `docs/project-status.md` was not modified. No frozen normative artifact was modified. No +optimization work was performed, no test was added, removed, or suppressed to affect coverage, and no +production code was excluded from measurement. Uncovered production paths were recorded and, where they +intersect an authority or state-transition boundary, noted as **potential** GI inputs only. GI was not +started, and neither was F9 or F10. diff --git a/docs/prompts/session-14-post-f8d-engineering-baseline.md b/docs/prompts/session-14-post-f8d-engineering-baseline.md new file mode 100644 index 0000000..8124ce5 --- /dev/null +++ b/docs/prompts/session-14-post-f8d-engineering-baseline.md @@ -0,0 +1,372 @@ +# Standby — Session 14 Follow-Up + +## Post-F8D / Pre-GI Gas and Coverage Baseline + +F8D implementation is complete and: + +> **G8D — PASS / CLOSED** + +Before updating docs/project-status.md and before beginning GI, capture the first repository-wide engineering baseline for: + +1. gas usage; +2. test coverage. + +This is a **measurement and evidence task only**. + +F0–F8D production path complete +G8D PASS / CLOSED +↓ +POST-F8D / PRE-GI BASELINE +↓ +project-status update + ↓ +session 14 closout +↓ +GI not started + +--- + +# 1. Authority Boundary + +Apply the established clean rule: + +> **`CLAUDE.md` owns permanent operating behavior.** +> +> **`.claude/rules/*` owns permanent Solidity/testing conventions.** +> +> **This Session 14 follow-up owns only this evidence task's objective, scope, requirements, prohibitions, file boundaries, evidence requirements, and completion boundary.** + +--- + +# 2. Objective + +Capture the repository state at the following checkpoint: + +```text id="lhbf6w" +F0–F8D production path complete +G8D PASS / CLOSED + ↓ +POST-F8D / PRE-GI BASELINE + ↓ +GI not started +``` + +Create: + +```text id="2wfr02" +docs/reports/gas-snapshot.md +docs/reports/coverage-summary.md +``` + +Preserve the normal machine-readable gas snapshot generated by Foundry, if applicable. + +These reports are: + +> **Non-normative engineering evidence.** + +They do not define protocol semantics and do not independently establish or close an implementation gate. + +--- + +# 3. Evidence Context + +Both reports must identify the measured repository state sufficiently to make the baseline reproducible. + +Record: + +- measurement date; +- Git branch; +- Git commit SHA; +- working-tree state relevant to this measurement; +- Forge / Foundry version; +- active Foundry profile where relevant; +- existing local Foundry gas-limit configuration where relevant; +- exact measurement command. + +If the current Session 14 work is not yet committed, state that explicitly. + +Do not represent the measurement as belonging to a commit that does not contain the measured state. + +--- + +# 4. Gas Baseline + +Run: + +```bash id="w0eiyz" +forge snapshot +``` + +unless an existing permanent repository convention requires a different invocation. + +Do not change gas-related configuration for this task. + +Preserve the normal machine-comparable gas snapshot artifact produced by Foundry where applicable. + +Create: + +```text id="6y3j6r" +docs/reports/gas-snapshot.md +``` + +The report must contain: + +## Status + +State: + +> **Status: Non-normative engineering evidence. This report does not define protocol semantics or independently establish a verification gate.** + +## Checkpoint + +Identify the measurement as: + +```text id="7e78ha" +Post-F8D / Pre-GI +``` + +## Environment + +Record the evidence context from Section 3. + +## Command and Result + +Record: + +- exact command executed; +- whether it completed successfully; +- any material limitation affecting interpretation. + +## Measurements + +Summarize the materially useful gas measurements reported for the Standby production implementation. + +Report actual measured values rather than generalized assessments. + +Preserve the machine-readable snapshot as the detailed comparison source where applicable. + +## Baseline Purpose + +State that this report establishes the post-F8D / pre-GI gas baseline against which later measurements may be compared. + +## Observations + +Record any materially noteworthy measurement observation. + +Do not turn an observation into a new optimization requirement. + +If there is no material observation, state that. + +--- + +# 5. Coverage Baseline + +Run: + +```bash id="e6vq8w" +forge coverage +``` + +unless an existing permanent repository convention requires a different invocation. + +Create: + +```text id="q4d4mm" +docs/reports/coverage-summary.md +``` + +The report must contain: + +## Status + +State: + +> **Status: Non-normative engineering evidence. This report does not define protocol semantics or independently establish a verification gate.** + +## Checkpoint + +Identify the measurement as: + +```text id="66pw0s" +Post-F8D / Pre-GI +``` + +## Environment + +Record the evidence context from Section 3. + +## Command and Result + +Record: + +- exact command executed; +- whether it completed successfully; +- any reporting options used; +- any material limitation affecting interpretation. + +## Repository-Level Coverage + +Record the distinct coverage dimensions reported by Foundry, where available: + +- line coverage; +- statement coverage; +- branch coverage; +- function coverage. + +Do not collapse the distinct dimensions into one generalized coverage percentage. + +## Production Coverage + +Where reliable per-file results are available, summarize coverage for the relevant Standby production contracts. + +Use the actual repository production-contract set. + +## Material Uncovered Production Areas + +Identify materially uncovered or partially covered production paths that may deserve examination when GI is derived. + +Do not classify an uncovered line or branch as a protocol defect solely because it is uncovered. + +## Potential GI Inputs + +Identify any uncovered production behavior that intersects an invariant-relevant or state-transition-relevant path and may therefore warrant examination during GI. + +Record these as **potential GI inputs**, not as already-established GI requirements. + +Do not begin GI derivation or implementation. + +--- + +# 6. Interpretation Boundary + +Preserve the following distinction: + +```text id="ew04gl" +Gas measurement + ≠ gas optimization requirement + +Coverage measurement + ≠ semantic correctness + +High coverage + ≠ invariant proof + +Uncovered code + ≠ automatic protocol defect +``` + +The reports expose implementation characteristics for subsequent engineering review. + +They do not alter the frozen protocol semantics or the already-closed implementation gates. + +--- + +# 7. Scope / Prohibitions + +This task authorizes measurement and evidence capture only. + +Do not: + +- modify Solidity production code; +- modify tests; +- modify harnesses; +- optimize gas; +- add tests to improve coverage; +- remove or suppress tests to affect coverage; +- exclude production code merely to improve reported coverage; +- change gas configuration; +- change compiler, optimizer, or EVM configuration; +- change dependencies; +- modify frozen normative artifacts; +- modify `docs/project-status.md`; +- reopen or redesign F8D; +- begin GI; +- begin F9 or F10; +- perform unrelated cleanup or refactoring. + +If either baseline cannot be captured without making one of these changes, do not make the change. + +Record the failed command and blocker instead. + +--- + +# 8. Authorized Files + +This follow-up may create or update only: + +```text id="48h55a" +docs/reports/gas-snapshot.md +docs/reports/coverage-summary.md +docs/prompts/session-14-log.md +``` + +and the normal machine-readable gas snapshot artifact produced by `forge snapshot`, if applicable. + +No other repository file modification is authorized. + +--- + +# 9. Session 14 Log + +Update: + +```text id="4jxd3m" +docs/prompts/session-14-log.md +``` + +with a concise record that the post-F8D / pre-GI baseline was captured. + +Record: + +- gas command and result; +- coverage command and result; +- reports created; +- machine-readable gas snapshot created or updated, if applicable; +- material measurement limitations or anomalies; +- confirmation that no production code, tests, configuration, or downstream implementation work was performed. + +Do not duplicate the detailed report contents into the session log. + +Reference the two detailed evidence artifacts: + +```text id="b03x95" +docs/reports/gas-snapshot.md +docs/reports/coverage-summary.md +``` + +--- + +# 10. Completion Report + +When complete, report: + +1. files created; +2. files modified; +3. exact gas command and result; +4. exact coverage command and result; +5. headline gas measurements; +6. repository-level coverage results; +7. materially uncovered production areas, if any; +8. potential GI inputs identified, if any; +9. measurement limitations or blockers; +10. confirmation that no production code, tests, harnesses, or configuration were changed; +11. confirmation that GI was not started. + +Do not perform corrective implementation work based on the measurements. + +--- + +# 11. Completion Boundary + +The completion boundary is: + +```text id="grr9fl" +post-F8D / pre-GI gas baseline captured +docs/reports/gas-snapshot.md created +machine-readable gas snapshot preserved where applicable +post-F8D / pre-GI coverage baseline captured +docs/reports/coverage-summary.md created +session-14-log.md updated +``` + +Stop at the post-F8D / pre-GI engineering-baseline completion boundary. diff --git a/docs/reports/coverage-summary.md b/docs/reports/coverage-summary.md new file mode 100644 index 0000000..4f09657 --- /dev/null +++ b/docs/reports/coverage-summary.md @@ -0,0 +1,245 @@ +# Standby — Coverage Summary + +## Status + +> **Status: Non-normative engineering evidence. This report does not define protocol semantics or independently establish a verification gate.** + +Coverage is a measure of what the test suite executed. It is not a measure of semantic correctness, and it +is not invariant proof. An uncovered line is not, on its own, a protocol defect. + +--- + +## Checkpoint + +```text +Post-F8D / Pre-GI +``` + +The F0–F8D production path is complete and G8D is PASS / CLOSED. GI has not been started. + +--- + +## Environment + +| Item | Value | +| ---------------------- | ----------------------------------------------------------------- | +| Measurement date | 2026-09-08 | +| Git branch | `feat/f8d-o2-causal-finalization` | +| Git commit (`HEAD`) | `8938a85bb8036424c02c7cae57fc994371573933` | +| Working tree | **Not clean — the measured state is uncommitted.** See below. | +| Foundry | `1.3.5-stable`, commit `9979a41b5daa5da1572d973d7ac5a3dd2afc0221` | +| Solidity / EVM | `0.8.26` / `cancun` | +| Active Foundry profile | `default` (fuzz `runs = 1000`, `seed = 0x1`) | +| Local Forge gas limit | `gas_limit = 10000000000` | +| Compilation | **Optimizer and `via_ir` disabled by `forge coverage` itself** | + +### Working-tree state + +`HEAD` is the F8C merge commit. **The Session 14 F8D work was uncommitted when this measurement was taken, +so this report does not describe the state of commit `8938a85`.** It describes that commit plus the +following working-tree changes: + +Modified: + +```text +foundry.toml +docs/setup.md +src/ExerciseRouter.sol +src/StandbyHook.sol +test/harness/UnfinalizedExerciseRouter.sol +test/integration/ExerciseSettlement.t.sol +``` + +Added: + +```text +docs/prompts/session-14-f8d-02-causal-finalization.md +docs/prompts/session-14-log.md +test/harness/MisdirectedFinalizationRouter.sol +test/harness/NonFinalizingExerciseRouter.sol +test/shared/BaseExerciseFinalizationTest.t.sol +test/unit/ExerciseFinalization.t.sol +test/integration/ExerciseFinalization.t.sol +test/fuzz/ExerciseFinalizationFuzz.t.sol +``` + +When the Session 14 work is committed, this baseline should be understood as belonging to that commit. + +--- + +## Command and Result + +```bash +forge coverage +``` + +Completed successfully (exit 0): 58 suites, **539 tests passed, 0 failed, 0 skipped**, under coverage +instrumentation. The default summary report was used; no filtering, exclusion, or `--match` option was +applied. + +A second, read-only invocation was used to locate the individual uncovered items reported below: + +```bash +forge coverage --report debug +``` + +It produced no repository artifact. + +### Material limitations affecting interpretation + +1. **Instrumented build differs from the verified build.** Foundry reports: + `Warning: optimizer settings and 'viaIR' have been disabled for accurate coverage reports.` The measured + binary is therefore not the optimizer-enabled binary the gates were verified against. This affects which + binary executed, not which source constructs the suite reached. + +2. **Foundry's `Total` row is not a production figure.** It aggregates `src/`, `script/`, *and* `test/` + files, so test and fixture code is counted alongside protocol code. Both are reported separately below, + and the production aggregates are computed from the per-file rows rather than reported by Foundry. + +3. **`src/mocks/MockUSDC.sol` and `src/mocks/MockUSTB.sol` report 0.00%.** The uncovered items are their + `name()` and `symbol()` overrides, which nothing in the suite calls. They are deterministic fixture + currencies, not protocol code. + +4. **Coverage counts execution, not intent.** A covered branch is one the suite reached; it is not evidence + that what the branch does is correct, and it is not evidence about any invariant. + +--- + +## Repository-Level Coverage + +Reported by Foundry across all instrumented files (`src/`, `script/`, `test/`). The four dimensions are +distinct and are deliberately not collapsed into one number. + +| Dimension | Coverage | +| ------------ | -------------------- | +| Lines | 95.52% (1300 / 1361) | +| Statements | 94.72% (1239 / 1308) | +| Branches | 79.47% (120 / 151) | +| Functions | 97.03% (294 / 303) | + +--- + +## Production Coverage + +### Per file, as reported + +| File | Lines | Statements | Branches | Functions | +| ------------------------------------ | ---------------- | ---------------- | -------------- | -------------- | +| `src/StandbyHook.sol` | 99.73% (366/367) | 98.52% (400/406) | 92.77% (77/83) | 100.00% (66/66) | +| `src/ExerciseRouter.sol` | 97.14% (68/70) | 95.31% (61/64) | 72.73% (8/11) | 100.00% (16/16) | +| `src/EligibilityRegistry.sol` | 100.00% (20/20) | 100.00% (14/14) | 100.00% (2/2) | 100.00% (8/8) | +| `src/libraries/StandbyMath.sol` | 100.00% (34/34) | 100.00% (28/28) | 100.00% (7/7) | 100.00% (9/9) | +| `src/libraries/ServiceDomain.sol` | 100.00% (19/19) | 100.00% (19/19) | 100.00% (1/1) | 100.00% (6/6) | +| `src/libraries/CommitmentRefs.sol` | 100.00% (7/7) | 100.00% (9/9) | 100.00% (0/0) | 100.00% (2/2) | +| `src/demo/ActorAwareTestRouter.sol` | 100.00% (50/50) | 95.83% (46/48) | 71.43% (5/7) | 100.00% (12/12) | +| `src/mocks/MockFixtureCurrency.sol` | 96.30% (26/27) | 95.45% (21/22) | 100.00% (3/3) | 100.00% (6/6) | +| `src/mocks/MockUSDC.sol` | 0.00% (0/4) | 0.00% (0/2) | 100.00% (0/0) | 0.00% (0/2) | +| `src/mocks/MockUSTB.sol` | 0.00% (0/4) | 0.00% (0/2) | 100.00% (0/0) | 0.00% (0/2) | + +Deployment and configuration sources, for completeness: + +| File | Lines | Statements | Branches | Functions | +| ------------------------------------------------- | --------------- | --------------- | ------------- | -------------- | +| `script/DeployStandbyHook.s.sol` | 47.62% (20/42) | 46.81% (22/47) | 0.00% (0/6) | 66.67% (2/3) | +| `script/helpers/DeterministicFixtureDeployer.sol` | 81.82% (18/22) | 85.19% (23/27) | 25.00% (1/4) | 100.00% (3/3) | +| `script/helpers/HelperConfig.s.sol` | 100.00% (8/8) | 100.00% (8/8) | 100.00% (1/1) | 100.00% (2/2) | + +### Computed aggregates + +Summed from the per-file rows above. These are computed here, not reported by Foundry. + +Standby protocol core — the Hook, the ExerciseRouter, the EligibilityRegistry, and the three libraries: + +| Dimension | Coverage | +| ---------- | ------------------ | +| Lines | 99.42% (514 / 517) | +| Statements | 98.33% (531 / 540) | +| Branches | 91.35% (95 / 104) | +| Functions | 100.00% (107 / 107) | + +All of `src/`, including the demo router and the fixture currencies: + +| Dimension | Coverage | +| ---------- | ------------------ | +| Lines | 98.01% (590 / 602) | +| Statements | 97.39% (598 / 614) | +| Branches | 90.35% (103 / 114) | +| Functions | 96.90% (125 / 129) | + +--- + +## Material Uncovered Production Areas + +Every uncovered production item is listed. Each is a rejection path or a defensive clamp; none is an +untested economic transition, and none is classified here as a defect. + +### `src/StandbyHook.sol` — 1 line, 6 statements, 6 branches + +| Location | Construct | Note | +| -------- | --------- | ---- | +| L1062 | `authorizeExercise` → `StandbyHook__ServiceNotConfigured` | O2 authorization against a Hook with no service. Every exercise fixture activates a service first. | +| L1218–1219 | `authorizedProtectedExecution` → `StandbyHook__ExerciseExecutionNotAuthorized` | The read surface consulted while the causal context is not `AUTHORIZED`. The equivalent refusal on the authoritative callback path *is* covered. | +| L1668 | `_beforeRemoveLiquidity` → `StandbyHook__UntrustedLiquidityPerimeter` | Liquidity **removal** proposed by an untrusted sender. The same refusal on **addition** is covered. | +| L2217 | `_beginSwapDerivation` → `StandbyHook__ServiceNotConfigured` | Prospective swap derivation before activation. | +| L2343 | `_nextSwapTargetTick` → `tickNext <= TickMath.MIN_TICK` clamp | Mirrors the pinned v4 swap loop's own clamping; reachable only at the extreme tick bound. | +| L2344 | `_nextSwapTargetTick` → `tickNext >= TickMath.MAX_TICK` clamp | As above, at the opposite bound. | + +### `src/ExerciseRouter.sol` — 2 lines, 3 statements, 3 branches + +| Location | Construct | Note | +| -------- | --------- | ---- | +| L395–396 | `ExerciseRouter__InputTransferFailed` | Reached only when `transferFrom` returns `false` without reverting. The fixture currency reverts with its own typed errors instead, and those failure modes *are* covered. | +| L441–443 | `ExerciseRouter__UnresolvedExerciseDelta` | The fail-closed guard for an exercise leaving an open PoolManager delta. Settlement and delivery close each side exactly, so no reachable path leaves one open. | +| L475 | `ExerciseRouter__ExerciseContextAlreadyActive` | A nested `exercise()` while one is already in flight. | + +### `src/demo/ActorAwareTestRouter.sol` — 2 statements, 2 branches + +`ActorAwareTestRouter__NotPoolManager` (L195) and `ActorAwareTestRouter__SettlementTransferFailed` (L244). +This contract is demo and test-perimeter instrumentation, not protocol. + +### `src/mocks/` — fixture metadata + +`MockUSDC.name/symbol` and `MockUSTB.name/symbol` are never called; `MockFixtureCurrency` L118 is the +allowance-decrement path taken only when the allowance is not `type(uint256).max`. + +### `script/DeployStandbyHook.s.sol` — 22 lines, 25 statements, 6 branches + +The lowest production-adjacent figure in the repository. The mining and validation procedure is exercised +by every fixture, but the broadcasting `run()` entrypoint and its validation failure branches are not +reached by `forge test` — consistent with the limitation already recorded in `docs/project-status.md`, that +`run()` is verified in script simulation rather than against a live node. + +--- + +## Potential GI Inputs + +Recorded as **potential inputs to GI derivation, not as established GI requirements.** GI has not been +started and no derivation was performed here. Whether any of these belongs in GI is for GI's own derivation +to decide against `docs/invariants.md` and `docs/testing-strategy.md`. + +1. **Untrusted-perimeter liquidity removal** (`StandbyHook.sol` L1668). An O3 authority boundary on a + backing-affecting transition. A GI handler that generates liquidity actions from arbitrary senders would + intersect this path directly. + +2. **Nested exercise attribution** (`ExerciseRouter.sol` L475). The router's one-originator-per-request + discipline. It sits beside the Hook's own overlapping-authorization restriction, which is covered, and + the economic-reentrancy restriction of `uniswap-v4-realization.md` §16.3 is an invariant-relevant + property. + +3. **The causal-position refusal on the read surface** (`StandbyHook.sol` L1218–1219). A sequence-dependent + guard on a surface an adversarial handler could consult at any causal position. + +4. **Tick-bound clamping in the prospective swap derivation** (`StandbyHook.sol` L2343–2344). Reachable only + outside the configured service domain. A GI swap handler with deliberately extreme inputs is the kind of + generator that would reach it, and the prospective derivation is the authoritative input to backing + enforcement. + +5. **The unresolved-delta guard** (`ExerciseRouter.sol` L441–443). Structurally unreachable on the current + production path. Noted because it is the router's own fail-closed statement about exercise-local delta + isolation (RR-O2-15), and adversarial sequencing is what GI generates. + +Items 1 and 2 are the two that intersect an authority or state-transition boundary most directly. Items 3–5 +are noted for completeness. + +The two `ServiceNotConfigured` refusals and the fixture-currency metadata are **not** proposed as GI inputs: +GI operates against an activated service, and the mocks carry no protocol behavior. diff --git a/docs/reports/gas-snapshot.md b/docs/reports/gas-snapshot.md new file mode 100644 index 0000000..0cbbe48 --- /dev/null +++ b/docs/reports/gas-snapshot.md @@ -0,0 +1,223 @@ +# Standby — Gas Snapshot + +## Status + +> **Status: Non-normative engineering evidence. This report does not define protocol semantics or independently establish a verification gate.** + +It records measured values at one repository checkpoint so that later measurements have something to be +compared against. It creates no optimization requirement. + +--- + +## Checkpoint + +```text +Post-F8D / Pre-GI +``` + +The F0–F8D production path is complete and G8D is PASS / CLOSED. GI has not been started. + +--- + +## Environment + +| Item | Value | +| --------------------- | ----------------------------------------------------------------------- | +| Measurement date | 2026-09-08 | +| Git branch | `feat/f8d-o2-causal-finalization` | +| Git commit (`HEAD`) | `8938a85bb8036424c02c7cae57fc994371573933` | +| Working tree | **Not clean — the measured state is uncommitted.** See below. | +| Foundry | `1.3.5-stable`, commit `9979a41b5daa5da1572d973d7ac5a3dd2afc0221` | +| Solidity / EVM | `0.8.26` / `cancun` | +| Optimizer | enabled, `optimizer_runs = 800`, `via_ir = false`, `bytecode_hash=none` | +| Active Foundry profile | `default` (fuzz `runs = 1000`, `seed = 0x1`) | +| Local Forge gas limit | `gas_limit = 10000000000` | + +### Working-tree state + +`HEAD` is the F8C merge commit. **The Session 14 F8D work was uncommitted when this measurement was +taken, so this snapshot does not describe the state of commit `8938a85`.** It describes that commit plus +the following working-tree changes: + +Modified: + +```text +foundry.toml +docs/setup.md +src/ExerciseRouter.sol +src/StandbyHook.sol +test/harness/UnfinalizedExerciseRouter.sol +test/integration/ExerciseSettlement.t.sol +``` + +Added: + +```text +docs/prompts/session-14-f8d-02-causal-finalization.md +docs/prompts/session-14-log.md +test/harness/MisdirectedFinalizationRouter.sol +test/harness/NonFinalizingExerciseRouter.sol +test/shared/BaseExerciseFinalizationTest.t.sol +test/unit/ExerciseFinalization.t.sol +test/integration/ExerciseFinalization.t.sol +test/fuzz/ExerciseFinalizationFuzz.t.sol +``` + +When the Session 14 work is committed, this baseline should be understood as belonging to that commit. + +--- + +## Command and Result + +### Primary measurement + +```bash +forge snapshot +``` + +Completed successfully: 58 suites, **539 tests passed, 0 failed, 0 skipped**. + +It wrote the machine-readable artifact: + +```text +.gas-snapshot +``` + +which holds one entry per test — 539 entries, of which 85 are fuzz entries reported as +`(runs, μ, ~)` rather than a single value. `.gas-snapshot` is the detailed comparison source and is +committed, per the repository's existing `.gitignore` convention. + +### Supplementary measurement — attempted, did not complete + +```bash +forge test --gas-report +``` + +**Failed: 68 of 538 tests failed under `--gas-report`.** The per-function gas tables it produced are +therefore *not* used in this baseline. + +The cause was isolated and is a measurement artefact, not a defect: + +- `--gas-report` runs each top-level call in isolation, which makes every call its own transaction; +- `forge test --isolate` alone reproduces the same failures on the same suites, confirming the mechanism; +- Standby's O2 causal context and the ExerciseRouter's originating-exerciser attribution are deliberately + held in EIP-1153 transient storage, because the frozen realization requires that evidence to be + transaction-scoped (`uniswap-v4-realization.md` §16); +- isolated execution therefore discards that evidence between the calls that make up one exercise, and the + affected tests fail with causal-position errors such as + `StandbyHook__ExerciseExecutionNotAuthorized(0)`. + +No action was taken on this. Per-function gas attribution for the O2 path is simply not obtainable from +`--gas-report` while the causal proof is transaction-scoped. + +### Non-gas supporting measurement + +```bash +forge build --sizes +``` + +Used only for the deployed-size figures below. + +--- + +## Measurements + +### Deployed contract size + +Runtime size, initcode size, and remaining margin against the 24,576-byte limit: + +| Contract | Runtime | Initcode | Margin | +| -------------------------- | ------: | -------: | -----: | +| `StandbyHook` | 21,996 | 23,022 | 2,580 | +| `ExerciseRouter` | 4,284 | 4,635 | 20,292 | +| `EligibilityRegistry` | 1,024 | 1,208 | 23,552 | +| `ActorAwareTestRouter` | 4,752 | 4,940 | 19,824 | +| `MockUSTB` / `MockUSDC` | 1,528 | 1,556 | 23,048 | + +### Per-test gas — canonical production transitions + +These are whole-test figures from `.gas-snapshot`. Each includes its own fixture construction, so a value +is a stable comparison point rather than the cost of a single protocol transition in isolation. + +**O2 — complete production exercise** (authorize → execute → settle → deliver → finalize, production +`ExerciseRouter`, real PoolManager): + +| Test | Gas | +| ----------------------------------------------------------------- | ------: | +| `ExerciseFinalizationTest:test_minimalExercise_fulfilsExactlyOneRawUnit` | 475,146 | +| `ExerciseFinalizationTest:test_completedExercise_reducesRemainingByExactlyQ` | 479,065 | +| `ExerciseFinalizationTest:test_exhaustingExercise_leavesZeroRemainingAndZeroObligation` | 498,249 | +| `ExerciseFinalizationTest:test_canonicalSequence_reachesTheFrozenTerminalState` | 580,700 | +| `ExerciseFinalizationTest:test_sequentialPartialExercises_produceTheExactRemainder` (three exercises) | 677,504 | +| `MisdirectedFinalizationTest:test_correctlyDirectedFinalization_commitsTheSameExercise` | 635,024 | +| `MisdirectedFinalizationTest:test_refusedFinalization_unwindsTheCompleteExercise` | 625,612 | + +**O2 — settlement and delivery without finalization** (F8C fixture, `UnfinalizedExerciseRouter`): + +| Test | Gas | +| ----------------------------------------------------------------- | ------: | +| `ExerciseSettlementTest:test_authorizedExercise_settlesExactlyAndDeliversDirectly` | 587,776 | + +**O1 — commitment admission:** + +| Test | Gas | +| ----------------------------------------------------------------- | ------: | +| `CommitmentAdmissionTest:test_establishCommitment_byEstablishmentAuthority_isAdmitted` | 207,914 | +| `CommitmentAdmissionTest:test_establishCommitment_withAFutureWindow_isAdmitted` | 204,464 | +| `CommitmentAdmissionTest:test_establishCommitment_forAnIneligibleBeneficiary_isRejected` (refusal) | 148,368 | + +**O3 — ordinary backing-affecting transitions against an authentic obligation:** + +| Test | Gas | +| ----------------------------------------------------------------- | ------: | +| `O3AuthenticBackingEnforcementTest:test_canonicalA2_compatibleOrdinarySwap_succeedsAgainstAuthenticObligation` | 509,574 | +| `O3AuthenticBackingEnforcementTest:test_canonicalA3_destructiveOrdinarySwap_isRejectedForInsufficientBacking` | 605,189 | +| `O3LiquidityEnforcementTest:test_liquidityAddition_byEligibleProvider_becomesAuthoritative` | 234,341 | +| `O3LiquidityEnforcementTest:test_activeLiquidityRemoval_matchesTheProspectiveDerivation` | 203,827 | + +**Bounded aggregate-obligation derivation** (the scan whose cost the bounded index exists to cap): + +| Test | Gas | +| ----------------------------------------------------------------- | --------: | +| `AggregateObligationTest:test_aggregateObligation_isZeroWhenEverySlotIsEmpty` | 50,506 | +| `AggregateObligationTest:test_aggregateObligation_countsOneBindingCommitment` | 167,451 | +| `AggregateObligationTest:test_aggregateObligation_derivesAcrossAFullyOccupiedIndex` | 1,957,147 | + +`.gas-snapshot` remains the authoritative detailed source; the entries above are a readable extract. + +--- + +## Baseline Purpose + +This report establishes the **post-F8D / pre-GI gas baseline** against which later measurements may be +compared. `.gas-snapshot` is the machine-comparable artifact; a subsequent `forge snapshot --diff` or +`forge snapshot --check` against it will show what changed and by how much. + +It is a reference point only. It does not create a gas budget, a regression threshold, or an optimization +requirement, and no measurement in it has been acted upon. + +--- + +## Observations + +Four observations are materially noteworthy for interpreting the numbers. None of them is a defect and none +creates a requirement. + +1. **The largest snapshot entries measure Hook address mining, not Standby execution.** The five highest + entries range from ~209M to ~428M gas (for example + `StandbyServiceConfigurationTest:test_configureAndActivate_rejectsAHookWithNoTrustedPositionManager` at + 427,994,963, and the `ProspectiveTraversalBoundTest` suite at ~209M each). These suites deploy Hooks + in-test through the canonical procedure, and the cost is the pinned `HookMiner.find` search, which + rehashes the whole Hook creation code per candidate salt. This is the same characteristic recorded in + `docs/setup.md` for the local Forge gas limit. + +2. **Per-test gas is not per-transition gas.** Every figure includes the fixture the test builds — pool + initialization, canonical liquidity, service activation, and commitment admission — so comparisons + between different suites are not comparisons between protocol operations. + +3. **Per-function gas attribution is unavailable for the O2 path** under `forge test --gas-report`, for the + transient-storage reason recorded under Command and Result. Anyone wanting per-function figures for + Standby's non-O2 surface would need to obtain them without isolated execution. + +4. **`StandbyHook` has 2,580 bytes of runtime margin** against the 24,576-byte limit at this checkpoint. + Recorded as a fact to compare against, not as a limit being approached in a way that requires action. diff --git a/docs/setup.md b/docs/setup.md index 1b9d914..025b52c 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -612,9 +612,28 @@ The current Standby implementation baseline is: - **Optimizer Runs:** `800` - **Bytecode Hash:** `none` - **FFI:** disabled by default +- **Local Forge execution gas limit:** `10000000000` The repository configuration in `foundry.toml` is authoritative for the active Foundry build settings. +## Local Forge execution gas limit + +`gas_limit` is a local Foundry execution budget used by Forge tests and local script simulation. It is not a +compiler, bytecode, EVM-target, deployed-contract, protocol, or deployed-chain gas-limit setting. It is +raised above Foundry's default `1073741824` for one reason. + +The canonical StandbyHook deployment mines its address with the pinned `HookMiner.find`, which rehashes the +entire Hook creation code and allocates a fresh buffer on every one of its `MAX_LOOP` = 160,444 candidate +salts. Its cost is therefore quadratic in the iteration count, and under the default limit the pinned search +runs out of gas after roughly 125,000 of its own iterations. A Hook whose valid salt lies beyond that point +then fails to deploy for want of gas rather than for want of a salt, and which salt is valid changes with +every edit to the Hook — so under the default limit any Solidity change can make an unrelated fixture fail +to build its Hook. + +Standby's own bounded execution is enforced by explicit protocol bounds and reverts — for example +`MAX_PROSPECTIVE_SWAP_STEPS` and `MAX_LIVE_COMMITMENTS` — never by exhausting a gas limit. Raising this +value therefore weakens no verification and conceals no unbounded production path. + A change to the compiler, EVM target, optimizer settings, or other execution-relevant configuration must be treated as a toolchain change and requires rerunning the verification gates that depend on that configuration. --- @@ -718,6 +737,7 @@ optimizer = true optimizer_runs = 800 bytecode_hash = none ffi = false +gas_limit = 10000000000 ``` --- diff --git a/foundry.toml b/foundry.toml index 0871304..95a359e 100644 --- a/foundry.toml +++ b/foundry.toml @@ -13,6 +13,19 @@ bytecode_hash = "none" ffi = false +# Local Forge execution gas budget; used by tests and local script simulation. +# Does not change Solidity compilation, generated bytecode, the EVM target, deployed-chain gas limits, +# deployed Standby contract execution limits, or Standby protocol semantics. +# +# The canonical StandbyHook deployment mines its address with the pinned `HookMiner.find`, which rehashes +# the whole creation code and allocates a fresh buffer on every one of its `MAX_LOOP` = 160,444 candidate +# salts. The resulting cost is quadratic in the iteration count, so Foundry's default 2^30 call gas limit +# stops the pinned search after roughly 125,000 of its own iterations — and a Hook whose valid salt lies +# beyond that point fails to deploy for want of gas rather than for want of a salt. Standby's own bounded +# execution is enforced by explicit protocol bounds and reverts, never by exhausting a gas limit, so +# raising this weakens no requirement and hides no unbounded production path. +gas_limit = 10000000000 + [profile.default.fuzz] runs = 1000 seed = "0x1" @@ -37,6 +50,10 @@ bytecode_hash = "none" ffi = false +# Local Forge execution gas budget; used by tests and local script simulation. See the note on the default +# profile: the pinned HookMiner search needs more than the default 2^30. +gas_limit = 10000000000 + [profile.ci.fuzz] runs = 10000 seed = "0x1" diff --git a/src/ExerciseRouter.sol b/src/ExerciseRouter.sol index a081922..5196fde 100644 --- a/src/ExerciseRouter.sol +++ b/src/ExerciseRouter.sol @@ -23,24 +23,24 @@ import {IActorAwarePeriphery} from "./interfaces/IActorAwarePeriphery.sol"; /// @title ExerciseRouter /// @notice The designated O2 coordinator of one Protected Execution Service. -/// @dev At implementation slice F8C this contract owns four of the five responsibilities the reference +/// @dev At implementation slice F8D this contract owns all five of the responsibilities the reference /// realization assigns it (`implementation-plan.md` §14.0): it accepts the external exercise request, /// it preserves the originating exerciser across the call into the Hook so that the Hook can recover /// an authenticated economic actor, it coordinates exactly one protected PoolManager execution for -/// the authorization the Hook produced, and it resolves that execution — settling the actual input -/// debt from the authenticated exerciser and having the PoolManager deliver exactly `q` directly to -/// the authoritative Beneficiary. Causal finalization is a later slice and is deliberately absent. +/// the authorization the Hook produced, it resolves that execution — settling the actual input debt +/// from the authenticated exerciser and having the PoolManager deliver exactly `q` directly to the +/// authoritative Beneficiary — and it asks the Hook to finalize the exercise it has resolved. /// -/// Because it is absent, no production exercise completes at this slice, and that is enforced rather -/// than merely observed. A completed exercise leaves the Hook's causal context `EXECUTED`, which is -/// transaction-scoped: it disappears when the transaction ends. If this contract returned -/// successfully from an unfinalized exercise, the swap, the payment, and the delivery would all -/// survive while Remaining Entitlement stayed unreduced and the causal proof that could have reduced -/// it evaporated — leaving the commitment exercisable again for the whole quantity it had already -/// been exercised for. The top-level request therefore requires the causal context to have been -/// consumed before it may return, and until finalization exists that requirement is one no exercise -/// can satisfy. The barrier is a completion condition rather than a stub: finalization will consume -/// the context inside the same unlock, and the same condition will then pass unchanged. +/// Asking is the whole of its part in finalization. A completed exercise leaves the Hook's causal +/// context `EXECUTED`, which is transaction-scoped: it disappears when the transaction ends. If this +/// contract returned successfully from an unfinalized exercise, the swap, the payment, and the +/// delivery would all survive while Remaining Entitlement stayed unreduced and the causal proof that +/// could have reduced it evaporated — leaving the commitment exercisable again for the whole quantity +/// it had already been exercised for. The top-level request therefore still requires the causal +/// context to have been consumed before it may return; the request the Hook grants inside the same +/// unlock is what now satisfies it, and the barrier itself is unchanged. What decides whether that +/// request is granted is entirely the Hook's: the actual post-execution backing, the current +/// Remaining Entitlement, and the identity of the commitment the proof is bound to. /// /// It owns no Standby economic truth and cannot acquire any. It does not know whether the commitment /// exists, who its Beneficiary is, who may exercise it, whether it is valid, whether it is @@ -202,16 +202,17 @@ contract ExerciseRouter is IActorAwarePeriphery, IUnlockCallback { /// hold a restored authorization it could execute against again. This one cannot, because it does /// not survive the failure. /// - /// What a resolved exercise establishes is Hook-owned and still narrow: the authorized swap - /// produced exactly `q`, the exerciser paid exactly what the pool charged for it, and the - /// Beneficiary holds exactly `q`. No commitment has been fulfilled and no Remaining Entitlement has - /// been reduced — which is why the request may not return here. The completion barrier requires the - /// Hook's causal context to have been consumed, and nothing at this slice can consume it. + /// What a resolved exercise establishes is Hook-owned and narrow: the authorized swap produced + /// exactly `q`, the exerciser paid exactly what the pool charged for it, and the Beneficiary holds + /// exactly `q`. Fulfillment is a further question and a different one, and the Hook answers it — + /// against the state the exercise actually produced — when this router asks it to finalize. Only a + /// granted request consumes the causal context, which is what lets the completion barrier pass. /// /// The originator context is cleared on the way out, so this router exposes no reusable identity - /// afterwards. The Hook's own causal context is not cleared here, and must not be: whether it may + /// afterwards. The Hook's own causal context is never cleared here, and must not be: whether it may /// be replaced, consumed, or repeated is a Standby economic question the Hook owns, and a router - /// that could clear it would be able to manufacture a second authorization. + /// that could clear it would be able to manufacture a second authorization or fabricate a + /// fulfillment it never earned. /// @param _commitmentId The commitment the caller is asking to exercise. /// @param _q The protected-output quantity the caller is asking to exercise. /// @param _maxInput The most input the caller is willing to pay for that quantity. @@ -220,18 +221,19 @@ contract ExerciseRouter is IActorAwarePeriphery, IUnlockCallback { i_hook.authorizeExercise(_commitmentId, _q); - i_poolManager.unlock(abi.encode(_maxInput)); + i_poolManager.unlock(abi.encode(_commitmentId, _maxInput)); _requireFinalizedExercise(); _endExerciserContext(); } - /// @notice Performs and resolves the one protected execution the Hook's active authorization admits. - /// @dev Only the PoolManager may invoke this. The one thing it carries is the requesting exerciser's - /// own cost bound, which the PoolManager returns verbatim from the unlock this router opened; it - /// is request data, it is checked against authoritative accounting rather than trusted, and it is - /// the only value that crosses this boundary. The swap itself is asked of the Hook rather than + /// @notice Performs, resolves and finalizes the one protected execution the Hook's authorization admits. + /// @dev Only the PoolManager may invoke this. What it carries is the two fields of the request that + /// opened this unlock which the later stages need — the commitment the caller asked to exercise and + /// the caller's own cost bound — returned verbatim by the PoolManager. Both are request data and + /// neither is trusted: the bound is checked against authoritative accounting, and the commitment is + /// checked against the Hook's own causal binding. The swap itself is asked of the Hook rather than /// chosen here or forwarded from the request, and so are the two parties settlement moves value /// between. Direction, exact-output mode, the quantity, the qualification boundary, the payer, and /// the recipient are all Standby facts, and a router that composed its own version of them would @@ -241,16 +243,27 @@ contract ExerciseRouter is IActorAwarePeriphery, IUnlockCallback { /// operation on the authoritative PoolManager callback path against its own causal context, so this /// contract can propose and never authorize. /// + /// The three stages are ordered by what each one needs to be true, and the order is not + /// negotiable: nothing may be settled before the Hook has proven the execution, nothing may be + /// finalized before the Beneficiary has been delivered to, and the caller is authenticated as the + /// PoolManager before either. The request payload is decoded after that authentication, so a + /// direct caller is refused whatever it presents. + /// /// It is `virtual` for one reason, and the reason is not extensibility: the F8B execution evidence /// is verified against a committed real-PoolManager swap that has no settlement behind it, which /// requires a test-only subclass to close the resulting deltas mechanically. Nothing in production /// overrides it. - /// @param _data The encoded exercise-local cost bound of the request that opened this unlock. + /// @param _data The encoded finalization target and exercise-local cost bound of the request that + /// opened this unlock. /// @return result The encoded balance delta of the performed execution. function unlockCallback(bytes calldata _data) external virtual returns (bytes memory result) { (PoolKey memory key, SwapParams memory params, BalanceDelta delta) = _executeAuthorizedExercise(); - _resolveExecutedExercise(key, params, delta, abi.decode(_data, (uint256))); + (uint256 commitmentId, uint256 maxInput) = abi.decode(_data, (uint256, uint256)); + + _resolveExecutedExercise(key, params, delta, maxInput); + + _finalizeExercise(commitmentId); result = abi.encode(delta); } @@ -399,6 +412,28 @@ contract ExerciseRouter is IActorAwarePeriphery, IUnlockCallback { _requireResolvedDelta(_outputCurrency); } + /// @dev Asks the Hook to turn the exercise this unlock resolved into durable fulfillment. + /// + /// The router requests and owns none of it. It names the commitment the request it is coordinating + /// asked for, and the Hook requires that to be exactly the commitment its own causal context is + /// bound to — so what crosses this boundary is a request target being checked against Standby's + /// truth, never a router supplying one. Everything the fulfillment actually turns on is the Hook's + /// and could not be supplied from here: the quantity, the Beneficiary, the exerciser, the service, + /// the current Remaining Entitlement, the Supporting Capacity of the actual post-execution state, + /// and the current Aggregate Capacity Obligation. + /// + /// Nothing is caught. A refused finalization — an unbacked resulting state above all — unwinds the + /// delivery, the settlement, the swap, and the authorization with it, which is the only acceptable + /// outcome: a durable delivery whose fulfillment failed would be output paid for out of backing + /// that was never released. + /// + /// It is `virtual` for one reason, and the reason is not extensibility: proving that settlement and + /// delivery fulfil nothing on their own requires a test-only subclass that omits exactly this step. + /// Nothing in production overrides it. + function _finalizeExercise(uint256 _commitmentId) internal virtual { + i_hook.finalizeExercise(_commitmentId); + } + /// @dev Requires this router's PoolManager delta in one currency to be fully closed. function _requireResolvedDelta(Currency _currency) internal view { int256 remainingDelta = i_poolManager.currencyDelta(address(this), _currency); @@ -410,10 +445,11 @@ contract ExerciseRouter is IActorAwarePeriphery, IUnlockCallback { /// @dev Requires the exercise's causal proof to have been consumed before the request may return. /// - /// The condition is stated as what completion actually requires rather than as a slice marker, so - /// the slice that consumes the context satisfies it by doing its own work and nothing here has to - /// be revisited or removed. Until then every production exercise fails here, atomically, with the - /// swap, the settlement, and the delivery unwound with it. + /// The condition is stated as what completion actually requires rather than as a slice marker, + /// which is why it needed no revision when finalization arrived: a granted finalization consumes + /// the context, and the same requirement passes unchanged. What it still refuses is an exercise + /// that settled and delivered without being finalized — atomically, with the swap, the settlement, + /// and the delivery unwound with it. /// /// It is `virtual` for one reason, and the reason is not extensibility: verifying that the /// settlement and delivery mechanics themselves are correct requires observing a committed diff --git a/src/StandbyHook.sol b/src/StandbyHook.sol index 725a42f..1d91f14 100644 --- a/src/StandbyHook.sol +++ b/src/StandbyHook.sol @@ -39,13 +39,14 @@ import {StandbyMath} from "./libraries/StandbyMath.sol"; /// @title StandbyHook /// @notice The Standby Uniswap v4 Hook. -/// @dev At implementation slice F8B this contract owns the Hook-wide immutable trust basis, one one-shot +/// @dev At implementation slice F8D this contract owns the Hook-wide immutable trust basis, one one-shot /// Protected Execution Service configuration, the authoritative commitment record store with its /// bounded enforcement-reference index, the composition of the authoritative economic derivation /// kernel, the admission or rejection of ordinary O3 backing-affecting pool transitions, the O1 /// admission that turns a proposed commitment into an authoritative binding one, the O2 -/// authorization that binds one exercise attempt into a transaction-scoped causal context, and the -/// O2 classification and execution evidence that bind exactly one PoolManager swap to that context. +/// authorization that binds one exercise attempt into a transaction-scoped causal context, the O2 +/// classification and execution evidence that bind exactly one PoolManager swap to that context, and +/// the O2 finalization that converts that proof into the one durable fulfillment consequence. /// /// Authorization is not exercise. It executes no swap, settles no input, delivers nothing to the /// Beneficiary, fulfils nothing, and reduces no Remaining Entitlement. What authorization establishes @@ -57,8 +58,16 @@ import {StandbyMath} from "./libraries/StandbyMath.sol"; /// causally bound to the active authorization actually produced exactly the authorized protected /// output `q` — and nothing beyond that. It does not say that the input debt has been paid, that the /// exerciser's cost bound was honoured, that the Beneficiary received anything, that the commitment -/// was fulfilled, or that any Remaining Entitlement or Aggregate Capacity Obligation moved. No -/// production path at this slice reduces Remaining Entitlement, so no fulfillment can occur. +/// was fulfilled, or that any Remaining Entitlement or Aggregate Capacity Obligation moved. +/// +/// Fulfillment is what finalization adds, and it is the only thing that reduces Remaining Entitlement. +/// Everything before it is transaction-scoped evidence that would disappear with its transaction; +/// finalization is where the Hook re-derives Supporting Capacity from the state the exercise actually +/// produced, requires that state to remain backed once exactly the delivered obligation is released, +/// reduces the bound commitment's remainder by exactly the bound `q`, and consumes the causal proof so +/// that one execution can never fulfil twice. No other path — ordinary swap, liquidity action, direct +/// transfer, settlement, delivery, expiry, or eligibility change — reduces Remaining Entitlement at +/// all. /// /// What separates a Standby exercise from an ordinary swap is therefore not who asked for it. It is /// the conjunction of an existing Hook-owned authorization, the bound ExerciseRouter as the @@ -208,9 +217,9 @@ contract StandbyHook is BaseHook { /// @notice The lifecycle position of the transaction-scoped O2 causal context. /// @dev The frozen O2 causal lifecycle is `EMPTY -> AUTHORIZED -> EXECUTED -> consumed/EMPTY` - /// (`uniswap-v4-realization.md` §16). This slice owns the arcs up to `EXECUTED`; consumption - /// belongs to finalization and is deliberately not expressible here, because declaring it early - /// would let something claim a transition no code can make. + /// (`uniswap-v4-realization.md` §16). Consumption needs no position of its own: a consumed context + /// is the empty one, cleared field by field, so the lifecycle closes where it opened and there is + /// no terminal marker for anything to mistake for surviving evidence. /// /// Two of these positions are implementation guards rather than causal lifecycle positions, and /// neither carries economic truth of its own. @@ -409,6 +418,27 @@ contract StandbyHook is BaseHook { uint256 enforcementReferenceSlot ); + /// @notice Emitted when a causally proven O2 exercise is finalized into durable fulfillment. + /// @dev Observational evidence of a fulfillment that has already become authoritative. It exists + /// because the causal context that attributes the fulfillment is transaction-scoped and gone by + /// the time anything could read it: what persists afterwards is a reduced Remaining Entitlement, + /// and the reduced remainder alone does not say which exercise, which exerciser, or which delivery + /// discharged it. Nothing later enforces against this event. + /// @param commitmentId The commitment whose Remaining Entitlement was reduced. + /// @param serviceId The Protected Execution Service the exercise was performed under. + /// @param beneficiary The authoritative Beneficiary the protected output was delivered to. + /// @param exerciser The authenticated originating exerciser. + /// @param q The protected-output quantity the fulfillment discharged. + /// @param remainingEntitlement The Remaining Entitlement left after the reduction. + event ExerciseFinalized( + uint256 indexed commitmentId, + PoolId indexed serviceId, + address indexed beneficiary, + address exerciser, + uint256 q, + uint128 remainingEntitlement + ); + /*////////////////////////////////////////////////////////////// ERRORS //////////////////////////////////////////////////////////////*/ @@ -728,6 +758,55 @@ contract StandbyHook is BaseHook { /// @param authorizedQuantity The protected-output quantity the authorization admits. error StandbyHook__ProtectedOutputNotExecuted(int256 actualProtectedOutput, uint256 authorizedQuantity); + /// @notice Thrown when finalization is attempted without a causally proven exercise to finalize. + /// @dev One condition covering every position that is not proof of a completed protected execution. An + /// `EMPTY` context has no exercise at all — which is what a replayed finalization, an ordinary + /// swap, a direct transfer, and a transaction that never exercised all present — while + /// `AUTHORIZING`, `AUTHORIZED` and `EXECUTING` are exercises that have not produced anything yet. + /// None of them may reduce Remaining Entitlement, so none of them is distinguished into a + /// condition it does not have. + /// @param state The causal position the context was actually in. + error StandbyHook__NoProvenExerciseToFinalize(ExerciseAuthorizationState state); + + /// @notice Thrown when finalization does not arrive through the ExerciseRouter the proof is bound to. + /// @dev Distinct from `StandbyHook__NotExerciseRouter`, which reports that an *authorization request* + /// did not arrive through the configured ExerciseRouter, and from + /// `StandbyHook__NotAuthorizedExerciseExecutor`, which reports the same about a proposed swap. + /// This one reports that the account asking for durable fulfillment is not the coordinator the + /// proven exercise was authorized through. + /// @param caller The account that attempted the finalization. + error StandbyHook__NotAuthorizedExerciseFinalizer(address caller); + + /// @notice Thrown when finalization names a commitment other than the one the proof is bound to. + /// @dev The requested target is not authority and never selects what is finalized: the commitment is + /// the one the authorization bound, and a request that names a different one is refused rather + /// than redirected onto the bound commitment it did not ask for. + /// @param requestedCommitmentId The identity the finalization request named. + /// @param provenCommitmentId The identity the causal context is bound to. + error StandbyHook__NotTheProvenExerciseCommitment(uint256 requestedCommitmentId, uint256 provenCommitmentId); + + /// @notice Thrown when the proven quantity exceeds the commitment's current Remaining Entitlement. + /// @dev Distinct from `StandbyHook__InvalidExerciseExtent`, which reports an inadmissible *requested* + /// extent at authorization time. This one revalidates the already-authorized quantity against + /// authoritative state re-read at finalization, so a fulfillment can never discharge more than the + /// commitment still carries however the remainder came to be what it is. + /// @param q The proven protected-output quantity. + /// @param remainingEntitlement The authoritative current unfulfilled remainder. + error StandbyHook__FulfillmentExceedsRemainingEntitlement(uint256 q, uint128 remainingEntitlement); + + /// @notice Thrown when the state a completed exercise actually produced would leave the service + /// unbacked. + /// @dev The fourth and last backing rejection, and the only one comparing two facts about the present. + /// `StandbyHook__InsufficientProspectiveExerciseBacking` compares a *predicted* post-exercise + /// capacity against the obligation a complete exercise would leave, because authorization decides + /// about a swap that has not happened. This one compares the Supporting Capacity of the actual + /// post-execution PoolManager state against the obligation that remains once exactly the delivered + /// quantity is released (RR-O2-20), because by now the swap has happened and the prediction is no + /// longer the authority on anything. + /// @param actualCapacity The Supporting Capacity of the actual post-execution state. + /// @param finalObligation The Aggregate Capacity Obligation the fulfillment would leave. + error StandbyHook__InsufficientFinalExerciseBacking(uint256 actualCapacity, uint256 finalObligation); + /*////////////////////////////////////////////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////*/ @@ -1028,6 +1107,81 @@ contract StandbyHook is BaseHook { ); } + /// @notice Turns one causally proven, executed, settled and delivered O2 exercise into durable + /// fulfillment. + /// @dev The last stage of O2, and the only one that changes persistent Standby economic state. What + /// arrives here is transaction-local proof and nothing more: this Hook authorized exactly one + /// exercise, the PoolManager produced exactly `q` for it, the exerciser paid the debt that + /// execution created, and the Beneficiary holds the output. None of that has fulfilled anything, + /// and if the transaction ended here none of it ever would — the proof is transaction-scoped and + /// would vanish with the transaction that produced it, leaving a paid Beneficiary and an entitlement + /// that had never been exercised. + /// + /// The caller supplies one field and it is not an economic fact. `_commitmentId` is the target the + /// request names, required to be exactly the commitment this Hook bound when it authorized the + /// exercise; it never selects what is finalized. The quantity, the exerciser, the Beneficiary, and + /// the service are recovered from the Hook-owned causal context (RR-O2-18), so there is no field + /// through which any of them could be substituted, and the coordinator asking is required to be + /// the one the proof is bound to. + /// + /// The economic decision is made against the present rather than against the prediction that + /// admitted the exercise. Authorization asked whether the proposed exercise *would* be safe; this + /// asks whether the state the exercise *actually* produced is safe once exactly the delivered + /// obligation is released. Both sides are therefore re-derived here: current Remaining Entitlement + /// from the commitment record, Supporting Capacity from the actual post-execution PoolManager + /// state, and the obligation from the current authoritative aggregate. + /// + /// The consequence is one persistent write, after every check has passed. The admitted extent is + /// never rewritten, and no fulfilled, completed, settled, delivered, or expired flag is introduced: + /// complete fulfillment is `Remaining == 0`, and the released obligation needs no separate record + /// because the aggregate is derived from the remainder that just changed. The bounded reference is + /// deliberately left in place — an exhausted commitment simply stops contributing, and its slot + /// becomes reclaimable by the same predicate that already governs reclamation. + /// + /// The causal context is then consumed, once and completely. That is what makes one proven + /// execution cause at most one entitlement reduction: what consumption leaves behind is the empty + /// context, which proves nothing, so a replayed finalization has nothing to finalize. And it is + /// the only path to this write — an ordinary swap, a liquidity action, a direct transfer to the + /// Beneficiary, settlement alone, delivery alone, expiry, and an eligibility change all reach it + /// through nothing. + /// @param _commitmentId The commitment the request asks to finalize. + function finalizeExercise(uint256 _commitmentId) external { + ExerciseAuthorizationContext memory context = _readExerciseAuthorization(); + + if (context.state != ExerciseAuthorizationState.EXECUTED) { + revert StandbyHook__NoProvenExerciseToFinalize(context.state); + } + + if (msg.sender != context.exerciseRouter) revert StandbyHook__NotAuthorizedExerciseFinalizer(msg.sender); + + if (_commitmentId != context.commitmentId) { + revert StandbyHook__NotTheProvenExerciseCommitment(_commitmentId, context.commitmentId); + } + + uint128 remainingEntitlement = _commitments[context.commitmentId].remainingEntitlement; + + if (context.q > remainingEntitlement) { + revert StandbyHook__FulfillmentExceedsRemainingEntitlement(context.q, remainingEntitlement); + } + + _requireFinalExerciseBacking(context.q); + + uint128 fulfilledRemainder = remainingEntitlement - context.q.toUint128(); + + _writeRemainingEntitlement(context.commitmentId, fulfilledRemainder); + + _consumeExerciseAuthorization(); + + emit ExerciseFinalized( + context.commitmentId, + context.serviceId, + context.beneficiary, + context.exerciser, + context.q, + fulfilledRemainder + ); + } + /// @notice Returns the transaction-scoped O2 causal context this Hook currently holds. /// @dev Observation of Hook-owned state, never a substitute for it. Nothing may treat a value read here /// as authority: authorization is proven by the context the Hook itself consults, and a caller @@ -1656,6 +1810,52 @@ contract StandbyHook is BaseHook { capacity = _prospectiveSupportingCapacity(sqrtPriceX96, liquidity); } + /// @dev Requires the state a completed exercise actually produced to stay backed once `q` is released. + /// + /// Both sides are authoritative and both are read now. The capacity side is current Supporting + /// Capacity, derived by the one kernel from the actual post-execution PoolManager state — never the + /// prospective value authorization was decided on, which was a prediction about a swap that had not + /// happened yet and is not evidence about the state that exists (RR-O2-20). The obligation side is + /// the current authoritative aggregate less the quantity this fulfillment is about to discharge, + /// because Remaining Entitlement still carries `q` at this point and therefore so does the + /// aggregate. + /// + /// The subtraction is checked. Finalization has already established `q <= Remaining`, and a + /// commitment carrying positive Remaining Entitlement that was valid when it was authorized is not + /// permanently non-binding, so its reference cannot have been reclaimed and its full remainder is + /// inside the aggregate: `q <= Remaining <= O` holds structurally. Failing closed on the arithmetic + /// is the correct outcome should that ever cease to be true. + /// + /// Exact sufficiency passes: a fulfillment that leaves capacity exactly equal to the obligation it + /// leaves behind has preserved backing. + function _requireFinalExerciseBacking(uint256 _q) internal view { + uint256 actualCapacity = _supportingCapacity(); + uint256 finalObligation = _aggregateObligation() - _q; + + if (actualCapacity < finalObligation) { + revert StandbyHook__InsufficientFinalExerciseBacking(actualCapacity, finalObligation); + } + } + + /// @dev Consumes the causal context, returning it to the empty position with every binding cleared. + /// + /// The whole context is cleared rather than only its position. A context that reported `EMPTY` + /// while still carrying a commitment, an actor, a Beneficiary, or a quantity would be exactly the + /// reusable evidence consumption exists to destroy. + function _consumeExerciseAuthorization() internal { + _writeExerciseAuthorization( + ExerciseAuthorizationContext({ + state: ExerciseAuthorizationState.EMPTY, + serviceId: PoolId.wrap(bytes32(0)), + commitmentId: 0, + exerciseRouter: address(0), + exerciser: address(0), + beneficiary: address(0), + q: 0 + }) + ); + } + /// @dev Writes the complete authorized causal context, replacing the in-flight marker. function _writeExerciseAuthorization(ExerciseAuthorizationContext memory _context) internal { _writeExerciseAuthorizationState(_context.state); @@ -1802,8 +2002,9 @@ contract StandbyHook is BaseHook { /// /// Remaining Entitlement is the one mutable commitment fact, and this is the only mechanism that /// changes it. The mechanism applies no economic rule — it does not decide what the new value - /// should be, does not require it to decrease, and does not bound it by the admitted extent. The - /// authoritative reduction and its causal justification belong to the fulfillment slice. + /// should be, does not require it to decrease, and does not bound it by the admitted extent. + /// Whether a reduction is causally justified, and by how much, is `finalizeExercise`'s question, + /// and it is answered in full before this runs. /// /// Expiry and eligibility deliberately have no path to this function. A commitment that has /// passed `validUntil`, or whose Beneficiary has lost eligibility, keeps the Remaining diff --git a/test/fuzz/ExerciseFinalizationFuzz.t.sol b/test/fuzz/ExerciseFinalizationFuzz.t.sol new file mode 100644 index 0000000..6541e05 --- /dev/null +++ b/test/fuzz/ExerciseFinalizationFuzz.t.sol @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +/*////////////////////////////////////////////////////////////// + IMPORTS +//////////////////////////////////////////////////////////////*/ + +import {StandbyFixtureConfig} from "../../script/helpers/StandbyFixtureConfig.sol"; + +import {BaseExerciseFinalizationTest} from "../shared/BaseExerciseFinalizationTest.t.sol"; + +/*////////////////////////////////////////////////////////////// + CONTRACTS +//////////////////////////////////////////////////////////////*/ + +/// @notice Fuzz evidence that durable fulfillment is exact across the whole admissible quantity domain +/// (G8D-B, G8D-C, G8D-D). +/// @dev The fulfillment consequence is one subtraction, and a subtraction is the kind of thing that is +/// right almost everywhere and wrong at an edge — at one raw unit, at the whole remainder, and at the +/// point where releasing the obligation is the only reason the resulting state is still backed. Every +/// run below performs a complete production exercise on the real pinned execution stack: a real +/// authorization, a real swap, a real settlement out of the exerciser's own account, a real delivery +/// to the Beneficiary, and a real finalization. +/// +/// The expected outcome of every run is stated as arithmetic over the admitted extent and the +/// quantities the runs themselves chose, never read back from the derivation under test. Quantities +/// are bounded by what the frozen fixture can actually back rather than by what is convenient, so the +/// admissible domain is covered to both of its endpoints. +contract ExerciseFinalizationFuzzTest is BaseExerciseFinalizationTest { + /*////////////////////////////////////////////////////////////// + STATE VARIABLES + //////////////////////////////////////////////////////////////*/ + + /// @dev The canonical commitment entitlement: 50,000 MockUSDC. + uint128 internal constant CANONICAL_ENTITLEMENT = uint128(StandbyFixtureConfig.CANONICAL_COMMITMENT_Q); + + /*////////////////////////////////////////////////////////////// + EXACT FULFILLMENT CONSEQUENCE + //////////////////////////////////////////////////////////////*/ + + /// @notice Any admissible quantity is fulfilled exactly, and nothing else about the commitment moves. + function testFuzz_completedExercise_fulfilsExactlyTheExercisedQuantity(uint256 _qSeed) public { + uint256 q = bound(_qSeed, 1, CANONICAL_ENTITLEMENT); + + uint256 commitmentId = _establishExercisable(CANONICAL_ENTITLEMENT); + + SettlementState memory before = _settlementState(commitmentExerciseAuthority, commitmentId); + + _authorizeAs(commitmentExerciseAuthority, commitmentId, q); + + _assertExactFulfillment(before, commitmentExerciseAuthority, commitmentId, q); + } + + /// @notice Two successive exercises leave the exact arithmetic remainder and deliver the exact sum. + /// @dev The second quantity is chosen against the remainder the first one produced, which is the whole + /// point: an implementation that fulfilled against the admitted extent rather than the current + /// remainder would pass the single-exercise case and fail here. + function testFuzz_sequentialExercises_produceTheExactRemainder(uint256 _firstSeed, uint256 _secondSeed) public { + uint256 first = bound(_firstSeed, 1, CANONICAL_ENTITLEMENT - 1); + uint256 second = bound(_secondSeed, 1, CANONICAL_ENTITLEMENT - first); + + uint256 commitmentId = _establishExercisable(CANONICAL_ENTITLEMENT); + + SettlementState memory before = _settlementState(commitmentExerciseAuthority, commitmentId); + + _authorizeAs(commitmentExerciseAuthority, commitmentId, first); + _authorizeAs(commitmentExerciseAuthority, commitmentId, second); + + SettlementState memory current = _settlementState(commitmentExerciseAuthority, commitmentId); + + assertEq( + uint256(current.remainingEntitlement), + uint256(CANONICAL_ENTITLEMENT) - first - second, + "the remainder must be the admitted extent less everything fulfilled against it" + ); + assertEq( + uint256(current.originalEntitlement), + uint256(CANONICAL_ENTITLEMENT), + "no sequence of fulfillments may rewrite the admitted extent" + ); + assertEq( + current.aggregateObligation, + before.aggregateObligation - first - second, + "the derived obligation must release exactly the sum of what was fulfilled" + ); + assertEq( + current.beneficiaryOutput - before.beneficiaryOutput, + first + second, + "the Beneficiary must receive exactly the sum of what was fulfilled" + ); + } + + /// @notice Any admissible entitlement can be exercised to exactly zero, releasing its whole obligation. + /// @dev The extent is fuzzed as well as the quantity, up to everything the canonical geometry can back, + /// so exhaustion is exercised across the admissible commitment domain rather than at one size. What + /// it must produce is derived and not flagged: a zero remainder, a zero commitment obligation, and + /// a zero aggregate. + function testFuzz_anyAdmissibleEntitlement_canBeExhaustedExactly(uint128 _entitlementSeed) public { + uint128 entitlement = uint128(bound(_entitlementSeed, 1, _referenceSupportingCapacity())); + + uint256 commitmentId = _establishExercisable(entitlement); + + SettlementState memory before = _settlementState(commitmentExerciseAuthority, commitmentId); + + _authorizeAs(commitmentExerciseAuthority, commitmentId, entitlement); + + _assertExactFulfillment(before, commitmentExerciseAuthority, commitmentId, entitlement); + + assertEq( + uint256(_commitmentRecord(commitmentId).remainingEntitlement), + 0, + "exhausting an entitlement must leave exactly zero" + ); + assertEq(hook.commitmentObligation(commitmentId), 0, "an exhausted commitment must carry no obligation"); + assertEq(hook.aggregateObligation(), 0, "the aggregate must release the exhausted commitment entirely"); + } + + /// @notice A completed exercise of any admissible quantity leaves no reusable causal evidence. + function testFuzz_completedExercise_leavesNoReusableCausalEvidence(uint256 _qSeed) public { + uint256 q = bound(_qSeed, 1, CANONICAL_ENTITLEMENT); + + uint256 commitmentId = _establishExercisable(CANONICAL_ENTITLEMENT); + + _authorizeAs(commitmentExerciseAuthority, commitmentId, q); + + _assertNoAuthorizationContext("a finalized exercise must consume its causal proof entirely"); + } + + /// @notice The service remains backed by both independent reconstructions after any fulfillment. + /// @dev Both derivations are checked against oracles that recompute them from authoritative PoolManager + /// state and from the persisted commitment facts, so a fulfillment that moved the remainder and the + /// capacity consistently but wrongly still fails here. + function testFuzz_fulfilledService_remainsBackedByIndependentDerivation(uint256 _qSeed) public { + uint256 q = bound(_qSeed, 1, CANONICAL_ENTITLEMENT); + + uint256 commitmentId = _establishExercisable(CANONICAL_ENTITLEMENT); + + _authorizeAs(commitmentExerciseAuthority, commitmentId, q); + + _assertDerivationsMatchOracles("the fulfilled state must match both independent reconstructions"); + + assertGe( + _referenceSupportingCapacity(), + _referenceAggregateObligation(), + "the independently derived state must remain backed after fulfillment" + ); + } +} diff --git a/test/harness/MisdirectedFinalizationRouter.sol b/test/harness/MisdirectedFinalizationRouter.sol new file mode 100644 index 0000000..3d125dc --- /dev/null +++ b/test/harness/MisdirectedFinalizationRouter.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +/*////////////////////////////////////////////////////////////// + IMPORTS +//////////////////////////////////////////////////////////////*/ + +import {ExerciseRouter} from "../../src/ExerciseRouter.sol"; +import {StandbyHook} from "../../src/StandbyHook.sol"; + +/*////////////////////////////////////////////////////////////// + CONTRACTS +//////////////////////////////////////////////////////////////*/ + +/// @notice The production `ExerciseRouter` with its finalization request pointed at a chosen commitment. +/// @dev Economic atomicity has to hold when finalization *fails*, and a finalization can only fail for +/// reasons the production system cannot produce: a resulting state that is no longer backed, a +/// remainder smaller than the proven quantity, or a request naming a commitment the proof is not bound +/// to. The first two are unreachable because the invariant they would violate is the one every earlier +/// transition already maintains. The third is unreachable because the production router names the +/// commitment its own request was for — which is exactly what this contract stops doing. +/// +/// Nothing else is changed. The authorization, the unlock, the protected execution, the input-debt +/// derivation, the `maxInput` comparison, the exerciser-funded settlement, the direct Beneficiary +/// delivery, both delta-closure requirements, and the completion barrier are all inherited production +/// code running unmodified against the real PoolManager — so what a refused finalization unwinds here +/// is a genuine committed swap, a genuine payment, and a genuine delivery. +/// +/// A misdirected request is not a Standby capability and no test may read it as one. What it is for is +/// the property on the other side of the refusal: that nothing durable survives it. +contract MisdirectedFinalizationRouter is ExerciseRouter { + /*////////////////////////////////////////////////////////////// + STATE VARIABLES + //////////////////////////////////////////////////////////////*/ + + /// @dev The commitment every finalization request names, whatever the exercise was actually for. + uint256 private _finalizationTarget; + + /*////////////////////////////////////////////////////////////// + CONSTRUCTOR + //////////////////////////////////////////////////////////////*/ + + /// @notice Deploys the misdirecting router against the same Hook the production router binds. + /// @param _hook The StandbyHook that owns the Protected Execution Service. + constructor(StandbyHook _hook) ExerciseRouter(_hook) {} + + /*////////////////////////////////////////////////////////////// + EXTERNAL FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /// @notice Chooses the commitment the next finalization request will name. + /// @param _commitmentId The identity to name. + function setFinalizationTarget(uint256 _commitmentId) external { + _finalizationTarget = _commitmentId; + } + + /*////////////////////////////////////////////////////////////// + INTERNAL FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /// @dev Names the chosen commitment instead of the one the request was for. + function _finalizeExercise(uint256) internal override { + i_hook.finalizeExercise(_finalizationTarget); + } +} diff --git a/test/harness/NonFinalizingExerciseRouter.sol b/test/harness/NonFinalizingExerciseRouter.sol new file mode 100644 index 0000000..4d13205 --- /dev/null +++ b/test/harness/NonFinalizingExerciseRouter.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +/*////////////////////////////////////////////////////////////// + IMPORTS +//////////////////////////////////////////////////////////////*/ + +import {ExerciseRouter} from "../../src/ExerciseRouter.sol"; +import {StandbyHook} from "../../src/StandbyHook.sol"; + +/*////////////////////////////////////////////////////////////// + CONTRACTS +//////////////////////////////////////////////////////////////*/ + +/// @notice The production `ExerciseRouter` with its finalization request removed and its completion +/// barrier intact. +/// @dev Production now asks the Hook to finalize every exercise it resolves, so a production exercise that +/// reaches the completion barrier with an unconsumed causal context no longer exists. The barrier is +/// nonetheless production code enforcing a production requirement — an exercise that settled and +/// delivered without being finalized must not commit — and a requirement whose failing case has become +/// unreachable is not a requirement that has stopped mattering. This contract constructs that case. +/// +/// It removes exactly one thing: the request for finalization. `exercise`, the originator attribution, +/// the authorization request, the unlock, the PoolManager authentication, the protected execution, the +/// causal-context read, the input-debt derivation, the `maxInput` comparison, the exerciser-funded +/// settlement, the direct Beneficiary delivery, both delta-closure requirements, and the completion +/// barrier itself are all inherited production code running unmodified. +/// +/// What that establishes is narrow and is the point: with nothing consuming the causal proof, the +/// barrier refuses, and the swap, the payment, and the delivery unwind with the refusal. Nothing +/// observed through this contract is evidence about what a production exercise does, and no test may +/// present it as such. +contract NonFinalizingExerciseRouter is ExerciseRouter { + /*////////////////////////////////////////////////////////////// + CONSTRUCTOR + //////////////////////////////////////////////////////////////*/ + + /// @notice Deploys the non-finalizing router against the same Hook the production router binds. + /// @param _hook The StandbyHook that owns the Protected Execution Service. + constructor(StandbyHook _hook) ExerciseRouter(_hook) {} + + /*////////////////////////////////////////////////////////////// + INTERNAL FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /// @dev Omits the finalization request, so no causal proof is ever consumed. + function _finalizeExercise(uint256) internal pure override {} +} diff --git a/test/harness/UnfinalizedExerciseRouter.sol b/test/harness/UnfinalizedExerciseRouter.sol index 1688106..2c37872 100644 --- a/test/harness/UnfinalizedExerciseRouter.sol +++ b/test/harness/UnfinalizedExerciseRouter.sol @@ -10,33 +10,34 @@ import {Currency} from "v4-core/types/Currency.sol"; import {PoolKey} from "v4-core/types/PoolKey.sol"; import {SwapParams} from "v4-core/types/PoolOperation.sol"; -import {ExerciseRouter} from "../../src/ExerciseRouter.sol"; import {StandbyHook} from "../../src/StandbyHook.sol"; +import {NonFinalizingExerciseRouter} from "./NonFinalizingExerciseRouter.sol"; + /*////////////////////////////////////////////////////////////// CONTRACTS //////////////////////////////////////////////////////////////*/ -/// @notice Lets a production settlement and delivery sequence commit while finalization does not exist. -/// @dev The production `ExerciseRouter` refuses to return from an exercise whose causal proof has not been -/// consumed, and nothing at this slice can consume it, so no production exercise commits. That refusal -/// is a Standby safety requirement rather than an obstacle — but it also means the settlement and -/// delivery mechanics it protects could not otherwise be observed at all, because every transaction -/// that performs them is unwound before anything can be measured. +/// @notice Lets a production settlement and delivery sequence commit without being finalized. +/// @dev The production `ExerciseRouter` asks the Hook to finalize every exercise it resolves, and then +/// refuses to return from one whose causal proof was not consumed. Both are Standby safety +/// requirements rather than obstacles — but together they mean the settlement and delivery mechanics +/// between them cannot be observed *on their own*: a production transaction either finalizes, in which +/// case what is measured afterwards includes the fulfillment, or it unwinds before anything can be +/// measured at all. /// -/// This contract is that observation and nothing more. It is the production `ExerciseRouter` with the -/// completion barrier lifted and with no other difference whatsoever: `exercise`, the originator -/// attribution, the authorization request, the unlock, the PoolManager authentication, the protected -/// execution, the causal-context read, the input-debt derivation, the `maxInput` comparison, the -/// exerciser-funded settlement, the direct Beneficiary delivery, and both delta-closure requirements -/// are all inherited production code running unmodified. +/// This contract is that observation and nothing more. It is the non-finalizing production router with +/// the completion barrier additionally lifted, and with no other difference whatsoever: `exercise`, +/// the originator attribution, the authorization request, the unlock, the PoolManager authentication, +/// the protected execution, the causal-context read, the input-debt derivation, the `maxInput` +/// comparison, the exerciser-funded settlement, the direct Beneficiary delivery, and both +/// delta-closure requirements are all inherited production code running unmodified. /// -/// What it removes is exactly one thing, and removing it establishes exactly one thing: that an -/// exercise which has settled and delivered has not been finalized. It does not reduce Remaining -/// Entitlement, release any obligation, attribute any fulfillment, or consume the Hook's causal -/// context — because it implements none of that, and neither does the production code it inherits. A -/// committed exercise here leaves the causal context `EXECUTED`, which is precisely the state the -/// production barrier exists to refuse. +/// What the two removals establish together is exactly one thing: that an exercise which has settled +/// and delivered has not been fulfilled. It reduces no Remaining Entitlement, releases no obligation, +/// attributes no fulfillment, and consumes no causal context — because it asks for none of that, and +/// the production code it inherits does none of it by itself. A committed exercise here leaves the +/// causal context `EXECUTED`, which is precisely the state the production barrier exists to refuse. /// /// Nothing observed through this contract is evidence that a production O2 completes, and no test may /// present it as such. What it is evidence for is what the production R3 and R4 mechanics do, measured @@ -47,14 +48,14 @@ import {StandbyHook} from "../../src/StandbyHook.sol"; /// adds no check, removes none, and re-implements nothing. It exists because the derivation's whole /// job is to interpret a signed `BalanceDelta` correctly, and the real PoolManager will only ever hand /// it well-formed ones — so the malformed cases the derivation must refuse are unreachable without it. -contract UnfinalizedExerciseRouter is ExerciseRouter { +contract UnfinalizedExerciseRouter is NonFinalizingExerciseRouter { /*////////////////////////////////////////////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////*/ /// @notice Deploys the unfinalized router against the same Hook the production router binds. /// @param _hook The StandbyHook that owns the Protected Execution Service. - constructor(StandbyHook _hook) ExerciseRouter(_hook) {} + constructor(StandbyHook _hook) NonFinalizingExerciseRouter(_hook) {} /*////////////////////////////////////////////////////////////// EXTERNAL FUNCTIONS @@ -100,6 +101,7 @@ contract UnfinalizedExerciseRouter is ExerciseRouter { INTERNAL FUNCTIONS //////////////////////////////////////////////////////////////*/ - /// @dev Lifts the completion barrier, so a settled and delivered exercise commits unfinalized. + /// @dev Lifts the completion barrier, so the settled and delivered exercise its non-finalizing parent + /// produces commits instead of unwinding. function _requireFinalizedExercise() internal view override {} } diff --git a/test/integration/ExerciseFinalization.t.sol b/test/integration/ExerciseFinalization.t.sol new file mode 100644 index 0000000..c520b9f --- /dev/null +++ b/test/integration/ExerciseFinalization.t.sol @@ -0,0 +1,510 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +/*////////////////////////////////////////////////////////////// + IMPORTS +//////////////////////////////////////////////////////////////*/ + +import {StandbyFixtureConfig} from "../../script/helpers/StandbyFixtureConfig.sol"; + +import {StandbyHook} from "../../src/StandbyHook.sol"; + +import {MisdirectedFinalizationRouter} from "../harness/MisdirectedFinalizationRouter.sol"; +import {BaseExerciseFinalizationTest} from "../shared/BaseExerciseFinalizationTest.t.sol"; + +/*////////////////////////////////////////////////////////////// + CONTRACTS +//////////////////////////////////////////////////////////////*/ + +/// @notice Integration evidence for F8D durable fulfillment on the complete production path (G8D-A +/// through G8D-F). +/// @dev Every exercise here is requested through the production `ExerciseRouter`, authorized by the +/// production Hook, executed by the real pinned `PoolManager` against a real pool holding real +/// liquidity, settled from the exerciser's own account, delivered directly to the Beneficiary, and +/// finalized by the production Hook — for a commitment the production O1 transition admitted. Nothing +/// is seeded, no harness participates, and no barrier is lifted. +/// +/// That is what makes these the first suites in the repository whose subject is a *committed* Standby +/// exercise. Everything they assert afterwards is read back from authoritative state: the commitment +/// record, the derived obligation, the derived capacity, real token balances, and the Hook's own +/// causal context. +contract ExerciseFinalizationTest is BaseExerciseFinalizationTest { + /*////////////////////////////////////////////////////////////// + STATE VARIABLES + //////////////////////////////////////////////////////////////*/ + + /// @dev The canonical commitment entitlement: 50,000 MockUSDC. + uint128 internal constant CANONICAL_ENTITLEMENT = uint128(StandbyFixtureConfig.CANONICAL_COMMITMENT_Q); + + /// @dev The exercise quantity used wherever the test is not about the quantity: 20,000 MockUSDC. + uint256 internal constant EXERCISE_Q = 20_000_000_000; + + /*////////////////////////////////////////////////////////////// + G8D-C, G8D-F — EXACT PARTIAL FULFILLMENT + //////////////////////////////////////////////////////////////*/ + + /// @notice Proves a completed exercise reduces Remaining Entitlement by exactly the exercised quantity. + /// @dev The canonical positive case, end to end. What the Beneficiary received and what the commitment + /// gave up are asserted against each other in the same breath, because a fulfillment that + /// discharged a different amount than it delivered would be exactly the failure that matters here. + /// The admitted extent is untouched, the derived obligation released exactly the same quantity, + /// and the emitted attribution names the commitment, the Beneficiary, the exerciser, and the + /// remainder the reduction produced. + function test_completedExercise_reducesRemainingByExactlyQ() public { + uint256 commitmentId = _establishExercisable(CANONICAL_ENTITLEMENT); + + SettlementState memory before = _settlementState(commitmentExerciseAuthority, commitmentId); + + vm.expectEmit(true, true, true, true, address(hook)); + emit StandbyHook.ExerciseFinalized( + commitmentId, + servicePoolId, + beneficiary, + commitmentExerciseAuthority, + EXERCISE_Q, + CANONICAL_ENTITLEMENT - uint128(EXERCISE_Q) + ); + + _authorizeAs(commitmentExerciseAuthority, commitmentId, EXERCISE_Q); + + _assertExactFulfillment(before, commitmentExerciseAuthority, commitmentId, EXERCISE_Q); + } + + /// @notice Proves a one-raw-unit exercise fulfils exactly one raw unit. + /// @dev The smallest fulfillment the protocol admits. It still performs a real swap, still settles a + /// real debt, and still moves the remainder by exactly one. + function test_minimalExercise_fulfilsExactlyOneRawUnit() public { + uint256 commitmentId = _establishExercisable(CANONICAL_ENTITLEMENT); + + SettlementState memory before = _settlementState(commitmentExerciseAuthority, commitmentId); + + _authorizeAs(commitmentExerciseAuthority, commitmentId, 1); + + _assertExactFulfillment(before, commitmentExerciseAuthority, commitmentId, 1); + } + + /// @notice Proves successive partial exercises leave the exact arithmetic remainder. + /// @dev Three separate complete exercises against one commitment, each with its own authorization, its + /// own swap, its own settlement, its own delivery, and its own finalization. The remainder after + /// each is the exact difference, and the Beneficiary's total is the exact sum — so no exercise + /// double-counts, rounds, or drifts against the others. + function test_sequentialPartialExercises_produceTheExactRemainder() public { + uint256 commitmentId = _establishExercisable(CANONICAL_ENTITLEMENT); + + uint256 beneficiaryBefore = usdc.balanceOf(beneficiary); + + _authorizeAs(commitmentExerciseAuthority, commitmentId, EXERCISE_Q); + _assertRemaining(commitmentId, CANONICAL_ENTITLEMENT - uint128(EXERCISE_Q), "after one partial exercise"); + + _authorizeAs(commitmentExerciseAuthority, commitmentId, EXERCISE_Q); + _assertRemaining(commitmentId, CANONICAL_ENTITLEMENT - uint128(2 * EXERCISE_Q), "after two partial exercises"); + + _authorizeAs(commitmentExerciseAuthority, commitmentId, 1); + _assertRemaining( + commitmentId, CANONICAL_ENTITLEMENT - uint128(2 * EXERCISE_Q) - 1, "after three partial exercises" + ); + + assertEq( + usdc.balanceOf(beneficiary) - beneficiaryBefore, + 2 * EXERCISE_Q + 1, + "the Beneficiary must hold exactly the sum of what was fulfilled" + ); + assertEq( + uint256(_commitmentRecord(commitmentId).originalEntitlement), + uint256(CANONICAL_ENTITLEMENT), + "no number of exercises may rewrite the admitted extent" + ); + } + + /*////////////////////////////////////////////////////////////// + G8D-C — EXACT EXHAUSTION + //////////////////////////////////////////////////////////////*/ + + /// @notice Proves exercising the whole remainder leaves exactly zero and releases the whole obligation. + /// @dev Completion is derived, not flagged. The commitment's own obligation and the service aggregate + /// both reach zero because they are computed from a remainder that reached zero, and the bounded + /// reference is deliberately still there — an exhausted commitment stops contributing without + /// anything being sent to clear it. + function test_exhaustingExercise_leavesZeroRemainingAndZeroObligation() public { + uint256 commitmentId = _establishExercisable(CANONICAL_ENTITLEMENT); + + (bool referenced, uint256 slot) = _referenceSlotOf(commitmentId); + + assertTrue(referenced, "the admitted commitment must be referenced before it is fulfilled"); + + SettlementState memory before = _settlementState(commitmentExerciseAuthority, commitmentId); + + _authorizeAs(commitmentExerciseAuthority, commitmentId, CANONICAL_ENTITLEMENT); + + _assertExactFulfillment(before, commitmentExerciseAuthority, commitmentId, CANONICAL_ENTITLEMENT); + _assertRemaining(commitmentId, 0, "exact exhaustion"); + + assertEq(hook.commitmentObligation(commitmentId), 0, "an exhausted commitment must carry no obligation"); + assertEq(hook.aggregateObligation(), 0, "the aggregate must release it entirely"); + + _assertReferenceRetained(commitmentId, slot, "fulfillment must not clear the bounded reference"); + } + + /// @notice Proves an exhausted commitment cannot be exercised again. + /// @dev In a later transaction, with the exerciser still funded and the service still backed. The + /// refusal is the ordinary extent predicate reporting a remainder of zero, which is what "complete + /// fulfillment is derived from `Remaining == 0`" has to mean operationally. + function test_exhaustedCommitment_cannotBeExercisedAgain() public { + uint256 commitmentId = _establishExercisable(CANONICAL_ENTITLEMENT); + + _authorizeAs(commitmentExerciseAuthority, commitmentId, CANONICAL_ENTITLEMENT); + + SettlementState memory before = _settlementState(commitmentExerciseAuthority, commitmentId); + + vm.expectRevert(abi.encodeWithSelector(StandbyHook.StandbyHook__InvalidExerciseExtent.selector, 1, 0)); + _authorizeAs(commitmentExerciseAuthority, commitmentId, 1); + + _assertNoExerciseResidue(before, commitmentExerciseAuthority, commitmentId); + } + + /// @notice Proves a partially fulfilled commitment admits exactly its remainder and no more. + /// @dev Both sides of the boundary, against the remainder a previous exercise produced rather than + /// against the admitted extent — which is the difference between an entitlement that was partly + /// used and one that was not. + function test_partiallyFulfilledCommitment_admitsExactlyItsRemainder() public { + uint256 commitmentId = _establishExercisable(CANONICAL_ENTITLEMENT); + + _authorizeAs(commitmentExerciseAuthority, commitmentId, EXERCISE_Q); + + uint128 remainder = CANONICAL_ENTITLEMENT - uint128(EXERCISE_Q); + + vm.expectRevert( + abi.encodeWithSelector( + StandbyHook.StandbyHook__InvalidExerciseExtent.selector, uint256(remainder) + 1, remainder + ) + ); + _authorizeAs(commitmentExerciseAuthority, commitmentId, uint256(remainder) + 1); + + _authorizeAs(commitmentExerciseAuthority, commitmentId, remainder); + + _assertRemaining(commitmentId, 0, "the exact remainder must be exercisable"); + } + + /*////////////////////////////////////////////////////////////// + G8D-B — FINAL BACKING AGAINST THE ACTUAL STATE + //////////////////////////////////////////////////////////////*/ + + /// @notice Proves an exercise that leaves capacity exactly equal to the obligation succeeds. + /// @dev The commitment is admitted for the whole of the service's current Supporting Capacity, taken + /// from the independent oracle rather than from the derivation under test, so the service sits + /// exactly at `S == O` before the exercise and exactly at `S == O` after it. Equality has to pass + /// at both the authorization comparison and the final one, and a fulfillment refused here would be + /// one the frozen requirement admits. + function test_exerciseAtExactBacking_succeeds() public { + uint128 fullyBackedEntitlement = uint128(_referenceSupportingCapacity()); + + uint256 commitmentId = _establishExercisable(fullyBackedEntitlement); + + assertEq(hook.supportingCapacity(), hook.aggregateObligation(), "the service must start exactly at backing"); + + SettlementState memory before = _settlementState(commitmentExerciseAuthority, commitmentId); + + _authorizeAs(commitmentExerciseAuthority, commitmentId, EXERCISE_Q); + + _assertExactFulfillment(before, commitmentExerciseAuthority, commitmentId, EXERCISE_Q); + + assertEq(hook.supportingCapacity(), hook.aggregateObligation(), "the service must remain exactly at backing"); + _assertDerivationsMatchOracles("both derivations must still match their independent reconstructions"); + } + + /*////////////////////////////////////////////////////////////// + G8D-D — ONLY THE CAUSAL O2 PATH CAN FULFIL + //////////////////////////////////////////////////////////////*/ + + /// @notice Proves a completed exercise leaves no reusable causal evidence behind. + /// @dev The state the F8C completion barrier exists to refuse is `EXECUTED`; what a finalized exercise + /// leaves is the empty context, every field cleared. That is both what lets the barrier pass and + /// what makes the proof unusable a second time. + function test_completedExercise_leavesNoReusableCausalEvidence() public { + uint256 commitmentId = _establishExercisable(CANONICAL_ENTITLEMENT); + + _authorizeAs(commitmentExerciseAuthority, commitmentId, EXERCISE_Q); + + _assertNoAuthorizationContext("a finalized exercise must consume its causal proof entirely"); + } + + /// @notice Proves a direct finalization request outside any exercise fulfils nothing. + /// @dev The Hook's finalization surface is externally reachable, and it has to be: the coordinator calls + /// it. Reaching it is not the same as satisfying it, and outside an exercise there is no proof to + /// present — for the configured router exactly as for anyone else. + function test_directFinalizationOutsideAnExercise_fulfilsNothing() public { + uint256 commitmentId = _establishExercisable(CANONICAL_ENTITLEMENT); + + SettlementState memory before = _settlementState(commitmentExerciseAuthority, commitmentId); + + _expectUnprovenRefusal(); + vm.prank(address(configuredExerciseRouter)); + hook.finalizeExercise(commitmentId); + + _expectUnprovenRefusal(); + vm.prank(commitmentExerciseAuthority); + hook.finalizeExercise(commitmentId); + + _expectUnprovenRefusal(); + vm.prank(unauthorizedExerciser); + hook.finalizeExercise(commitmentId); + + _assertNoFulfillment(before, commitmentExerciseAuthority, commitmentId, "no direct request may fulfil anything"); + } + + /// @notice Proves an ordinary swap fulfils nothing, however much protected output it produces. + /// @dev An authoritative protected-direction transition that moves the price, consumes Supporting + /// Capacity, and hands an eligible trader the same currency a Beneficiary would receive. It + /// discharges no entitlement, because fulfillment is causal rather than a matter of the right + /// currency moving. + function test_ordinarySwap_fulfilsNothing() public { + uint256 commitmentId = _establishExercisable(CANONICAL_ENTITLEMENT); + + SettlementState memory before = _settlementState(commitmentExerciseAuthority, commitmentId); + + _swapAs(eligibleTrader, _protectedExactOutputSwapParams(EXERCISE_Q)); + + assertLt( + hook.supportingCapacity(), before.aggregateObligation + EXERCISE_Q, "the swap must have moved capacity" + ); + + _assertNoFulfillment(before, commitmentExerciseAuthority, commitmentId, "an ordinary swap must fulfil nothing"); + } + + /// @notice Proves a direct transfer of the protected currency to the Beneficiary fulfils nothing. + /// @dev The Beneficiary ends up holding exactly what a fulfilled exercise would have delivered, from an + /// account willing to send it. The commitment is untouched: what discharges an entitlement is the + /// causally attributable O2 path, not the Beneficiary's balance. + function test_directTransferToTheBeneficiary_fulfilsNothing() public { + uint256 commitmentId = _establishExercisable(CANONICAL_ENTITLEMENT); + + SettlementState memory before = _settlementState(commitmentExerciseAuthority, commitmentId); + + vm.prank(eligibleTrader); + bool sent = usdc.transfer(beneficiary, EXERCISE_Q); + + assertTrue(sent, "the fixture must be able to pay the Beneficiary directly"); + assertEq( + usdc.balanceOf(beneficiary) - before.beneficiaryOutput, + EXERCISE_Q, + "the Beneficiary must hold exactly what a fulfillment would have delivered" + ); + + _assertNoFulfillment(before, commitmentExerciseAuthority, commitmentId, "a direct transfer must fulfil nothing"); + } + + /// @notice Proves a refused exercise fulfils nothing and leaves no residue anywhere. + /// @dev The cost bound is one raw unit below the debt the swap actually produced, so the exercise + /// completes authorization, execution, settlement and delivery before it fails — and then unwinds + /// all of it, the finalization included. + function test_refusedExercise_fulfilsNothing() public { + uint256 commitmentId = _establishExercisable(CANONICAL_ENTITLEMENT); + + uint256 actualInput = _probeActualInput(commitmentExerciseAuthority, commitmentId, EXERCISE_Q); + + SettlementState memory before = _settlementState(commitmentExerciseAuthority, commitmentId); + + vm.expectRevert(); + _authorizeAs(commitmentExerciseAuthority, commitmentId, EXERCISE_Q, actualInput - 1); + + _assertNoExerciseResidue(before, commitmentExerciseAuthority, commitmentId); + } + + /*////////////////////////////////////////////////////////////// + G8D-E — FULFILLMENT AND DELIVERY ARE ATOMIC + //////////////////////////////////////////////////////////////*/ + + /// @notice Proves the exerciser's inability to pay leaves no delivery and no fulfillment. + /// @dev The one direction of the atomicity requirement that a real failure can demonstrate on the + /// production path: everything up to the payment succeeded, and after the refusal there is no + /// delivery without fulfillment and no fulfillment without delivery — there is neither. + function test_unpayableExercise_leavesNeitherDeliveryNorFulfillment() public { + uint256 commitmentId = _establishExercisable(CANONICAL_ENTITLEMENT); + + _drainExerciser(commitmentExerciseAuthority); + + SettlementState memory before = _settlementState(commitmentExerciseAuthority, commitmentId); + + vm.expectRevert(); + _authorizeAs(commitmentExerciseAuthority, commitmentId, EXERCISE_Q); + + _assertNoExerciseResidue(before, commitmentExerciseAuthority, commitmentId); + } + + /*////////////////////////////////////////////////////////////// + G8D-F — CANONICAL A4 TERMINAL STATE + //////////////////////////////////////////////////////////////*/ + + /// @notice Proves the canonical A1–A4 sequence ends in the frozen terminal state. + /// @dev The whole reference scenario on the production stack: the canonical service starts at 80,000 + /// Supporting Capacity, a 50,000 commitment is admitted, an ordinary backing-compatible protected + /// swap of 15,000 takes capacity to 65,000 without disturbing the commitment, and the Beneficiary's + /// full 50,000 is then exercised. The terminal relationship is the frozen one — capacity 15,000, + /// obligation zero, remainder zero, and exactly 50,000 delivered — and every quantity in it is read + /// back from authoritative state or from the independent oracles. + function test_canonicalSequence_reachesTheFrozenTerminalState() public { + uint256 commitmentId = _establishExercisable(CANONICAL_ENTITLEMENT); + + assertEq( + _referenceSupportingCapacity(), + StandbyFixtureConfig.EXPECTED_INITIAL_S, + "the canonical service must start at the frozen initial capacity" + ); + + _swapAs(eligibleTrader, _protectedExactOutputSwapParams(StandbyFixtureConfig.COMPATIBLE_ORDINARY_SWAP_OUTPUT)); + + assertEq( + _referenceSupportingCapacity(), + StandbyFixtureConfig.EXPECTED_A2_S, + "the compatible ordinary swap must leave the frozen A2 capacity" + ); + + uint256 beneficiaryBefore = usdc.balanceOf(beneficiary); + + _authorizeAs(commitmentExerciseAuthority, commitmentId, CANONICAL_ENTITLEMENT); + + assertEq( + _referenceSupportingCapacity(), + StandbyFixtureConfig.EXPECTED_A2_S - StandbyFixtureConfig.CANONICAL_COMMITMENT_Q, + "A4 must terminate at 15,000 Supporting Capacity" + ); + assertEq(hook.aggregateObligation(), 0, "A4 must terminate at zero Aggregate Capacity Obligation"); + _assertRemaining(commitmentId, 0, "A4 must terminate at zero Remaining Entitlement"); + assertEq( + usdc.balanceOf(beneficiary) - beneficiaryBefore, + StandbyFixtureConfig.CANONICAL_COMMITMENT_Q, + "A4 must deliver exactly 50,000 to the Beneficiary" + ); + + _assertDerivationsMatchOracles("the terminal state must match both independent reconstructions"); + } + + /*////////////////////////////////////////////////////////////// + INTERNAL FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /// @dev Expects the Hook's refusal of a finalization with no proven execution behind it. + function _expectUnprovenRefusal() internal { + vm.expectRevert( + abi.encodeWithSelector( + StandbyHook.StandbyHook__NoProvenExerciseToFinalize.selector, + StandbyHook.ExerciseAuthorizationState.EMPTY + ) + ); + } + + /// @dev Asserts a commitment's authoritative Remaining Entitlement. + function _assertRemaining(uint256 _commitmentId, uint128 _expected, string memory _context) internal view { + assertEq(uint256(_commitmentRecord(_commitmentId).remainingEntitlement), uint256(_expected), _context); + } + + /// @dev Removes every unit of input currency an exerciser holds. + function _drainExerciser(address _exerciser) internal { + uint256 held = ustb.balanceOf(_exerciser); + + vm.prank(_exerciser); + bool drained = ustb.transfer(address(0xdead), held); + + assertTrue(drained, "the fixture must be able to remove the exerciser's funds"); + } +} + +/// @notice Integration evidence that a refused finalization unwinds the complete O2 transaction (G8D-E). +/// @dev The atomicity requirement is about what happens when finalization *fails*, and every reason it can +/// fail is one the production system cannot produce. An unbacked resulting state and a remainder +/// smaller than the proven quantity are both unreachable, because the invariant they would violate is +/// the one every earlier transition already maintains. A finalization naming the wrong commitment is +/// unreachable because the production router names the commitment its own request was for. +/// +/// The configured router here is the production router with exactly that one thing changed: it names a +/// commitment chosen by the test. Everything else — the authorization, the swap, the settlement out of +/// the exerciser's account, and the direct delivery to the Beneficiary — is production code running +/// unmodified against the real PoolManager, so the exercise this suite refuses is a real one that had +/// really executed, really paid, and really delivered by the time it was refused. +/// +/// What that isolates is the property on the other side of the refusal: no durable delivery without +/// fulfillment, and no durable fulfillment without delivery. Neither survives, and neither does +/// anything else the exercise had done. +contract MisdirectedFinalizationTest is BaseExerciseFinalizationTest { + /*////////////////////////////////////////////////////////////// + STATE VARIABLES + //////////////////////////////////////////////////////////////*/ + + /// @dev The entitlement of each of the two commitments: 25,000 MockUSDC. + /// + /// Two of these are admissible against the canonical initial Supporting Capacity and two canonical + /// entitlements are not, so the pair is chosen by what the fixture can actually back. + uint128 internal constant PAIRED_ENTITLEMENT = 25_000_000_000; + + /// @dev The exercise quantity used throughout: 20,000 MockUSDC. + uint256 internal constant EXERCISE_Q = 20_000_000_000; + + /// @dev The configured router, typed so its finalization target is reachable. + MisdirectedFinalizationRouter internal misdirectingRouter; + + /*////////////////////////////////////////////////////////////// + G8D-E — FINALIZATION FAILURE ATOMICITY + //////////////////////////////////////////////////////////////*/ + + /// @notice Proves a refused finalization unwinds the swap, the payment, and the delivery with it. + /// @dev The exercise is for the first commitment and completes every stage before finalization; the + /// request then names the second, and the Hook refuses it against its own causal binding. What is + /// asserted afterwards spans every surface the exercise had already touched: the pool price, tick + /// and active liquidity, the exerciser's balance, the Beneficiary's balance, the PoolManager's + /// balances on both currencies, both commitments' facts, the derived obligation, and the Hook's + /// causal context. + function test_refusedFinalization_unwindsTheCompleteExercise() public { + uint256 exercisedCommitmentId = _establishExercisable(PAIRED_ENTITLEMENT); + uint256 namedCommitmentId = _establishExercisable(PAIRED_ENTITLEMENT); + + misdirectingRouter.setFinalizationTarget(namedCommitmentId); + + SettlementState memory before = _settlementState(commitmentExerciseAuthority, exercisedCommitmentId); + StandbyHook.Commitment memory namedBefore = _commitmentRecord(namedCommitmentId); + + vm.expectRevert( + abi.encodeWithSelector( + StandbyHook.StandbyHook__NotTheProvenExerciseCommitment.selector, + namedCommitmentId, + exercisedCommitmentId + ) + ); + _authorizeAs(commitmentExerciseAuthority, exercisedCommitmentId, EXERCISE_Q); + + _assertNoExerciseResidue(before, commitmentExerciseAuthority, exercisedCommitmentId); + _assertCommitmentFactsUnchanged( + namedCommitmentId, namedBefore, "the named commitment must not be fulfilled either" + ); + } + + /// @notice Proves the same exercise commits once the finalization request names the right commitment. + /// @dev The control. Nothing about the request, the fixture, the funding, or the router changes except + /// which commitment the finalization names — so the refusal above is attributable to the + /// misdirection and to nothing else in the setup. + function test_correctlyDirectedFinalization_commitsTheSameExercise() public { + uint256 exercisedCommitmentId = _establishExercisable(PAIRED_ENTITLEMENT); + + _establishExercisable(PAIRED_ENTITLEMENT); + + misdirectingRouter.setFinalizationTarget(exercisedCommitmentId); + + SettlementState memory before = _settlementState(commitmentExerciseAuthority, exercisedCommitmentId); + + _authorizeAs(commitmentExerciseAuthority, exercisedCommitmentId, EXERCISE_Q); + + _assertExactFulfillment(before, commitmentExerciseAuthority, exercisedCommitmentId, EXERCISE_Q); + } + + /*////////////////////////////////////////////////////////////// + INTERNAL FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /// @dev Activates the service with the production router whose finalization request is misdirectable. + function _resolveExerciseRouter() internal override returns (address router) { + misdirectingRouter = new MisdirectedFinalizationRouter(hook); + + configuredExerciseRouter = misdirectingRouter; + + router = address(configuredExerciseRouter); + } +} diff --git a/test/integration/ExerciseSettlement.t.sol b/test/integration/ExerciseSettlement.t.sol index 3ee6bdc..a1ab391 100644 --- a/test/integration/ExerciseSettlement.t.sol +++ b/test/integration/ExerciseSettlement.t.sol @@ -11,6 +11,7 @@ import {ExerciseRouter} from "../../src/ExerciseRouter.sol"; import {StandbyHook} from "../../src/StandbyHook.sol"; import {MockFixtureCurrency} from "../../src/mocks/MockFixtureCurrency.sol"; +import {NonFinalizingExerciseRouter} from "../harness/NonFinalizingExerciseRouter.sol"; import {BaseExerciseSettlementTest} from "../shared/BaseExerciseSettlementTest.t.sol"; /*////////////////////////////////////////////////////////////// @@ -383,18 +384,23 @@ contract ExerciseSettlementTest is BaseExerciseSettlementTest { } } -/// @notice Integration evidence that the production exercise path stays fail-closed until finalization -/// exists (G8C-19). +/// @notice Integration evidence that an unfinalized exercise stays fail-closed (G8C-19). /// @dev The only difference from the fixture above is the one that matters: the configured ExerciseRouter -/// is the production `ExerciseRouter`, with its completion barrier intact. Everything else is -/// identical — the same Hook, the same pool, the same liquidity, the same production commitment, the -/// same funded and approved exerciser, and the same request. +/// keeps the production completion barrier. Everything else is identical — the same Hook, the same +/// pool, the same liquidity, the same production commitment, the same funded and approved exerciser, +/// and the same request. /// /// What that isolates is the barrier itself. The exercise gets all the way through authorization, /// execution, settlement, and delivery, and is then refused for the one thing that has not happened: /// nothing consumed the causal proof. Because the proof is transaction-scoped, committing here would /// leave a paid exerciser, a paid Beneficiary, an unreduced entitlement, and no surviving evidence /// that any of it happened — so the whole exercise unwinds instead. +/// +/// Production reaches this state through nothing, because production asks the Hook to finalize every +/// exercise it resolves. The router here is the production router with that request omitted and +/// nothing else changed, which is what makes the requirement's failing case constructible at all. That +/// a production exercise does finalize, and that the barrier then passes on its own, is evidence the +/// F8D suites carry against the production router. contract ProductionExerciseCompletionTest is BaseExerciseSettlementTest { /*////////////////////////////////////////////////////////////// STATE VARIABLES @@ -410,11 +416,11 @@ contract ProductionExerciseCompletionTest is BaseExerciseSettlementTest { G8C-19 — SLICE-COMPLETION SAFETY //////////////////////////////////////////////////////////////*/ - /// @notice Proves a production exercise cannot commit while its causal proof is unconsumed. + /// @notice Proves an exercise cannot commit while its causal proof is unconsumed. /// @dev The refusal names the position the context was actually left in, which is what makes it a /// completion condition rather than a placeholder: `EXECUTED` is exactly the state finalization - /// will consume, and consuming it is what will let this same requirement pass. - function test_productionExercise_cannotCompleteWhileUnfinalized() public { + /// consumes, and consuming it is what lets this same requirement pass unchanged. + function test_unfinalizedExercise_cannotComplete() public { uint256 commitmentId = _establishExercisable(CANONICAL_ENTITLEMENT); SettlementState memory before = _settlementState(commitmentExerciseAuthority, commitmentId); @@ -433,7 +439,7 @@ contract ProductionExerciseCompletionTest is BaseExerciseSettlementTest { /// @notice Proves the refusal survives every request shape a caller could try. /// @dev A generous cost bound, an exact one, and a whole-remainder quantity are all refused identically. /// The barrier is not about the request and cannot be routed around by making the request better. - function test_productionExercise_isRefusedWhateverTheRequest() public { + function test_unfinalizedExercise_isRefusedWhateverTheRequest() public { uint256 commitmentId = _establishExercisable(CANONICAL_ENTITLEMENT); SettlementState memory before = _settlementState(commitmentExerciseAuthority, commitmentId); @@ -454,9 +460,9 @@ contract ProductionExerciseCompletionTest is BaseExerciseSettlementTest { INTERNAL FUNCTIONS //////////////////////////////////////////////////////////////*/ - /// @dev Activates the service with the production router, completion barrier intact. + /// @dev Activates the service with the production router minus its finalization request. function _resolveExerciseRouter() internal override returns (address router) { - configuredExerciseRouter = new ExerciseRouter(hook); + configuredExerciseRouter = new NonFinalizingExerciseRouter(hook); router = address(configuredExerciseRouter); } diff --git a/test/shared/BaseExerciseFinalizationTest.t.sol b/test/shared/BaseExerciseFinalizationTest.t.sol new file mode 100644 index 0000000..eb6180a --- /dev/null +++ b/test/shared/BaseExerciseFinalizationTest.t.sol @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +/*////////////////////////////////////////////////////////////// + IMPORTS +//////////////////////////////////////////////////////////////*/ + +import {ExerciseRouter} from "../../src/ExerciseRouter.sol"; +import {StandbyHook} from "../../src/StandbyHook.sol"; +import {MAX_LIVE_COMMITMENTS} from "../../src/libraries/CommitmentRefs.sol"; + +import {BaseExerciseSettlementTest} from "./BaseExerciseSettlementTest.t.sol"; + +/*////////////////////////////////////////////////////////////// + CONTRACTS +//////////////////////////////////////////////////////////////*/ + +/// @notice Shared real-path fixture for F8D causal-finalization evidence. +/// @dev Built on the F8C fixture without altering it: the same real pinned `PoolManager`, the same +/// canonical `StandbyHook` deployment, the same F1 currencies, the same F2 registry under its own +/// administrator, the same production activation, the same canonical liquidity added through the +/// production `beforeAddLiquidity` path, the same production `establishCommitment` transition for +/// every commitment, and the same funded and approved exerciser. Nothing economic is seeded. +/// +/// Exactly one thing differs, and it is the thing F8D is about: the configured ExerciseRouter is the +/// production `ExerciseRouter`. Every earlier exercise fixture configures a router with something +/// removed — the finalization request, the completion barrier, or the whole settlement stage — because +/// until finalization existed no production exercise could commit and the mechanics before it could +/// not otherwise be measured. Nothing is removed here. An exercise these suites request is authorized, +/// executed, settled, delivered, finalized, and committed by production code alone, which is the only +/// kind of exercise that can be evidence about durable fulfillment. +/// +/// Roles stay separate throughout, as they do in every layer below: the exerciser is not the +/// Beneficiary, and neither is the router, the Hook, the establishment authority, the configuration +/// authority, a trader, a liquidity provider, or the registry administrator. +abstract contract BaseExerciseFinalizationTest is BaseExerciseSettlementTest { + /*////////////////////////////////////////////////////////////// + INTERNAL FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /// @dev Activates the service with the complete production ExerciseRouter. + function _resolveExerciseRouter() internal virtual override returns (address router) { + configuredExerciseRouter = new ExerciseRouter(hook); + + router = address(configuredExerciseRouter); + } + + /// @dev Proves one completed exercise had exactly the fulfillment consequence it should have. + /// + /// Four independent statements, and a wrong finalization would break them one at a time. The + /// remainder fell by exactly the exercised quantity; the admitted extent did not move at all; the + /// aggregate obligation released exactly what the remainder released, which is what it means for + /// the obligation to be derived from the remainder rather than accounted separately; and the + /// Beneficiary was paid exactly the quantity that was discharged, which is what makes the + /// fulfillment attributable to this exercise rather than to anything else that happened. + function _assertExactFulfillment( + SettlementState memory _before, + address _exerciser, + uint256 _commitmentId, + uint256 _q + ) internal view { + SettlementState memory current = _settlementState(_exerciser, _commitmentId); + + assertEq( + uint256(current.remainingEntitlement), + uint256(_before.remainingEntitlement) - _q, + "Remaining Entitlement must fall by exactly the exercised quantity" + ); + assertEq( + uint256(current.originalEntitlement), + uint256(_before.originalEntitlement), + "the admitted entitlement extent must never be rewritten" + ); + assertEq( + current.aggregateObligation, + _before.aggregateObligation - _q, + "the derived obligation must release exactly the fulfilled quantity" + ); + assertEq( + current.beneficiaryOutput - _before.beneficiaryOutput, + _q, + "the Beneficiary must have received exactly the quantity that was fulfilled" + ); + } + + /// @dev Proves a transition fulfilled nothing at all. + /// + /// The complement of `_assertExactFulfillment`, and the assertion every non-O2 path has to satisfy: + /// an ordinary swap, a direct transfer, a liquidity action, and a refused exercise all leave the + /// commitment exactly as they found it. + function _assertNoFulfillment( + SettlementState memory _before, + address _exerciser, + uint256 _commitmentId, + string memory _context + ) internal view { + SettlementState memory current = _settlementState(_exerciser, _commitmentId); + + assertEq(uint256(current.remainingEntitlement), uint256(_before.remainingEntitlement), _context); + assertEq(uint256(current.originalEntitlement), uint256(_before.originalEntitlement), _context); + assertEq(current.aggregateObligation, _before.aggregateObligation, _context); + } + + /// @dev Proves the bounded enforcement index still references a commitment, slot for slot. + /// + /// Fulfillment does not clear a reference, and must not be made to: an exhausted commitment simply + /// contributes zero to the derived obligation, and its slot becomes reclaimable by the same + /// predicate that already governs reclamation. + function _assertReferenceRetained(uint256 _commitmentId, uint256 _slot, string memory _context) internal view { + uint256[MAX_LIVE_COMMITMENTS] memory references = hook.enforcementReferences(); + + assertEq(references[_slot], _commitmentId, _context); + } + + /// @dev Reads the complete authoritative fact record of a commitment. + function _commitmentRecord(uint256 _commitmentId) internal view returns (StandbyHook.Commitment memory record) { + record = hook.commitment(_commitmentId); + } +} diff --git a/test/unit/ExerciseFinalization.t.sol b/test/unit/ExerciseFinalization.t.sol new file mode 100644 index 0000000..5fb052b --- /dev/null +++ b/test/unit/ExerciseFinalization.t.sol @@ -0,0 +1,437 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +/*////////////////////////////////////////////////////////////// + IMPORTS +//////////////////////////////////////////////////////////////*/ + +import {StandbyFixtureConfig} from "../../script/helpers/StandbyFixtureConfig.sol"; + +import {StandbyHook} from "../../src/StandbyHook.sol"; + +import {BaseUnbackedExerciseAuthorizationTest} from "../shared/BaseUnbackedExerciseAuthorizationTest.t.sol"; + +/*////////////////////////////////////////////////////////////// + CONTRACTS +//////////////////////////////////////////////////////////////*/ + +/// @notice Unit evidence for the F8D causal prerequisites, the final-backing comparison, and exact-once +/// consumption (G8D-A, G8D-B, G8D-C, G8D-D). +/// @dev Everything F8D refuses is something the reachable production system cannot present to it, and that +/// is a property of the protocol working rather than a gap in the evidence. A production finalization +/// arrives from inside a PoolManager unlock, immediately after a swap this Hook accepted, proved, +/// settled, and delivered — so a context that is not `EXECUTED`, a caller that is not the bound +/// ExerciseRouter, a commitment that is not the bound one, a remainder smaller than the proven +/// quantity, and a post-execution state that is no longer backed are all unreachable there. Each is +/// nonetheless a requirement, and each is verifiable only by constructing it directly. +/// +/// This is the F8A unbacked fixture, unchanged: the real PoolManager, the real pool, the production +/// activation, the canonical liquidity through the production liquidity path, the real registry, the +/// real ExerciseRouter, and commitments created only by the production O1 transition — with a +/// `StandbyHookHarness` in the production Hook's place. The harness contributes two abilities and no +/// economics: writing a causal context at a chosen position, and writing a commitment's Remaining +/// Entitlement directly. +/// +/// A context written that way is a starting position, not an authorization: nothing authenticated it, +/// no swap stands behind it, and nothing was delivered for it. It is valid evidence about what +/// finalization requires of the position it is handed, and about nothing else. In particular, no test +/// here is evidence that a production exercise fulfils anything — that is the integration suite's. +contract ExerciseFinalizationTest is BaseUnbackedExerciseAuthorizationTest { + /*////////////////////////////////////////////////////////////// + STATE VARIABLES + //////////////////////////////////////////////////////////////*/ + + /// @dev The canonical commitment entitlement: 50,000 MockUSDC. + uint128 internal constant CANONICAL_ENTITLEMENT = uint128(StandbyFixtureConfig.CANONICAL_COMMITMENT_Q); + + /// @dev The proven quantity every seeded context carries: 20,000 MockUSDC. + uint256 internal constant PROVEN_Q = 20_000_000_000; + + /// @dev The entitlement used where a test needs two commitments at once: 25,000 MockUSDC. + /// + /// Two of these are admissible against the canonical initial Supporting Capacity and two canonical + /// entitlements are not, so the pair is chosen by what the fixture can actually back rather than + /// by convenience. Nothing below turns on the value. + uint128 internal constant PAIRED_ENTITLEMENT = 25_000_000_000; + + /*////////////////////////////////////////////////////////////// + G8D-A — A PROVEN EXECUTION IS REQUIRED + //////////////////////////////////////////////////////////////*/ + + /// @notice Proves an absent causal context finalizes nothing. + /// @dev The position every transaction that never exercised is in, and the position a consumed context + /// returns to. An ordinary swap, a direct transfer, a liquidity action, and a replayed + /// finalization all present exactly this and are all refused by it. + function test_absentContext_finalizesNothing() public { + uint256 commitmentId = _establishExercisable(CANONICAL_ENTITLEMENT); + + _expectUnprovenRefusal(StandbyHook.ExerciseAuthorizationState.EMPTY); + + _finalize(commitmentId); + + _assertRemainingEntitlement(commitmentId, CANONICAL_ENTITLEMENT, "an absent context must fulfil nothing"); + } + + /// @notice Proves an authorization that has not executed finalizes nothing. + /// @dev Every binding a fulfillment needs is present — this commitment, this exerciser, this + /// Beneficiary, this quantity — and the pool has still produced nothing. Reducing the entitlement + /// here would discharge an obligation against a delivery that never happened. + function test_authorizedButUnexecutedContext_finalizesNothing() public { + uint256 commitmentId = _seededCommitment(StandbyHook.ExerciseAuthorizationState.AUTHORIZED); + + _expectUnprovenRefusal(StandbyHook.ExerciseAuthorizationState.AUTHORIZED); + + _finalize(commitmentId); + + _assertRemainingEntitlement(commitmentId, CANONICAL_ENTITLEMENT, "an unexecuted exercise must fulfil nothing"); + } + + /// @notice Proves a swap merely in flight finalizes nothing. + function test_inFlightContext_finalizesNothing() public { + uint256 commitmentId = _seededCommitment(StandbyHook.ExerciseAuthorizationState.EXECUTING); + + _expectUnprovenRefusal(StandbyHook.ExerciseAuthorizationState.EXECUTING); + + _finalize(commitmentId); + + _assertRemainingEntitlement(commitmentId, CANONICAL_ENTITLEMENT, "an in-flight swap must fulfil nothing"); + } + + /// @notice Proves an undecided authorization finalizes nothing. + function test_undecidedContext_finalizesNothing() public { + uint256 commitmentId = _seededCommitment(StandbyHook.ExerciseAuthorizationState.AUTHORIZING); + + _expectUnprovenRefusal(StandbyHook.ExerciseAuthorizationState.AUTHORIZING); + + _finalize(commitmentId); + + _assertRemainingEntitlement(commitmentId, CANONICAL_ENTITLEMENT, "an undecided attempt must fulfil nothing"); + } + + /*////////////////////////////////////////////////////////////// + G8D-A — THE BOUND COORDINATOR IS REQUIRED + //////////////////////////////////////////////////////////////*/ + + /// @notice Proves an account with no relationship to the exercise cannot finalize it. + /// @dev The proof exists, is complete, and belongs to somebody else. Finalization is not a public + /// transition that anyone may complete on the protocol's behalf. + function test_unrelatedAccount_cannotFinalize() public { + uint256 commitmentId = _seededCommitment(StandbyHook.ExerciseAuthorizationState.EXECUTED); + + _expectFinalizerRefusal(unauthorizedExerciser); + + vm.prank(unauthorizedExerciser); + hook.finalizeExercise(commitmentId); + + _assertRemainingEntitlement(commitmentId, CANONICAL_ENTITLEMENT, "an outsider must fulfil nothing"); + } + + /// @notice Proves the commitment's own exercise authority cannot finalize its exercise directly. + /// @dev The one account that may exercise this commitment, and it still may not reach the fulfillment + /// transition except through the coordinator the proof is bound to — because what finalization + /// completes is a specific coordinated operation, not a right the authority holds standing. + function test_exerciseAuthority_cannotFinalizeDirectly() public { + uint256 commitmentId = _seededCommitment(StandbyHook.ExerciseAuthorizationState.EXECUTED); + + _expectFinalizerRefusal(commitmentExerciseAuthority); + + vm.prank(commitmentExerciseAuthority); + hook.finalizeExercise(commitmentId); + + _assertRemainingEntitlement(commitmentId, CANONICAL_ENTITLEMENT, "the exercise authority must fulfil nothing"); + } + + /// @notice Proves an identical but unconfigured ExerciseRouter cannot finalize. + /// @dev Same bytecode, same Hook, no configuration. What authorizes finalization is being the + /// coordinator this proof was produced through, which is a Hook-owned fact rather than a property + /// of an implementation. + function test_unconfiguredRouter_cannotFinalize() public { + uint256 commitmentId = _seededCommitment(StandbyHook.ExerciseAuthorizationState.EXECUTED); + + _expectFinalizerRefusal(address(unconfiguredExerciseRouter)); + + vm.prank(address(unconfiguredExerciseRouter)); + hook.finalizeExercise(commitmentId); + + _assertRemainingEntitlement(commitmentId, CANONICAL_ENTITLEMENT, "an unconfigured router must fulfil nothing"); + } + + /*////////////////////////////////////////////////////////////// + G8D-A, G8D-D — THE BOUND COMMITMENT + //////////////////////////////////////////////////////////////*/ + + /// @notice Proves a finalization naming another commitment is refused rather than redirected. + /// @dev Two authentic commitments exist and the proof is bound to the first. The request names the + /// second, and the outcome is neither "the second is fulfilled" nor "the first is fulfilled + /// anyway": it is a refusal, so a caller cannot discover which commitment a proof belongs to by + /// watching what a wrong guess does. + function test_crossCommitmentFinalization_isRefused() public { + uint256 provenCommitmentId = + _seededCommitment(StandbyHook.ExerciseAuthorizationState.EXECUTED, PAIRED_ENTITLEMENT, PROVEN_Q); + uint256 otherCommitmentId = _establishExercisable(PAIRED_ENTITLEMENT); + + vm.expectRevert( + abi.encodeWithSelector( + StandbyHook.StandbyHook__NotTheProvenExerciseCommitment.selector, otherCommitmentId, provenCommitmentId + ) + ); + + _finalize(otherCommitmentId); + + _assertRemainingEntitlement(provenCommitmentId, PAIRED_ENTITLEMENT, "the proven commitment must be intact"); + _assertRemainingEntitlement(otherCommitmentId, PAIRED_ENTITLEMENT, "the named commitment must be intact"); + } + + /*////////////////////////////////////////////////////////////// + G8D-A, G8D-C — THE QUANTITY COMES FROM THE PROOF + //////////////////////////////////////////////////////////////*/ + + /// @notice Proves the reduction is the quantity the context carries, whatever the caller wanted. + /// @dev The finalization surface carries no quantity at all, so this is what "the caller cannot + /// substitute `q`" means concretely: two identical requests differing only in the quantity their + /// proofs were bound to reduce the entitlement by those two different amounts, and nothing the + /// caller passes participates. + function test_reduction_followsTheProvenQuantity() public { + uint256 smallCommitmentId = + _seededCommitment(StandbyHook.ExerciseAuthorizationState.EXECUTED, PAIRED_ENTITLEMENT, 1); + + _finalize(smallCommitmentId); + + _assertRemainingEntitlement( + smallCommitmentId, PAIRED_ENTITLEMENT - 1, "one raw unit of proof must fulfil one raw unit" + ); + + uint256 largeCommitmentId = + _seededCommitment(StandbyHook.ExerciseAuthorizationState.EXECUTED, PAIRED_ENTITLEMENT, PROVEN_Q); + + _finalize(largeCommitmentId); + + _assertRemainingEntitlement( + largeCommitmentId, + PAIRED_ENTITLEMENT - uint128(PROVEN_Q), + "a larger proof must fulfil exactly that larger quantity" + ); + } + + /// @notice Proves the whole remainder can be discharged, leaving exactly zero. + /// @dev Complete fulfillment is `Remaining == 0` and nothing else: no flag is set, and the obligation + /// the commitment carried disappears because it is derived from the remainder that just reached + /// zero. + function test_exhaustingProof_leavesZeroRemainingAndZeroObligation() public { + uint256 commitmentId = _seededCommitment( + StandbyHook.ExerciseAuthorizationState.EXECUTED, CANONICAL_ENTITLEMENT, CANONICAL_ENTITLEMENT + ); + + _finalize(commitmentId); + + _assertRemainingEntitlement(commitmentId, 0, "exact exhaustion must produce zero Remaining Entitlement"); + + assertEq(hook.commitmentObligation(commitmentId), 0, "an exhausted commitment must carry no obligation"); + assertEq(hook.aggregateObligation(), 0, "the aggregate must release the exhausted commitment entirely"); + assertEq( + uint256(_commitmentFacts(commitmentId).originalEntitlement), + uint256(CANONICAL_ENTITLEMENT), + "the admitted extent must survive complete fulfillment" + ); + } + + /*////////////////////////////////////////////////////////////// + G8D-A, G8D-C — AUTHORITATIVE STATE IS RE-READ + //////////////////////////////////////////////////////////////*/ + + /// @notice Proves a proof larger than the current remainder is refused rather than clamped. + /// @dev Finalization does not trust the extent its own authorization admitted; it re-reads the + /// commitment and compares against what the record says now. A remainder smaller than the proven + /// quantity is a state finalization must not resolve by fulfilling less, because a partial + /// fulfillment of a complete delivery would discharge less obligation than was actually delivered. + function test_proofExceedingTheCurrentRemainder_isRefused() public { + uint256 commitmentId = _seededCommitment(StandbyHook.ExerciseAuthorizationState.EXECUTED); + + uint128 shortenedRemainder = uint128(PROVEN_Q) - 1; + + _writeRemainingEntitlement(commitmentId, shortenedRemainder); + + vm.expectRevert( + abi.encodeWithSelector( + StandbyHook.StandbyHook__FulfillmentExceedsRemainingEntitlement.selector, PROVEN_Q, shortenedRemainder + ) + ); + + _finalize(commitmentId); + + _assertRemainingEntitlement(commitmentId, shortenedRemainder, "a refused finalization must fulfil nothing"); + } + + /*////////////////////////////////////////////////////////////// + G8D-B — ACTUAL POST-EXECUTION BACKING + //////////////////////////////////////////////////////////////*/ + + /// @notice Proves a resulting state that would be unbacked refuses the fulfillment. + /// @dev The comparison is between two present facts: the Supporting Capacity the pool actually has, and + /// the obligation that would remain once exactly the proven quantity is released. The remainder is + /// inflated past what this pool can support, so releasing `q` still leaves more obligation than + /// capacity — and the refusal names both derived quantities rather than a generic failure. + function test_unbackedResultingState_refusesFulfillment() public { + uint256 commitmentId = _seededCommitment(StandbyHook.ExerciseAuthorizationState.EXECUTED); + + uint256 capacity = _referenceSupportingCapacity(); + uint128 unbackedRemainder = uint128(capacity + PROVEN_Q + 1); + + _writeRemainingEntitlement(commitmentId, unbackedRemainder); + + vm.expectRevert( + abi.encodeWithSelector( + StandbyHook.StandbyHook__InsufficientFinalExerciseBacking.selector, capacity, capacity + 1 + ) + ); + + _finalize(commitmentId); + + _assertRemainingEntitlement(commitmentId, unbackedRemainder, "an unbacked result must fulfil nothing"); + } + + /// @notice Proves a resulting state exactly at the backing boundary is accepted. + /// @dev The boundary that must not be off by one in the safe direction. Capacity exactly equal to the + /// obligation the fulfillment leaves behind preserves backing, and refusing it would refuse a + /// fulfillment the frozen requirement admits. + function test_exactlyBackedResultingState_isAccepted() public { + uint256 commitmentId = _seededCommitment(StandbyHook.ExerciseAuthorizationState.EXECUTED); + + uint256 capacity = _referenceSupportingCapacity(); + uint128 exactRemainder = uint128(capacity + PROVEN_Q); + + _writeRemainingEntitlement(commitmentId, exactRemainder); + + _finalize(commitmentId); + + _assertRemainingEntitlement( + commitmentId, uint128(capacity), "exact final backing must fulfil exactly the proven quantity" + ); + + assertEq( + hook.aggregateObligation(), hook.supportingCapacity(), "the fulfilled state must sit exactly at backing" + ); + } + + /// @notice Proves the authorization-time prediction cannot stand in for the actual resulting state. + /// @dev Both quantities are present in the fixture and they are different numbers. The prospective + /// capacity of the canonical exercise is `S - q`, which would clear an obligation this pool cannot + /// actually support; the refusal reports `S` instead, so the comparison demonstrably used the state + /// the pool is in rather than a prediction about a swap. + function test_finalBacking_usesActualCapacityRatherThanTheProspectivePrediction() public { + uint256 commitmentId = _seededCommitment(StandbyHook.ExerciseAuthorizationState.EXECUTED); + + uint256 capacity = _referenceSupportingCapacity(); + uint256 prospectiveCapacity = _expectedProspectiveExerciseCapacity(PROVEN_Q); + + assertLt(prospectiveCapacity, capacity, "the prediction and the actual state must be distinguishable"); + + _writeRemainingEntitlement(commitmentId, uint128(capacity + PROVEN_Q + 1)); + + vm.expectRevert( + abi.encodeWithSelector( + StandbyHook.StandbyHook__InsufficientFinalExerciseBacking.selector, capacity, capacity + 1 + ) + ); + + _finalize(commitmentId); + } + + /*////////////////////////////////////////////////////////////// + G8D-D — EXACT-ONCE CAUSAL CONSUMPTION + //////////////////////////////////////////////////////////////*/ + + /// @notice Proves a successful finalization consumes the whole causal context. + /// @dev Every binding is checked, not only the position. A context that reported `EMPTY` while still + /// carrying a commitment, an actor, a Beneficiary, or a quantity would be exactly the reusable + /// evidence consumption exists to destroy. + function test_finalization_consumesTheWholeCausalContext() public { + uint256 commitmentId = _seededCommitment(StandbyHook.ExerciseAuthorizationState.EXECUTED); + + _finalize(commitmentId); + + _assertNoAuthorizationContext("finalization must leave no reusable causal evidence"); + } + + /// @notice Proves one proven execution can reduce Remaining Entitlement exactly once. + /// @dev The replay, made as directly as it can be made: the same coordinator, the same commitment, in + /// the same transaction, immediately after the finalization that succeeded. It is refused against + /// the consumed position, and the entitlement moved once. + function test_finalization_cannotBeReplayed() public { + uint256 commitmentId = _seededCommitment(StandbyHook.ExerciseAuthorizationState.EXECUTED); + + _finalize(commitmentId); + + _expectUnprovenRefusal(StandbyHook.ExerciseAuthorizationState.EMPTY); + + _finalize(commitmentId); + + _assertRemainingEntitlement( + commitmentId, CANONICAL_ENTITLEMENT - uint128(PROVEN_Q), "one proof must cause exactly one reduction" + ); + } + + /*////////////////////////////////////////////////////////////// + INTERNAL FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /// @dev Requests finalization as the ExerciseRouter the seeded proofs are bound to. + function _finalize(uint256 _commitmentId) internal { + vm.prank(exerciseRouter); + hook.finalizeExercise(_commitmentId); + } + + /// @dev Admits an authentic commitment and seeds a causal context bound to it at a chosen position. + function _seededCommitment(StandbyHook.ExerciseAuthorizationState _state) internal returns (uint256 commitmentId) { + commitmentId = _seededCommitment(_state, CANONICAL_ENTITLEMENT, PROVEN_Q); + } + + /// @dev The same, with the admitted extent and the proven quantity chosen explicitly. + /// + /// The commitment is admitted by the production O1 transition and every fact about it is + /// authentic. The context is not: it is written directly, at a position no swap put it in, so that + /// one finalization requirement can be addressed at a time. + function _seededCommitment(StandbyHook.ExerciseAuthorizationState _state, uint128 _entitlement, uint256 _q) + internal + returns (uint256 commitmentId) + { + commitmentId = _establishExercisable(_entitlement); + + serviceHarness.writeExerciseAuthorization( + StandbyHook.ExerciseAuthorizationContext({ + state: _state, + serviceId: servicePoolId, + commitmentId: commitmentId, + exerciseRouter: exerciseRouter, + exerciser: commitmentExerciseAuthority, + beneficiary: beneficiary, + q: _q + }) + ); + } + + /// @dev Expects the refusal of a finalization offered against a position that proves no execution. + function _expectUnprovenRefusal(StandbyHook.ExerciseAuthorizationState _state) internal { + vm.expectRevert(abi.encodeWithSelector(StandbyHook.StandbyHook__NoProvenExerciseToFinalize.selector, _state)); + } + + /// @dev Expects the refusal of a finalization requested by anything but the bound coordinator. + function _expectFinalizerRefusal(address _caller) internal { + vm.expectRevert( + abi.encodeWithSelector(StandbyHook.StandbyHook__NotAuthorizedExerciseFinalizer.selector, _caller) + ); + } + + /// @dev Reads a commitment's authoritative fact record. + function _commitmentFacts(uint256 _commitmentId) internal view returns (StandbyHook.Commitment memory record) { + record = hook.commitment(_commitmentId); + } + + /// @dev Asserts a commitment's authoritative Remaining Entitlement. + function _assertRemainingEntitlement(uint256 _commitmentId, uint128 _expected, string memory _context) + internal + view + { + assertEq(uint256(_commitmentFacts(_commitmentId).remainingEntitlement), uint256(_expected), _context); + } +}