Spaces:
Paused
Paused
| // SPDX-License-Identifier: MIT | |
| pragma solidity ^0.8.20; | |
| import "./CGAERegistry.sol"; | |
| /** | |
| * @title CGAEEscrow | |
| * @notice Contract management and escrow for the CGAE economy. | |
| * Implements tier-gated contract assignment with budget ceiling enforcement | |
| * (Theorem 1: Bounded Economic Exposure). | |
| * | |
| * @dev All economic activity is mediated through formally specified contracts. | |
| * Rewards are escrowed until verification. Penalties are enforced on failure. | |
| */ | |
| contract CGAEEscrow { | |
| // ----------------------------------------------------------------------- | |
| // Types | |
| // ----------------------------------------------------------------------- | |
| enum ContractStatus { | |
| Open, | |
| Assigned, | |
| Completed, | |
| Failed, | |
| Expired, | |
| Cancelled | |
| } | |
| struct EconomicContract { | |
| bytes32 contractId; | |
| address issuer; | |
| address assignedAgent; | |
| string objective; | |
| bytes32 constraintsHash; // Hash of machine-verifiable constraint set (Phi) | |
| string verifierSpecHash; // 0G Storage hash or pointer to verifier specification | |
| uint8 minTier; | |
| uint256 reward; | |
| uint256 penalty; | |
| uint64 deadline; | |
| uint64 createdAt; | |
| ContractStatus status; | |
| string domain; | |
| } | |
| // ----------------------------------------------------------------------- | |
| // State | |
| // ----------------------------------------------------------------------- | |
| CGAERegistry public registry; | |
| mapping(bytes32 => EconomicContract) public contracts; | |
| bytes32[] public contractIds; | |
| // Agent -> total active penalty exposure | |
| mapping(address => uint256) public activeExposure; | |
| // Economic accounting | |
| uint256 public totalRewardsPaid; | |
| uint256 public totalPenaltiesCollected; | |
| uint256 public totalEscrowed; | |
| address public admin; | |
| // ----------------------------------------------------------------------- | |
| // Events | |
| // ----------------------------------------------------------------------- | |
| event ContractCreated(bytes32 indexed contractId, uint8 minTier, uint256 reward, string domain); | |
| event ContractAssigned(bytes32 indexed contractId, address indexed agent); | |
| event ContractCompleted(bytes32 indexed contractId, address indexed agent, uint256 reward); | |
| event ContractFailed(bytes32 indexed contractId, address indexed agent, uint256 penalty); | |
| event ContractExpired(bytes32 indexed contractId); | |
| // ----------------------------------------------------------------------- | |
| // Constructor | |
| // ----------------------------------------------------------------------- | |
| constructor(address _registry) { | |
| registry = CGAERegistry(_registry); | |
| admin = msg.sender; | |
| } | |
| // ----------------------------------------------------------------------- | |
| // Contract Lifecycle | |
| // ----------------------------------------------------------------------- | |
| /** | |
| * @notice Create a new contract. Issuer deposits reward as escrow. | |
| */ | |
| function createContract( | |
| string calldata objective, | |
| bytes32 constraintsHash, | |
| string calldata verifierSpecHash, | |
| uint8 minTier, | |
| uint256 penalty, | |
| uint64 deadline, | |
| string calldata domain | |
| ) external payable { | |
| require(msg.value > 0, "Must escrow reward"); | |
| require(minTier > 0 && minTier <= 5, "Invalid tier"); | |
| require(deadline > block.timestamp, "Deadline must be in future"); | |
| require(constraintsHash != bytes32(0), "Missing constraints hash"); | |
| require(bytes(verifierSpecHash).length > 0, "Missing verifier spec"); | |
| bytes32 contractId = keccak256(abi.encodePacked( | |
| msg.sender, block.timestamp, objective, contractIds.length | |
| )); | |
| contracts[contractId] = EconomicContract({ | |
| contractId: contractId, | |
| issuer: msg.sender, | |
| assignedAgent: address(0), | |
| objective: objective, | |
| constraintsHash: constraintsHash, | |
| verifierSpecHash: verifierSpecHash, | |
| minTier: minTier, | |
| reward: msg.value, | |
| penalty: penalty, | |
| deadline: deadline, | |
| createdAt: uint64(block.timestamp), | |
| status: ContractStatus.Open, | |
| domain: domain | |
| }); | |
| contractIds.push(contractId); | |
| totalEscrowed += msg.value; | |
| emit ContractCreated(contractId, minTier, msg.value, domain); | |
| } | |
| /** | |
| * @notice Agent accepts a contract. Enforces: | |
| * 1. Agent tier >= min_tier | |
| * 2. Agent exposure + penalty <= budget ceiling (Theorem 1) | |
| */ | |
| function acceptContract(bytes32 contractId) external payable { | |
| EconomicContract storage c = contracts[contractId]; | |
| require(c.status == ContractStatus.Open, "Not open"); | |
| require(block.timestamp < c.deadline, "Past deadline"); | |
| // Tier check | |
| CGAERegistry.AgentRecord memory agent = registry.getAgent(msg.sender); | |
| require(agent.active, "Agent not active"); | |
| require(agent.currentTier >= c.minTier, "Tier too low"); | |
| // Budget ceiling check (Theorem 1: Bounded Economic Exposure) | |
| uint256 ceiling = registry.getBudgetCeiling(agent.currentTier); | |
| require( | |
| activeExposure[msg.sender] + c.penalty <= ceiling, | |
| "Would exceed budget ceiling" | |
| ); | |
| // Agent must deposit penalty as collateral | |
| require(msg.value >= c.penalty, "Insufficient penalty collateral"); | |
| c.assignedAgent = msg.sender; | |
| c.status = ContractStatus.Assigned; | |
| activeExposure[msg.sender] += c.penalty; | |
| emit ContractAssigned(contractId, msg.sender); | |
| } | |
| /** | |
| * @notice Mark a contract as completed. Called by admin/verifier after | |
| * output verification. Releases reward to agent, returns collateral. | |
| */ | |
| function completeContract(bytes32 contractId) external { | |
| require(msg.sender == admin, "Only admin/verifier"); | |
| EconomicContract storage c = contracts[contractId]; | |
| require(c.status == ContractStatus.Assigned, "Not assigned"); | |
| c.status = ContractStatus.Completed; | |
| // Release exposure | |
| activeExposure[c.assignedAgent] -= c.penalty; | |
| // Pay reward to agent | |
| uint256 reward = c.reward; | |
| totalEscrowed -= reward; | |
| totalRewardsPaid += reward; | |
| // Return penalty collateral + pay reward | |
| uint256 payout = reward + c.penalty; | |
| payable(c.assignedAgent).transfer(payout); | |
| // Record on registry | |
| registry.recordContractOutcome(c.assignedAgent, true, reward); | |
| emit ContractCompleted(contractId, c.assignedAgent, reward); | |
| } | |
| /** | |
| * @notice Mark a contract as failed. Penalty is forfeited. | |
| */ | |
| function failContract(bytes32 contractId) external { | |
| require(msg.sender == admin, "Only admin/verifier"); | |
| EconomicContract storage c = contracts[contractId]; | |
| require(c.status == ContractStatus.Assigned, "Not assigned"); | |
| c.status = ContractStatus.Failed; | |
| // Release exposure | |
| activeExposure[c.assignedAgent] -= c.penalty; | |
| // Forfeit penalty collateral | |
| totalPenaltiesCollected += c.penalty; | |
| // Return escrowed reward to issuer | |
| totalEscrowed -= c.reward; | |
| payable(c.issuer).transfer(c.reward); | |
| // Record on registry | |
| registry.recordContractOutcome(c.assignedAgent, false, c.penalty); | |
| emit ContractFailed(contractId, c.assignedAgent, c.penalty); | |
| } | |
| /** | |
| * @notice Expire contracts past deadline. Anyone can call this. | |
| */ | |
| function expireContract(bytes32 contractId) external { | |
| EconomicContract storage c = contracts[contractId]; | |
| require(c.status == ContractStatus.Open, "Not open"); | |
| require(block.timestamp >= c.deadline, "Not expired yet"); | |
| c.status = ContractStatus.Expired; | |
| totalEscrowed -= c.reward; | |
| payable(c.issuer).transfer(c.reward); | |
| emit ContractExpired(contractId); | |
| } | |
| // ----------------------------------------------------------------------- | |
| // Views | |
| // ----------------------------------------------------------------------- | |
| function getContract(bytes32 contractId) external view returns (EconomicContract memory) { | |
| return contracts[contractId]; | |
| } | |
| function getContractCount() external view returns (uint256) { | |
| return contractIds.length; | |
| } | |
| function getExposure(address agent) external view returns (uint256) { | |
| return activeExposure[agent]; | |
| } | |
| function getEconomicsSummary() external view returns ( | |
| uint256 _totalRewards, | |
| uint256 _totalPenalties, | |
| uint256 _totalEscrowed, | |
| uint256 _contractCount | |
| ) { | |
| return (totalRewardsPaid, totalPenaltiesCollected, totalEscrowed, contractIds.length); | |
| } | |
| // ----------------------------------------------------------------------- | |
| // Admin | |
| // ----------------------------------------------------------------------- | |
| function updateAdmin(address newAdmin) external { | |
| require(msg.sender == admin, "Only admin"); | |
| admin = newAdmin; | |
| } | |
| receive() external payable {} | |
| } | |