The home for Hyperlane core contracts, sdk packages, and other infrastructure
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
hyperlane-monorepo/solidity/test/LiquidityLayerRouter.t.sol

294 lines
9.9 KiB

// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import {LiquidityLayerRouter} from "../contracts/middleware/liquidity-layer/LiquidityLayerRouter.sol";
import {CircleBridgeAdapter} from "../contracts/middleware/liquidity-layer/adapters/CircleBridgeAdapter.sol";
import {MockToken} from "../contracts/mock/MockToken.sol";
import {TestTokenRecipient} from "../contracts/test/TestTokenRecipient.sol";
import {TestRecipient} from "../contracts/test/TestRecipient.sol";
import {MockCircleMessageTransmitter} from "../contracts/mock/MockCircleMessageTransmitter.sol";
import {MockCircleTokenMessenger} from "../contracts/mock/MockCircleTokenMessenger.sol";
import {MockHyperlaneEnvironment} from "../contracts/mock/MockHyperlaneEnvironment.sol";
import {TypeCasts} from "../contracts/libs/TypeCasts.sol";
contract LiquidityLayerRouterTest is Test {
MockHyperlaneEnvironment testEnvironment;
LiquidityLayerRouter originLiquidityLayerRouter;
LiquidityLayerRouter destinationLiquidityLayerRouter;
MockCircleMessageTransmitter messageTransmitter;
MockCircleTokenMessenger tokenMessenger;
CircleBridgeAdapter originBridgeAdapter;
CircleBridgeAdapter destinationBridgeAdapter;
string bridge = "FooBridge";
uint32 originDomain = 123;
uint32 destinationDomain = 321;
TestTokenRecipient recipient;
MockToken token;
bytes messageBody = hex"beefdead";
uint256 amount = 420000;
event LiquidityLayerAdapterSet(string indexed bridge, address adapter);
function setUp() public {
token = new MockToken();
tokenMessenger = new MockCircleTokenMessenger(token);
messageTransmitter = new MockCircleMessageTransmitter(token);
originBridgeAdapter = new CircleBridgeAdapter();
destinationBridgeAdapter = new CircleBridgeAdapter();
recipient = new TestTokenRecipient();
originLiquidityLayerRouter = new LiquidityLayerRouter();
destinationLiquidityLayerRouter = new LiquidityLayerRouter();
testEnvironment = new MockHyperlaneEnvironment(
originDomain,
destinationDomain
);
address owner = address(this);
originLiquidityLayerRouter.initialize(
address(testEnvironment.mailboxes(originDomain)),
address(testEnvironment.igps(originDomain)),
address(testEnvironment.isms(originDomain)),
owner
);
destinationLiquidityLayerRouter.initialize(
address(testEnvironment.mailboxes(destinationDomain)),
address(testEnvironment.igps(destinationDomain)),
address(testEnvironment.isms(destinationDomain)),
owner
);
originLiquidityLayerRouter.enrollRemoteRouter(
destinationDomain,
TypeCasts.addressToBytes32(address(destinationLiquidityLayerRouter))
);
destinationLiquidityLayerRouter.enrollRemoteRouter(
originDomain,
TypeCasts.addressToBytes32(address(originLiquidityLayerRouter))
);
originBridgeAdapter.initialize(
owner,
address(tokenMessenger),
address(messageTransmitter),
address(originLiquidityLayerRouter)
);
destinationBridgeAdapter.initialize(
owner,
address(tokenMessenger),
address(messageTransmitter),
address(destinationLiquidityLayerRouter)
);
originBridgeAdapter.addToken(address(token), "USDC");
destinationBridgeAdapter.addToken(address(token), "USDC");
originBridgeAdapter.enrollRemoteRouter(
destinationDomain,
TypeCasts.addressToBytes32(address(destinationBridgeAdapter))
);
destinationBridgeAdapter.enrollRemoteRouter(
originDomain,
TypeCasts.addressToBytes32(address(originBridgeAdapter))
);
originLiquidityLayerRouter.setLiquidityLayerAdapter(
bridge,
address(originBridgeAdapter)
);
destinationLiquidityLayerRouter.setLiquidityLayerAdapter(
bridge,
address(destinationBridgeAdapter)
);
token.mint(address(this), amount);
}
function testSetLiquidityLayerAdapter() public {
// Expect the LiquidityLayerAdapterSet event.
// Expect topic0 & data to match
vm.expectEmit(true, false, false, true);
emit LiquidityLayerAdapterSet(bridge, address(originBridgeAdapter));
// Set the token bridge adapter
originLiquidityLayerRouter.setLiquidityLayerAdapter(
bridge,
address(originBridgeAdapter)
);
// Expect the bridge adapter to have been set
assertEq(
originLiquidityLayerRouter.liquidityLayerAdapters(bridge),
address(originBridgeAdapter)
);
}
// ==== dispatchWithTokens ====
function testDispatchWithTokensRevertsWithUnkownBridgeAdapter() public {
vm.expectRevert("No adapter found for bridge");
originLiquidityLayerRouter.dispatchWithTokens(
destinationDomain,
TypeCasts.addressToBytes32(address(recipient)),
address(token),
amount,
"BazBridge", // some unknown bridge name,
messageBody
);
}
function testDispatchWithTokensRevertsWithFailedTransferIn() public {
vm.expectRevert("ERC20: insufficient allowance");
originLiquidityLayerRouter.dispatchWithTokens(
destinationDomain,
TypeCasts.addressToBytes32(address(recipient)),
address(token),
amount,
bridge,
messageBody
);
}
function testDispatchWithTokenTransfersMovesTokens() public {
token.approve(address(originLiquidityLayerRouter), amount);
originLiquidityLayerRouter.dispatchWithTokens(
destinationDomain,
TypeCasts.addressToBytes32(address(recipient)),
address(token),
amount,
bridge,
messageBody
);
}
function testDispatchWithTokensCallsAdapter() public {
vm.expectCall(
address(originBridgeAdapter),
abi.encodeWithSelector(
originBridgeAdapter.sendTokens.selector,
destinationDomain,
TypeCasts.addressToBytes32(address(recipient)),
address(token),
amount
)
);
token.approve(address(originLiquidityLayerRouter), amount);
originLiquidityLayerRouter.dispatchWithTokens(
destinationDomain,
TypeCasts.addressToBytes32(address(recipient)),
address(token),
amount,
bridge,
messageBody
);
}
function testProcessingRevertsIfBridgeAdapterReverts() public {
token.approve(address(originLiquidityLayerRouter), amount);
originLiquidityLayerRouter.dispatchWithTokens(
destinationDomain,
TypeCasts.addressToBytes32(address(recipient)),
address(token),
amount,
bridge,
messageBody
);
vm.expectRevert("Circle message not processed yet");
testEnvironment.processNextPendingMessage();
}
function testDispatchWithTokensTransfersOnDestination() public {
token.approve(address(originLiquidityLayerRouter), amount);
originLiquidityLayerRouter.dispatchWithTokens(
destinationDomain,
TypeCasts.addressToBytes32(address(recipient)),
address(token),
amount,
bridge,
messageBody
);
bytes32 nonceId = messageTransmitter.hashSourceAndNonce(
destinationBridgeAdapter.hyperlaneDomainToCircleDomain(
originDomain
),
tokenMessenger.nextNonce()
);
messageTransmitter.process(
nonceId,
address(destinationBridgeAdapter),
amount
);
testEnvironment.processNextPendingMessage();
assertEq(recipient.lastData(), messageBody);
assertEq(token.balanceOf(address(recipient)), amount);
}
function testCannotSendToRecipientWithoutHandle() public {
token.approve(address(originLiquidityLayerRouter), amount);
originLiquidityLayerRouter.dispatchWithTokens(
destinationDomain,
TypeCasts.addressToBytes32(address(this)),
address(token),
amount,
bridge,
messageBody
);
bytes32 nonceId = messageTransmitter.hashSourceAndNonce(
destinationBridgeAdapter.hyperlaneDomainToCircleDomain(
originDomain
),
tokenMessenger.nextNonce()
);
messageTransmitter.process(
nonceId,
address(destinationBridgeAdapter),
amount
);
vm.expectRevert();
testEnvironment.processNextPendingMessage();
}
function testSendToRecipientWithoutHandleWhenSpecifyingNoMessage() public {
TestRecipient noHandleRecipient = new TestRecipient();
token.approve(address(originLiquidityLayerRouter), amount);
originLiquidityLayerRouter.dispatchWithTokens(
destinationDomain,
TypeCasts.addressToBytes32(address(noHandleRecipient)),
address(token),
amount,
bridge,
""
);
bytes32 nonceId = messageTransmitter.hashSourceAndNonce(
destinationBridgeAdapter.hyperlaneDomainToCircleDomain(
originDomain
),
tokenMessenger.nextNonce()
);
messageTransmitter.process(
nonceId,
address(destinationBridgeAdapter),
amount
);
testEnvironment.processNextPendingMessage();
assertEq(token.balanceOf(address(noHandleRecipient)), amount);
}
}