Files
smom-dbis-138/test/bridge/trustless/DualRouterBridgeSwapCoordinator.t.sol
2026-03-02 12:14:09 -08:00

196 lines
7.4 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {Test} from "forge-std/Test.sol";
import "../../../contracts/bridge/trustless/DualRouterBridgeSwapCoordinator.sol";
import "../../../contracts/bridge/trustless/ChallengeManager.sol";
import "../../../contracts/bridge/trustless/BondManager.sol";
import "../../../contracts/bridge/trustless/LiquidityPoolETH.sol";
import "../../../contracts/bridge/trustless/InboxETH.sol";
import "../../../contracts/bridge/trustless/EnhancedSwapRouter.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MockERC20 is ERC20 {
constructor(string memory name, string memory symbol) ERC20(name, symbol) {
_mint(msg.sender, 1000000 ether);
}
function mint(address to, uint256 amount) external {
_mint(to, amount);
}
}
/**
* Mock SwapRouter: implements swapToStablecoin(..., bytes) returns (uint256).
* Transfers stablecoinToken to msg.sender (caller) for amountIn (1:1 mock).
*/
contract MockBasicSwapRouter {
function swapToStablecoin(
LiquidityPoolETH.AssetType,
address stablecoinToken,
uint256 amountIn,
uint256,
bytes calldata
) external payable returns (uint256) {
if (msg.value > 0) {
// Caller sent ETH; in real router it would wrap and swap. We just transfer stablecoin.
}
uint256 out = amountIn; // 1:1 mock
require(IERC20(stablecoinToken).transfer(msg.sender, out), "transfer failed");
return out;
}
}
/**
* Mock EnhancedSwapRouter: implements swapToStablecoin(..., SwapProvider) returns (uint256, SwapProvider).
*/
contract MockEnhancedSwapRouter {
function swapToStablecoin(
LiquidityPoolETH.AssetType,
address stablecoinToken,
uint256 amountIn,
uint256,
EnhancedSwapRouter.SwapProvider
) external payable returns (uint256 amountOut, EnhancedSwapRouter.SwapProvider providerUsed) {
uint256 out = amountIn;
require(IERC20(stablecoinToken).transfer(msg.sender, out), "transfer failed");
return (out, EnhancedSwapRouter.SwapProvider.UniswapV3);
}
}
contract DualRouterBridgeSwapCoordinatorTest is Test {
DualRouterBridgeSwapCoordinator public coordinator;
BondManager public bondManager;
ChallengeManager public challengeManager;
LiquidityPoolETH public liquidityPool;
InboxETH public inbox;
MockBasicSwapRouter public basicRouter;
MockEnhancedSwapRouter public enhancedRouter;
MockERC20 public weth;
MockERC20 public usdt;
address public deployer = address(0xDE01);
address public relayer = address(0x2222);
address public lpProvider = address(0x3333);
address public recipient = address(0x4444);
uint256 constant BOND_MULTIPLIER = 11000;
uint256 constant MIN_BOND = 1 ether;
uint256 constant CHALLENGE_WINDOW = 30 minutes;
uint256 constant LP_FEE_BPS = 5;
uint256 constant MIN_LIQUIDITY_RATIO_BPS = 11000;
function setUp() public {
vm.startPrank(deployer);
weth = new MockERC20("WETH", "WETH");
usdt = new MockERC20("USDT", "USDT");
bondManager = new BondManager(BOND_MULTIPLIER, MIN_BOND);
challengeManager = new ChallengeManager(address(bondManager), CHALLENGE_WINDOW);
liquidityPool = new LiquidityPoolETH(address(weth), LP_FEE_BPS, MIN_LIQUIDITY_RATIO_BPS);
inbox = new InboxETH(address(bondManager), address(challengeManager), address(liquidityPool));
basicRouter = new MockBasicSwapRouter();
enhancedRouter = new MockEnhancedSwapRouter();
coordinator = new DualRouterBridgeSwapCoordinator(
address(inbox),
address(liquidityPool),
address(basicRouter),
address(enhancedRouter),
address(challengeManager)
);
liquidityPool.authorizeRelease(address(coordinator));
usdt.mint(address(basicRouter), 1000 ether);
usdt.mint(address(enhancedRouter), 1000 ether);
vm.deal(lpProvider, 1000 ether);
vm.deal(relayer, 100 ether);
vm.stopPrank();
}
function _setupFinalizedClaim(uint256 depositAmount) internal returns (uint256 depositId) {
depositId = uint256(keccak256(abi.encodePacked(block.timestamp, recipient, depositAmount)));
vm.prank(address(inbox));
liquidityPool.addPendingClaim(depositAmount, LiquidityPoolETH.AssetType.ETH);
// Simulate ChallengeManager having a finalized claim (we don't have Inbox.submitClaim in this test,
// so we need to push claim state into ChallengeManager - but ChallengeManager is deployed and
// we can't easily inject a claim without going through Inbox. So we use a different approach:
// use the real flow: deploy Lockbox138, user deposits, relayer submits claim, time passes, finalize.
// For unit test we can use vm.mockCall to make getClaim return a finalized claim.
vm.mockCall(
address(challengeManager),
abi.encodeWithSelector(ChallengeManager.getClaim.selector, depositId),
abi.encode(
ChallengeManager.Claim({
depositId: depositId,
asset: address(0),
recipient: recipient,
amount: depositAmount,
challengeWindowEnd: block.timestamp,
finalized: true,
challenged: false
})
)
);
return depositId;
}
function testDeployment() public view {
assertEq(address(coordinator.inbox()), address(inbox));
assertEq(address(coordinator.liquidityPool()), address(liquidityPool));
assertEq(address(coordinator.swapRouter()), address(basicRouter));
assertEq(address(coordinator.enhancedSwapRouter()), address(enhancedRouter));
assertEq(address(coordinator.challengeManager()), address(challengeManager));
}
function testCanSwapWhenFinalized() public {
uint256 depositId = 1;
vm.mockCall(
address(challengeManager),
abi.encodeWithSelector(ChallengeManager.getClaim.selector, depositId),
abi.encode(
ChallengeManager.Claim({
depositId: depositId,
asset: address(0),
recipient: recipient,
amount: 1 ether,
challengeWindowEnd: block.timestamp,
finalized: true,
challenged: false
})
)
);
(bool canSwap, string memory reason) = coordinator.canSwap(depositId);
assertTrue(canSwap);
assertEq(reason, "");
}
function testCanSwapWhenNotFinalized() public {
uint256 depositId = 2;
vm.mockCall(
address(challengeManager),
abi.encodeWithSelector(ChallengeManager.getClaim.selector, depositId),
abi.encode(
ChallengeManager.Claim({
depositId: depositId,
asset: address(0),
recipient: recipient,
amount: 1 ether,
challengeWindowEnd: block.timestamp,
finalized: false,
challenged: false
})
)
);
(bool canSwap, string memory reason) = coordinator.canSwap(depositId);
assertFalse(canSwap);
assertEq(reason, "Claim not finalized");
}
}