Files
smom-dbis-138/test/bridge/atomic/AtomicBridgeCoordinator.t.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

272 lines
12 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../../../contracts/bridge/atomic/AtomicBridgeCoordinator.sol";
import "../../../contracts/bridge/atomic/AtomicFeePolicy.sol";
import "../../../contracts/bridge/atomic/AtomicFulfillerRegistry.sol";
import "../../../contracts/bridge/atomic/AtomicLiquidityVault.sol";
import "../../../contracts/bridge/atomic/AtomicObligationEscrow.sol";
import "../../../contracts/bridge/atomic/AtomicQuoteEngine.sol";
import "../../../contracts/bridge/atomic/AtomicSettlementRouter.sol";
import "../../../contracts/bridge/atomic/AtomicSlashingManager.sol";
import "../../../contracts/bridge/atomic/AtomicTypes.sol";
import "../../../contracts/bridge/atomic/interfaces/IAtomicSettlementAdapter.sol";
import "../../dbis/MockMintableToken.sol";
contract MockAtomicSettlementAdapter is IAtomicSettlementAdapter {
address public lastToken;
uint256 public lastAmount;
address public lastRecipient;
bytes public lastData;
function executeSettlement(
bytes32 obligationId,
address token,
uint256 amount,
address recipient,
bytes calldata data
) external payable returns (bytes32 settlementId) {
lastToken = token;
lastAmount = amount;
lastRecipient = recipient;
lastData = data;
settlementId = keccak256(abi.encode(obligationId, token, amount, recipient, data, block.timestamp));
}
}
contract AtomicBridgeCoordinatorTest is Test {
bytes32 internal constant MOCK_SETTLEMENT_MODE = keccak256("MOCK_SETTLEMENT_MODE");
MockMintableToken internal sourceToken;
MockMintableToken internal destinationToken;
MockMintableToken internal bondToken;
AtomicLiquidityVault internal vault;
AtomicFulfillerRegistry internal registry;
AtomicFeePolicy internal feePolicy;
AtomicObligationEscrow internal escrow;
AtomicSettlementRouter internal router;
AtomicSlashingManager internal slashingManager;
AtomicBridgeCoordinator internal coordinator;
AtomicQuoteEngine internal quoteEngine;
MockAtomicSettlementAdapter internal settlementAdapter;
address internal user = address(0x1111);
address internal fulfiller = address(0x2222);
address internal protocolTreasury = address(0x3333);
bytes32 internal corridorId;
function setUp() public {
sourceToken = new MockMintableToken("Chain 138 USD", "cUSDC", 6, address(this));
destinationToken = new MockMintableToken("Mainnet Wrapped USD", "cWUSDC", 6, address(this));
bondToken = new MockMintableToken("Bond USDC", "bUSDC", 6, address(this));
vault = new AtomicLiquidityVault(address(this));
registry = new AtomicFulfillerRegistry(address(bondToken), address(this));
feePolicy = new AtomicFeePolicy(address(this));
escrow = new AtomicObligationEscrow(address(this));
router = new AtomicSettlementRouter(address(this));
slashingManager = new AtomicSlashingManager(address(registry), address(this));
coordinator = new AtomicBridgeCoordinator(
address(vault),
address(registry),
address(escrow),
address(router),
address(feePolicy),
address(slashingManager),
protocolTreasury,
address(this)
);
quoteEngine = new AtomicQuoteEngine(
address(coordinator),
address(vault),
address(registry),
address(feePolicy)
);
settlementAdapter = new MockAtomicSettlementAdapter();
vault.grantRole(vault.COORDINATOR_ROLE(), address(coordinator));
vault.grantRole(vault.RECONCILER_ROLE(), address(coordinator));
registry.grantRole(registry.COORDINATOR_ROLE(), address(coordinator));
registry.grantRole(registry.SLASHER_ROLE(), address(slashingManager));
escrow.grantRole(escrow.COORDINATOR_ROLE(), address(coordinator));
router.grantRole(router.COORDINATOR_ROLE(), address(coordinator));
slashingManager.grantRole(slashingManager.COORDINATOR_ROLE(), address(coordinator));
router.setAdapter(MOCK_SETTLEMENT_MODE, address(settlementAdapter));
corridorId = coordinator.getCorridorId(138, 1, address(sourceToken), address(destinationToken));
coordinator.configureCorridor(
AtomicTypes.CorridorConfig({
enabled: true,
degraded: false,
sourceChain: 138,
destinationChain: 1,
assetIn: address(sourceToken),
assetOut: address(destinationToken),
maxNotional: 500_000e6,
maxReservedBps: 5_000,
targetBuffer: 10_000e6,
maxSettlementBacklog: 250_000e6,
maxOracleDriftBps: 500,
fulfilmentTimeout: 1 days,
settlementTimeout: 2 days,
defaultSettlementMode: MOCK_SETTLEMENT_MODE
})
);
feePolicy.setCorridorPolicy(corridorId, 100, 50, 12_000, 1_000, 1 days, 2 days);
vault.setTargetBuffer(corridorId, address(destinationToken), 10_000e6);
registry.setFulfillerActive(fulfiller, true);
registry.setCorridorAuthorization(fulfiller, corridorId, true);
destinationToken.mint(address(this), 100_000e6);
destinationToken.approve(address(vault), type(uint256).max);
vault.fundCorridor(corridorId, address(destinationToken), 100_000e6);
bondToken.mint(fulfiller, 100_000e6);
vm.startPrank(fulfiller);
bondToken.approve(address(registry), type(uint256).max);
registry.depositBond(50_000e6);
vm.stopPrank();
sourceToken.mint(user, 50_000e6);
vm.prank(user);
sourceToken.approve(address(escrow), type(uint256).max);
}
function testCreateIntentReservesDestinationLiquidity() public {
bytes32 obligationId = _createIntent(1_000e6, 900e6, block.timestamp + 1 hours);
AtomicTypes.AtomicObligation memory obligation = coordinator.getObligation(obligationId);
assertEq(uint8(obligation.status), uint8(AtomicTypes.ObligationStatus.IntentCreated));
assertEq(obligation.sourceEscrow, 1_000e6);
assertEq(obligation.destinationReserve, 900e6);
AtomicTypes.CorridorLiquidityState memory state =
vault.getCorridorLiquidityState(corridorId, address(destinationToken));
assertEq(state.totalLiquidity, 100_000e6);
assertEq(state.reservedLiquidity, 900e6);
assertEq(state.freeLiquidity, 99_100e6);
}
function testQuoteReflectsExecutionReadyPath() public {
AtomicTypes.AtomicQuote memory q = quoteEngine.quote(corridorId, 1_000e6, 900e6, fulfiller);
assertEq(uint8(q.routeClass), uint8(AtomicTypes.RouteClass.ExecutionReady));
assertTrue(q.fulfillerAuthorized);
assertTrue(q.fulfillerBondSufficient);
assertEq(q.protocolFee, 5e6);
assertEq(q.fulfillerFee, 10e6);
assertEq(q.requiredBond, 1_080e6);
}
function testCommitFailsWhenBondIsInsufficient() public {
address weakFulfiller = address(0x4444);
registry.setFulfillerActive(weakFulfiller, true);
registry.setCorridorAuthorization(weakFulfiller, corridorId, true);
bondToken.mint(weakFulfiller, 100e6);
vm.startPrank(weakFulfiller);
bondToken.approve(address(registry), type(uint256).max);
registry.depositBond(100e6);
vm.stopPrank();
bytes32 obligationId = _createIntent(1_000e6, 900e6, block.timestamp + 1 hours);
vm.prank(weakFulfiller);
vm.expectRevert(AtomicFulfillerRegistry.InsufficientAvailableBond.selector);
coordinator.submitCommitment(obligationId, bytes32(0));
}
function testSuccessfulSettlementReleasesBondAndReplenishesLiquidity() public {
bytes32 obligationId = _createIntent(1_000e6, 900e6, block.timestamp + 1 hours);
vm.prank(fulfiller);
coordinator.submitCommitment(obligationId, bytes32(0));
assertEq(destinationToken.balanceOf(user), 900e6);
AtomicTypes.CorridorLiquidityState memory postFulfill =
vault.getCorridorLiquidityState(corridorId, address(destinationToken));
assertEq(postFulfill.totalLiquidity, 99_100e6);
assertEq(postFulfill.settlementBacklog, 900e6);
bytes memory settlementData = abi.encodePacked(bytes32(uint256(123)));
bytes32 settlementId = coordinator.initiateSettlement(obligationId, settlementData);
assertTrue(settlementId != bytes32(0));
assertEq(settlementAdapter.lastToken(), address(sourceToken));
assertEq(settlementAdapter.lastAmount(), 985e6);
assertEq(settlementAdapter.lastRecipient(), user);
assertEq(sourceToken.balanceOf(protocolTreasury), 5e6);
assertEq(sourceToken.balanceOf(fulfiller), 10e6);
destinationToken.mint(address(this), 900e6);
destinationToken.approve(address(vault), 900e6);
coordinator.confirmSettlement(obligationId, 900e6);
AtomicTypes.AtomicObligation memory obligation = coordinator.getObligation(obligationId);
assertEq(uint8(obligation.status), uint8(AtomicTypes.ObligationStatus.Settled));
assertEq(registry.lockedBond(fulfiller), 0);
AtomicTypes.CorridorLiquidityState memory postSettlement =
vault.getCorridorLiquidityState(corridorId, address(destinationToken));
assertEq(postSettlement.totalLiquidity, 100_000e6);
assertEq(postSettlement.settlementBacklog, 0);
}
function testExpiredIntentRefundsAndReleasesReservation() public {
uint256 payerBalanceBefore = sourceToken.balanceOf(user);
bytes32 obligationId = _createIntent(1_000e6, 900e6, block.timestamp + 30 minutes);
vm.warp(block.timestamp + 31 minutes);
coordinator.refundExpiredIntent(obligationId);
AtomicTypes.AtomicObligation memory obligation = coordinator.getObligation(obligationId);
assertEq(uint8(obligation.status), uint8(AtomicTypes.ObligationStatus.Refunded));
assertEq(sourceToken.balanceOf(user), payerBalanceBefore);
AtomicTypes.CorridorLiquidityState memory state =
vault.getCorridorLiquidityState(corridorId, address(destinationToken));
assertEq(state.reservedLiquidity, 0);
}
function testSettlementTimeoutSlashesBondAndDegradesCorridor() public {
bytes32 obligationId = _createIntent(1_000e6, 900e6, block.timestamp + 1 hours);
vm.prank(fulfiller);
coordinator.submitCommitment(obligationId, bytes32(0));
coordinator.initiateSettlement(obligationId, abi.encodePacked(bytes32(uint256(456))));
uint256 treasuryBondBefore = bondToken.balanceOf(protocolTreasury);
vm.warp(block.timestamp + 3 days);
coordinator.handleSettlementTimeout(obligationId);
AtomicTypes.AtomicObligation memory obligation = coordinator.getObligation(obligationId);
assertEq(uint8(obligation.status), uint8(AtomicTypes.ObligationStatus.Slashed));
AtomicTypes.CorridorConfig memory cfg = coordinator.getCorridorConfig(corridorId);
assertTrue(cfg.degraded);
assertEq(bondToken.balanceOf(protocolTreasury), treasuryBondBefore + 1_080e6);
}
function testConcurrentReservationsDoNotOverReserveLiquidity() public {
_createIntent(10_000e6, 40_000e6, block.timestamp + 1 hours);
_createIntent(10_000e6, 10_000e6, block.timestamp + 1 hours);
vm.expectRevert(AtomicBridgeCoordinator.ReservedLiquidityLimitExceeded.selector);
_createIntent(10_000e6, 5_000e6, block.timestamp + 1 hours);
}
function _createIntent(uint256 amountIn, uint256 minAmountOut, uint256 deadline) internal returns (bytes32) {
vm.prank(user);
return coordinator.createIntent(
AtomicBridgeCoordinator.CreateIntentParams({
sourceChain: 138,
destinationChain: 1,
assetIn: address(sourceToken),
assetOut: address(destinationToken),
amountIn: amountIn,
minAmountOut: minAmountOut,
recipient: user,
deadline: deadline,
routeId: corridorId
})
);
}
}