Files
smom-dbis-138/contracts/bridge/atomic/AtomicFulfillerRegistry.sol
defiQUG 76aa419320 feat: bridges, PMM, flash workflow, token-aggregation, and deployment docs
- CCIP/trustless bridge contracts, GRU tokens, DEX/PMM tests, reserve vault.
- Token-aggregation service routes, planner, chain config, relay env templates.
- Config snapshots and multi-chain deployment markdown updates.
- gitignore services/btc-intake/dist/ (tsc output); do not track dist.

Run forge build && forge test before deploy (large solc graph).

Made-with: Cursor
2026-04-07 23:40:52 -07:00

134 lines
5.3 KiB
Solidity

// 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;
}
}