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.
293 lines
9.9 KiB
293 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);
|
|
}
|
|
}
|
|
|