// SPDX-License-Identifier: MIT pragma solidity 0.8.24; import { NativeAssetLedgerV3 } from "./common/NativeAssetLedgerV3.sol"; import { EmergencyControlsV3 } from "./common/EmergencyControlsV3.sol"; import { RuntimeCodeGuardV3 } from "./common/RuntimeCodeGuardV3.sol"; interface IObjectiveTriggerV3 { /// @dev Implementations must evaluate the exact snapshotted position, not a free-floating fact key. function triggered(bytes32 factKey, bytes32 coveredObject, bytes32 positionConfigHash) external view returns (bool); } /// @notice Fully reserved deterministic coverage. The contract does not assert legal "insurance" status. contract DeterministicCoverageV3 is NativeAssetLedgerV3, EmergencyControlsV3, RuntimeCodeGuardV3 { enum PolicyState { None, Offered, Active, Claimed, Expired, Cancelled } struct Policy { address offeror; address insured; address verifier; bytes32 verifierCodehash; bytes32 coveredObject; bytes32 factKey; bytes32 termsHash; uint96 payout; uint96 premium; uint64 offerDeadline; uint64 coverageDeadline; uint64 acceptedAt; bytes32 configHash; PolicyState state; } uint256 public immutable maxPolicyPayout; uint256 public immutable maxAggregateLiability; uint256 public nextPolicyId = 1; mapping(uint256 => Policy) private _policies; error BadPolicy(); error BadState(); error Unauthorized(); error DeadlinePassed(); error DeadlineNotPassed(); error CapExceeded(); error InvalidVerifier(); error TriggerFalse(); event CoverageOfferedV3( uint256 indexed policyId, address indexed offeror, address indexed insured, uint256 payout, uint256 premium, bytes32 coveredObject, bytes32 factKey ); event CoverageAcceptedV3(uint256 indexed policyId); event CoverageConfigurationSnapshottedV3( uint256 indexed policyId, bytes32 indexed configHash, bytes32 verifierCodehash, uint96 maximumLoss ); event CoverageClaimedV3(uint256 indexed policyId, address indexed insured, uint256 payout); event CoverageReleasedV3(uint256 indexed policyId, address indexed offeror, uint256 payout, PolicyState state); constructor( address governance_, address guardian_, uint64 minimumPauseDuration_, uint256 maxPolicyPayout_, uint256 maxAggregateLiability_ ) EmergencyControlsV3(governance_, guardian_, minimumPauseDuration_) { if (maxPolicyPayout_ == 0 || maxPolicyPayout_ > type(uint96).max || maxAggregateLiability_ < maxPolicyPayout_) { revert BadPolicy(); } maxPolicyPayout = maxPolicyPayout_; maxAggregateLiability = maxAggregateLiability_; } function offerCoverage( address insured, address verifier, bytes32 verifierCodehash, bytes32 coveredObject, bytes32 factKey, bytes32 termsHash, uint96 premium, uint64 offerDeadline, uint64 coverageDeadline ) external payable whenNotPaused returns (uint256 policyId) { if ( insured == address(0) || insured == msg.sender || verifier.code.length == 0 || verifier.codehash != verifierCodehash || coveredObject == bytes32(0) || factKey == bytes32(0) || termsHash == bytes32(0) || premium == 0 || offerDeadline <= block.timestamp || coverageDeadline <= offerDeadline ) revert BadPolicy(); _rejectDelegatingRuntime(verifier); if (msg.value == 0 || msg.value > maxPolicyPayout || accountedLiabilities() + msg.value > maxAggregateLiability) { revert CapExceeded(); } policyId = nextPolicyId++; bytes32 configHash = keccak256( abi.encode( block.chainid, address(this), msg.sender, insured, verifier, verifierCodehash, coveredObject, factKey, termsHash, uint96(msg.value), premium, offerDeadline, coverageDeadline ) ); _policies[policyId] = Policy({ offeror: msg.sender, insured: insured, verifier: verifier, verifierCodehash: verifierCodehash, coveredObject: coveredObject, factKey: factKey, termsHash: termsHash, payout: uint96(msg.value), premium: premium, offerDeadline: offerDeadline, coverageDeadline: coverageDeadline, acceptedAt: 0, configHash: configHash, state: PolicyState.Offered }); _reserveNewValue(msg.value); emit CoverageOfferedV3(policyId, msg.sender, insured, msg.value, premium, coveredObject, factKey); emit CoverageConfigurationSnapshottedV3(policyId, configHash, verifierCodehash, uint96(msg.value)); } function acceptCoverage(uint256 policyId) external payable whenNotPaused { Policy storage policy = _policies[policyId]; if (policy.state != PolicyState.Offered) revert BadState(); if (msg.sender != policy.insured) revert Unauthorized(); if (block.timestamp > policy.offerDeadline) revert DeadlinePassed(); if (msg.value != policy.premium || accountedLiabilities() + msg.value > maxAggregateLiability) { revert CapExceeded(); } policy.state = PolicyState.Active; policy.acceptedAt = uint64(block.timestamp); _creditNewValue(policy.offeror, msg.value); emit CoverageAcceptedV3(policyId); } /// @notice Objective claim remains callable while paused. function claim(uint256 policyId) external { Policy storage policy = _policies[policyId]; if (policy.state != PolicyState.Active) revert BadState(); if (block.timestamp > policy.coverageDeadline) revert DeadlinePassed(); if (policy.verifier.codehash != policy.verifierCodehash) revert InvalidVerifier(); (bool ok, bytes memory result) = policy.verifier.staticcall( abi.encodeCall(IObjectiveTriggerV3.triggered, (policy.factKey, policy.coveredObject, policy.configHash)) ); if (!ok || result.length != 32 || !abi.decode(result, (bool))) revert TriggerFalse(); policy.state = PolicyState.Claimed; _reservedToCredit(policy.insured, policy.payout); emit CoverageClaimedV3(policyId, policy.insured, policy.payout); } function cancelOffer(uint256 policyId) external { Policy storage policy = _policies[policyId]; if (policy.state != PolicyState.Offered || msg.sender != policy.offeror) revert Unauthorized(); _release(policyId, policy, PolicyState.Cancelled); } function recoverExpiredOffer(uint256 policyId) external { Policy storage policy = _policies[policyId]; if (policy.state != PolicyState.Offered || block.timestamp <= policy.offerDeadline) { revert DeadlineNotPassed(); } _release(policyId, policy, PolicyState.Expired); } function releaseUntriggered(uint256 policyId) external { Policy storage policy = _policies[policyId]; if (policy.state != PolicyState.Active || block.timestamp <= policy.coverageDeadline) { revert DeadlineNotPassed(); } _release(policyId, policy, PolicyState.Expired); } function policyOf(uint256 policyId) external view returns (Policy memory) { return _policies[policyId]; } function _release(uint256 policyId, Policy storage policy, PolicyState terminal) private { policy.state = terminal; _reservedToCredit(policy.offeror, policy.payout); emit CoverageReleasedV3(policyId, policy.offeror, policy.payout, terminal); } }