Files
smom-dbis-138/test/bridge/atomic/AtomicX402Intent.t.sol

313 lines
12 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.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/AtomicSettlementRouter.sol";
import "../../../contracts/bridge/atomic/AtomicSlashingManager.sol";
import "../../../contracts/bridge/atomic/AtomicTypes.sol";
import "../../../contracts/bridge/atomic/AtomicX402SourceAdapter.sol";
import "../../../contracts/bridge/atomic/CWMultiTokenBridgeSettlementAdapter.sol";
import "../../dbis/MockMintableToken.sol";
contract MockBurnMintToken is ERC20 {
uint8 private immutable _decimals;
constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC20(name_, symbol_) {
_decimals = decimals_;
}
function mint(address to, uint256 amount) external {
_mint(to, amount);
}
function burn(uint256 amount) external {
_burn(msg.sender, amount);
}
function decimals() public view override returns (uint8) {
return _decimals;
}
}
contract MockCWMultiTokenBridgeL1 {
uint256 public fee;
address public feeToken;
bytes32 public nextMessageId = keccak256("cw-message");
address public lastToken;
uint64 public lastSelector;
address public lastRecipient;
uint256 public lastAmount;
function setFee(uint256 fee_) external {
fee = fee_;
}
function setFeeToken(address feeToken_) external {
feeToken = feeToken_;
}
function calculateFee(address, uint64, address, uint256) external view returns (uint256) {
return fee;
}
function lockAndSend(address token, uint64 selector, address recipient, uint256 amount)
external
payable
returns (bytes32)
{
if (feeToken == address(0)) {
require(msg.value >= fee, "native fee");
} else {
IERC20(feeToken).transferFrom(msg.sender, address(this), fee);
}
IERC20(token).transferFrom(msg.sender, address(this), amount);
lastToken = token;
lastSelector = selector;
lastRecipient = recipient;
lastAmount = amount;
return nextMessageId;
}
}
contract AtomicX402IntentTest is Test {
uint64 internal constant SOURCE_CHAIN = 138;
uint64 internal constant POLYGON_SELECTOR = 4051577828743386545;
bytes32 internal constant SETTLEMENT_MODE = keccak256("CW_POLYGON_SETTLEMENT");
MockBurnMintToken internal legacyCUSDC;
MockBurnMintToken internal cUSDCV2;
MockMintableToken internal cWUSDCV2;
MockMintableToken internal bondToken;
AtomicLiquidityVault internal vault;
AtomicFulfillerRegistry internal registry;
AtomicFeePolicy internal feePolicy;
AtomicObligationEscrow internal escrow;
AtomicSettlementRouter internal router;
AtomicSlashingManager internal slashingManager;
AtomicBridgeCoordinator internal coordinator;
AtomicX402SourceAdapter internal sourceAdapter;
address internal user = address(0x1111);
address internal fulfiller = address(0x2222);
address internal protocolTreasury = address(0x3333);
bytes32 internal corridorId;
function setUp() public {
legacyCUSDC = new MockBurnMintToken("Legacy cUSDC", "cUSDC", 6);
cUSDCV2 = new MockBurnMintToken("V2 cUSDC", "cUSDC_V2", 6);
cWUSDCV2 = new MockMintableToken("Polygon cWUSDC V2", "cWUSDC_V2", 6, address(this));
bondToken = new MockMintableToken("Bond", "BOND", 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)
);
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));
corridorId = coordinator.getCorridorId(SOURCE_CHAIN, POLYGON_SELECTOR, address(cUSDCV2), address(cWUSDCV2));
coordinator.configureCorridor(
AtomicTypes.CorridorConfig({
enabled: true,
degraded: false,
sourceChain: SOURCE_CHAIN,
destinationChain: POLYGON_SELECTOR,
assetIn: address(cUSDCV2),
assetOut: address(cWUSDCV2),
maxNotional: 500_000e6,
maxReservedBps: 8_000,
targetBuffer: 10_000e6,
maxSettlementBacklog: 250_000e6,
maxOracleDriftBps: 500,
fulfilmentTimeout: 1 days,
settlementTimeout: 2 days,
defaultSettlementMode: SETTLEMENT_MODE
})
);
feePolicy.setCorridorPolicy(corridorId, 100, 50, 12_000, 1_000, 1 days, 2 days);
vault.setTargetBuffer(corridorId, address(cWUSDCV2), 10_000e6);
registry.setFulfillerActive(fulfiller, true);
registry.setCorridorAuthorization(fulfiller, corridorId, true);
cWUSDCV2.mint(address(this), 100_000e6);
cWUSDCV2.approve(address(vault), type(uint256).max);
vault.fundCorridor(corridorId, address(cWUSDCV2), 100_000e6);
bondToken.mint(fulfiller, 100_000e6);
vm.startPrank(fulfiller);
bondToken.approve(address(registry), type(uint256).max);
registry.depositBond(50_000e6);
vm.stopPrank();
sourceAdapter = new AtomicX402SourceAdapter(
address(legacyCUSDC),
address(cUSDCV2),
address(cWUSDCV2),
address(coordinator),
SOURCE_CHAIN,
POLYGON_SELECTOR
);
coordinator.grantRole(coordinator.INTENT_ADAPTER_ROLE(), address(sourceAdapter));
}
function testLegacyCUSDCNormalizesToV2AndCreatesRefundableIntent() public {
legacyCUSDC.mint(user, 1_000e6);
vm.startPrank(user);
legacyCUSDC.approve(address(sourceAdapter), 1_000e6);
bytes32 obligationId = sourceAdapter.createIntent(
address(legacyCUSDC), 1_000e6, 990e6, user, block.timestamp + 1 hours, corridorId
);
vm.stopPrank();
assertEq(legacyCUSDC.balanceOf(user), 0);
assertEq(legacyCUSDC.totalSupply(), 0);
assertEq(cUSDCV2.balanceOf(address(escrow)), 1_000e6);
assertEq(cUSDCV2.balanceOf(address(sourceAdapter)), 0);
assertEq(coordinator.getRefundRecipient(obligationId), user);
AtomicTypes.AtomicIntent memory intent = coordinator.getIntent(obligationId);
assertEq(intent.assetIn, address(cUSDCV2));
assertEq(intent.assetOut, address(cWUSDCV2));
assertEq(intent.destinationChain, POLYGON_SELECTOR);
}
function testDirectCUSDCV2InputCreatesIntentWithoutLegacyBurn() public {
cUSDCV2.mint(user, 1_000e6);
legacyCUSDC.mint(user, 1_000e6);
vm.startPrank(user);
cUSDCV2.approve(address(sourceAdapter), 1_000e6);
sourceAdapter.createIntent(address(cUSDCV2), 1_000e6, 990e6, user, block.timestamp + 1 hours, corridorId);
vm.stopPrank();
assertEq(cUSDCV2.balanceOf(address(escrow)), 1_000e6);
assertEq(legacyCUSDC.balanceOf(user), 1_000e6);
}
function testExpiredAdapterIntentRefundsUserWithNormalizedV2() public {
legacyCUSDC.mint(user, 1_000e6);
vm.startPrank(user);
legacyCUSDC.approve(address(sourceAdapter), 1_000e6);
bytes32 obligationId = sourceAdapter.createIntent(
address(legacyCUSDC), 1_000e6, 990e6, user, block.timestamp + 30 minutes, corridorId
);
vm.stopPrank();
vm.warp(block.timestamp + 31 minutes);
coordinator.refundExpiredIntent(obligationId);
assertEq(cUSDCV2.balanceOf(user), 1_000e6);
assertEq(cUSDCV2.balanceOf(address(sourceAdapter)), 0);
}
function testAsyncDestinationIntentRecordsPolygonProofBeforeSettlement() public {
bytes32 asyncCorridorId = coordinator.getCorridorId(
SOURCE_CHAIN, POLYGON_SELECTOR, address(cUSDCV2), address(0x7476a64B37732698b9f729fdB373a7B65e9677e9)
);
coordinator.configureCorridor(
AtomicTypes.CorridorConfig({
enabled: true,
degraded: false,
sourceChain: SOURCE_CHAIN,
destinationChain: POLYGON_SELECTOR,
assetIn: address(cUSDCV2),
assetOut: address(0x7476a64B37732698b9f729fdB373a7B65e9677e9),
maxNotional: 500_000e6,
maxReservedBps: 8_000,
targetBuffer: 0,
maxSettlementBacklog: 250_000e6,
maxOracleDriftBps: 500,
fulfilmentTimeout: 1 days,
settlementTimeout: 2 days,
defaultSettlementMode: SETTLEMENT_MODE
})
);
coordinator.setAsyncDestinationCorridor(asyncCorridorId, true);
registry.setCorridorAuthorization(fulfiller, asyncCorridorId, true);
AtomicX402SourceAdapter asyncSourceAdapter = new AtomicX402SourceAdapter(
address(legacyCUSDC),
address(cUSDCV2),
address(0x7476a64B37732698b9f729fdB373a7B65e9677e9),
address(coordinator),
SOURCE_CHAIN,
POLYGON_SELECTOR
);
coordinator.grantRole(coordinator.INTENT_ADAPTER_ROLE(), address(asyncSourceAdapter));
legacyCUSDC.mint(user, 1_100e6);
vm.startPrank(user);
legacyCUSDC.approve(address(asyncSourceAdapter), 1_100e6);
bytes32 obligationId = asyncSourceAdapter.createIntent(
address(legacyCUSDC), 1_100e6, 1_000e6, user, block.timestamp + 1 hours, asyncCorridorId
);
vm.stopPrank();
AtomicTypes.CorridorLiquidityState memory state =
vault.getCorridorLiquidityState(asyncCorridorId, address(0x7476a64B37732698b9f729fdB373a7B65e9677e9));
assertEq(state.reservedLiquidity, 0);
vm.prank(fulfiller);
coordinator.submitAsyncDestinationFulfillment(
obligationId, SETTLEMENT_MODE, keccak256("polygon-tx"), keccak256("invoice")
);
AtomicTypes.AtomicObligation memory obligation = coordinator.getObligation(obligationId);
assertEq(uint8(obligation.status), uint8(AtomicTypes.ObligationStatus.Fulfilled));
assertEq(obligation.fulfiller, fulfiller);
}
function testSettlementAdapterCallsCwBridgeLockAndSend() public {
MockCWMultiTokenBridgeL1 bridge = new MockCWMultiTokenBridgeL1();
bridge.setFee(0.01 ether);
CWMultiTokenBridgeSettlementAdapter adapter =
new CWMultiTokenBridgeSettlementAdapter(address(bridge), POLYGON_SELECTOR);
cUSDCV2.mint(address(adapter), 1_000e6);
bytes32 settlementId = adapter.executeSettlement{value: 0.01 ether}(
keccak256("obligation"),
address(cUSDCV2),
1_000e6,
fulfiller,
abi.encode(fulfiller, POLYGON_SELECTOR, uint256(0.01 ether))
);
assertEq(settlementId, bridge.nextMessageId());
assertEq(bridge.lastToken(), address(cUSDCV2));
assertEq(bridge.lastSelector(), POLYGON_SELECTOR);
assertEq(bridge.lastRecipient(), fulfiller);
assertEq(bridge.lastAmount(), 1_000e6);
assertEq(cUSDCV2.balanceOf(address(bridge)), 1_000e6);
}
}