// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/access/AccessControl.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {IAtomicFulfillerRegistry} from "./interfaces/IAtomicFulfillerRegistry.sol"; contract AtomicFulfillerRegistry is AccessControl, IAtomicFulfillerRegistry { using SafeERC20 for IERC20; bytes32 public constant COORDINATOR_ROLE = keccak256("COORDINATOR_ROLE"); bytes32 public constant SLASHER_ROLE = keccak256("SLASHER_ROLE"); bytes32 public constant AUTH_MANAGER_ROLE = keccak256("AUTH_MANAGER_ROLE"); struct LockedBond { address fulfiller; bytes32 corridorId; uint256 amount; bool active; } IERC20 public immutable bondToken; mapping(address => uint256) public totalBond; mapping(address => uint256) public lockedBond; mapping(address => bool) public fulfillerActive; mapping(address => mapping(bytes32 => bool)) public corridorAuthorization; mapping(bytes32 => LockedBond) public lockedBonds; event BondDeposited(address indexed fulfiller, uint256 amount); event BondWithdrawn(address indexed fulfiller, address indexed recipient, uint256 amount); event FulfillerActivationSet(address indexed fulfiller, bool active); event CorridorAuthorizationSet(address indexed fulfiller, bytes32 indexed corridorId, bool authorized); event BondLocked(bytes32 indexed obligationId, address indexed fulfiller, bytes32 indexed corridorId, uint256 amount); event BondReleased(bytes32 indexed obligationId, address indexed fulfiller, uint256 amount); event BondSlashed(bytes32 indexed obligationId, address indexed fulfiller, address indexed recipient, uint256 amount); error InsufficientAvailableBond(); error UnauthorizedFulfiller(); error BondAlreadyLocked(); error BondMissing(); constructor(address bondToken_, address admin) { bondToken = IERC20(bondToken_); _grantRole(DEFAULT_ADMIN_ROLE, admin); _grantRole(AUTH_MANAGER_ROLE, admin); } function depositBond(uint256 amount) external { bondToken.safeTransferFrom(msg.sender, address(this), amount); totalBond[msg.sender] += amount; emit BondDeposited(msg.sender, amount); } function withdrawBond(uint256 amount, address recipient) external { if (availableBond(msg.sender) < amount) revert InsufficientAvailableBond(); totalBond[msg.sender] -= amount; bondToken.safeTransfer(recipient, amount); emit BondWithdrawn(msg.sender, recipient, amount); } function setFulfillerActive(address fulfiller, bool active) external onlyRole(AUTH_MANAGER_ROLE) { fulfillerActive[fulfiller] = active; emit FulfillerActivationSet(fulfiller, active); } function setCorridorAuthorization(address fulfiller, bytes32 corridorId, bool authorized) external onlyRole(AUTH_MANAGER_ROLE) { corridorAuthorization[fulfiller][corridorId] = authorized; emit CorridorAuthorizationSet(fulfiller, corridorId, authorized); } function lockBond(bytes32 obligationId, address fulfiller, bytes32 corridorId, uint256 amount) external onlyRole(COORDINATOR_ROLE) { if (lockedBonds[obligationId].active) revert BondAlreadyLocked(); if (!isFulfillerAuthorized(fulfiller, corridorId)) revert UnauthorizedFulfiller(); if (availableBond(fulfiller) < amount) revert InsufficientAvailableBond(); lockedBond[fulfiller] += amount; lockedBonds[obligationId] = LockedBond({ fulfiller: fulfiller, corridorId: corridorId, amount: amount, active: true }); emit BondLocked(obligationId, fulfiller, corridorId, amount); } function releaseBond(bytes32 obligationId) external onlyRole(COORDINATOR_ROLE) returns (uint256 amount) { LockedBond memory entry = lockedBonds[obligationId]; if (!entry.active) revert BondMissing(); lockedBond[entry.fulfiller] -= entry.amount; delete lockedBonds[obligationId]; emit BondReleased(obligationId, entry.fulfiller, entry.amount); return entry.amount; } function slashBond(bytes32 obligationId, address recipient) external onlyRole(SLASHER_ROLE) returns (uint256 amount) { LockedBond memory entry = lockedBonds[obligationId]; if (!entry.active) revert BondMissing(); lockedBond[entry.fulfiller] -= entry.amount; totalBond[entry.fulfiller] -= entry.amount; delete lockedBonds[obligationId]; bondToken.safeTransfer(recipient, entry.amount); emit BondSlashed(obligationId, entry.fulfiller, recipient, entry.amount); return entry.amount; } function availableBond(address fulfiller) public view returns (uint256) { return totalBond[fulfiller] - lockedBond[fulfiller]; } function isFulfillerAuthorized(address fulfiller, bytes32 corridorId) public view returns (bool) { return fulfillerActive[fulfiller] && corridorAuthorization[fulfiller][corridorId]; } function canCover(address fulfiller, uint256 amount) external view returns (bool) { return availableBond(fulfiller) >= amount; } }