// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/access/Ownable.sol"; import "./interfaces/INotaryRegistry.sol"; /** * @title NotaryRegistry * @notice Immutable registry for plan hashes, codehashes, and audit trail */ contract NotaryRegistry is INotaryRegistry, Ownable { mapping(bytes32 => PlanRecord) public plans; mapping(bytes32 => CodehashRecord) public codehashes; event PlanRegistered(bytes32 indexed planId, address indexed creator, bytes32 planHash); event PlanFinalized(bytes32 indexed planId, bool success, bytes32 receiptHash); event CodehashRegistered(address indexed contractAddress, bytes32 codehash, string version); /** * @notice Register a plan with notary */ function registerPlan( bytes32 planId, IComboHandler.Step[] calldata steps, address creator ) external override { require(plans[planId].registeredAt == 0, "Plan already registered"); bytes32 planHash = keccak256(abi.encode(planId, steps, creator)); plans[planId] = PlanRecord({ planHash: planHash, creator: creator, registeredAt: block.timestamp, finalizedAt: 0, success: false, receiptHash: bytes32(0) }); emit PlanRegistered(planId, creator, planHash); } /** * @notice Finalize a plan with execution result */ function finalizePlan( bytes32 planId, bool success ) external override { PlanRecord storage record = plans[planId]; require(record.registeredAt > 0, "Plan not registered"); require(record.finalizedAt == 0, "Plan already finalized"); bytes32 receiptHash = keccak256(abi.encode(planId, success, block.timestamp)); record.finalizedAt = block.timestamp; record.success = success; record.receiptHash = receiptHash; emit PlanFinalized(planId, success, receiptHash); } /** * @notice Register contract codehash for upgrade verification */ function registerCodehash( address contractAddress, bytes32 codehash, string calldata version ) external onlyOwner { codehashes[keccak256(abi.encodePacked(contractAddress, version))] = CodehashRecord({ contractAddress: contractAddress, codehash: codehash, version: version, registeredAt: block.timestamp }); emit CodehashRegistered(contractAddress, codehash, version); } /** * @notice Get plan record */ function getPlan(bytes32 planId) external view returns (PlanRecord memory) { return plans[planId]; } /** * @notice Verify codehash matches registered version */ function verifyCodehash( address contractAddress, bytes32 codehash, string calldata version ) external view returns (bool) { bytes32 key = keccak256(abi.encodePacked(contractAddress, version)); CodehashRecord memory record = codehashes[key]; return record.codehash == codehash && record.registeredAt > 0; } }