Skip to content

Commit 193f470

Browse files
authored
Feat: fast bridge router v2 (#330)
* feat: copy FastBridgeRouter, scaffold new error * test: establish template for FBR v2 tests * test: add token tests for FBR V2 * feat: decode originSender in FastBridgeRouterV2 * refactor: deduplicate origin query in tests * refactor: deduplicate V2 tests * test: add native ETH tests for FBR v2 * refactor: EOA fallback * chore: better docs for `_getOriginSender` * feat: FBRv2 deploy, configure scripts * build: update network-specific options * deploy: FBR v2 * test: add cases for deadline/minAmountOut * fix: check deadline/amountOut for no-swap cases * deploy: updated FB RouterV2 * build: add helper scripts
1 parent 182e046 commit 193f470

27 files changed

+5105
-260
lines changed
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.17;
3+
4+
import {DefaultRouter, DeadlineExceeded, InsufficientOutputAmount} from "../router/DefaultRouter.sol";
5+
import {UniversalTokenLib} from "../router/libs/UniversalToken.sol";
6+
import {ActionLib, LimitedToken} from "../router/libs/Structs.sol";
7+
import {IFastBridge} from "./interfaces/IFastBridge.sol";
8+
import {IFastBridgeRouter, SwapQuery} from "./interfaces/IFastBridgeRouter.sol";
9+
import {ISwapQuoter} from "./interfaces/ISwapQuoter.sol";
10+
11+
import {Ownable} from "@openzeppelin/contracts-4.5.0/access/Ownable.sol";
12+
13+
contract FastBridgeRouterV2 is DefaultRouter, Ownable, IFastBridgeRouter {
14+
using UniversalTokenLib for address;
15+
16+
error FastBridgeRouterV2__OriginSenderNotSpecified();
17+
18+
/// @notice Emitted when the swap quoter is set.
19+
/// @param newSwapQuoter The new swap quoter.
20+
event SwapQuoterSet(address newSwapQuoter);
21+
22+
/// @notice Emitted when the new FastBridge contract is set.
23+
/// @param newFastBridge The new FastBridge contract.
24+
event FastBridgeSet(address newFastBridge);
25+
26+
/// @inheritdoc IFastBridgeRouter
27+
bytes1 public constant GAS_REBATE_FLAG = 0x2A;
28+
29+
/// @inheritdoc IFastBridgeRouter
30+
address public fastBridge;
31+
/// @inheritdoc IFastBridgeRouter
32+
address public swapQuoter;
33+
34+
constructor(address owner_) {
35+
transferOwnership(owner_);
36+
}
37+
38+
/// @inheritdoc IFastBridgeRouter
39+
function setFastBridge(address fastBridge_) external onlyOwner {
40+
fastBridge = fastBridge_;
41+
emit FastBridgeSet(fastBridge_);
42+
}
43+
44+
/// @inheritdoc IFastBridgeRouter
45+
function setSwapQuoter(address swapQuoter_) external onlyOwner {
46+
swapQuoter = swapQuoter_;
47+
emit SwapQuoterSet(swapQuoter_);
48+
}
49+
50+
/// @inheritdoc IFastBridgeRouter
51+
function bridge(
52+
address recipient,
53+
uint256 chainId,
54+
address token,
55+
uint256 amount,
56+
SwapQuery memory originQuery,
57+
SwapQuery memory destQuery
58+
) external payable {
59+
address originSender = _getOriginSender(destQuery.rawParams);
60+
if (originSender == address(0)) {
61+
revert FastBridgeRouterV2__OriginSenderNotSpecified();
62+
}
63+
if (originQuery.hasAdapter()) {
64+
// Perform a swap using the swap adapter, set this contract as recipient
65+
(token, amount) = _doSwap(address(this), token, amount, originQuery);
66+
} else {
67+
// Otherwise, pull the token from the user to this contract
68+
// We still need to perform the deadline and amountOut checks
69+
// solhint-disable-next-line not-rely-on-time
70+
if (block.timestamp > originQuery.deadline) {
71+
revert DeadlineExceeded();
72+
}
73+
if (amount < originQuery.minAmountOut) {
74+
revert InsufficientOutputAmount();
75+
}
76+
amount = _pullToken(address(this), token, amount);
77+
}
78+
IFastBridge.BridgeParams memory params = IFastBridge.BridgeParams({
79+
dstChainId: uint32(chainId),
80+
sender: originSender,
81+
to: recipient,
82+
originToken: token,
83+
destToken: destQuery.tokenOut,
84+
originAmount: amount,
85+
destAmount: destQuery.minAmountOut,
86+
sendChainGas: _chainGasRequested(destQuery.rawParams),
87+
deadline: destQuery.deadline
88+
});
89+
token.universalApproveInfinity(fastBridge, amount);
90+
uint256 msgValue = token == UniversalTokenLib.ETH_ADDRESS ? amount : 0;
91+
IFastBridge(fastBridge).bridge{value: msgValue}(params);
92+
}
93+
94+
/// @inheritdoc IFastBridgeRouter
95+
function getOriginAmountOut(
96+
address tokenIn,
97+
address[] memory rfqTokens,
98+
uint256 amountIn
99+
) external view returns (SwapQuery[] memory originQueries) {
100+
uint256 len = rfqTokens.length;
101+
originQueries = new SwapQuery[](len);
102+
for (uint256 i = 0; i < len; ++i) {
103+
originQueries[i] = ISwapQuoter(swapQuoter).getAmountOut(
104+
LimitedToken({actionMask: ActionLib.allActions(), token: tokenIn}),
105+
rfqTokens[i],
106+
amountIn
107+
);
108+
// Adjust the Adapter address if it exists
109+
if (originQueries[i].hasAdapter()) {
110+
originQueries[i].routerAdapter = address(this);
111+
}
112+
}
113+
}
114+
115+
/// @dev Retrieves the origin sender from the raw params.
116+
/// Note: falls back to msg.sender if origin sender is not specified in the raw params, but
117+
/// msg.sender is an EOA.
118+
function _getOriginSender(bytes memory rawParams) internal view returns (address originSender) {
119+
// Origin sender (if present) is encoded as 20 bytes following the rebate flag
120+
if (rawParams.length >= 21) {
121+
// The easiest way to read from memory is to use assembly
122+
// solhint-disable-next-line no-inline-assembly
123+
assembly {
124+
// Skip the rawParams.length (32 bytes) and the rebate flag (1 byte)
125+
originSender := mload(add(rawParams, 33))
126+
// The address is in the highest 160 bits. Shift right by 96 to get it in the lowest 160 bits
127+
originSender := shr(96, originSender)
128+
}
129+
}
130+
if (originSender == address(0) && msg.sender.code.length == 0) {
131+
// Fall back to msg.sender if it is an EOA. This maintains backward compatibility
132+
// for cases where we can safely assume that the origin sender is the same as msg.sender.
133+
originSender = msg.sender;
134+
}
135+
}
136+
137+
/// @dev Checks if the explicit instruction to send gas to the destination chain was provided.
138+
function _chainGasRequested(bytes memory rawParams) internal pure returns (bool) {
139+
return rawParams.length > 0 && rawParams[0] == GAS_REBATE_FLAG;
140+
}
141+
}

0 commit comments

Comments
 (0)