Skip to content

Commit

Permalink
Feat: fast bridge router v2 (#330)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
ChiTimesChi authored Aug 21, 2024
1 parent 182e046 commit 193f470
Show file tree
Hide file tree
Showing 27 changed files with 5,105 additions and 260 deletions.
141 changes: 141 additions & 0 deletions contracts/rfq/FastBridgeRouterV2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

import {DefaultRouter, DeadlineExceeded, InsufficientOutputAmount} from "../router/DefaultRouter.sol";
import {UniversalTokenLib} from "../router/libs/UniversalToken.sol";
import {ActionLib, LimitedToken} from "../router/libs/Structs.sol";
import {IFastBridge} from "./interfaces/IFastBridge.sol";
import {IFastBridgeRouter, SwapQuery} from "./interfaces/IFastBridgeRouter.sol";
import {ISwapQuoter} from "./interfaces/ISwapQuoter.sol";

import {Ownable} from "@openzeppelin/contracts-4.5.0/access/Ownable.sol";

contract FastBridgeRouterV2 is DefaultRouter, Ownable, IFastBridgeRouter {
using UniversalTokenLib for address;

error FastBridgeRouterV2__OriginSenderNotSpecified();

/// @notice Emitted when the swap quoter is set.
/// @param newSwapQuoter The new swap quoter.
event SwapQuoterSet(address newSwapQuoter);

/// @notice Emitted when the new FastBridge contract is set.
/// @param newFastBridge The new FastBridge contract.
event FastBridgeSet(address newFastBridge);

/// @inheritdoc IFastBridgeRouter
bytes1 public constant GAS_REBATE_FLAG = 0x2A;

/// @inheritdoc IFastBridgeRouter
address public fastBridge;
/// @inheritdoc IFastBridgeRouter
address public swapQuoter;

constructor(address owner_) {
transferOwnership(owner_);
}

/// @inheritdoc IFastBridgeRouter
function setFastBridge(address fastBridge_) external onlyOwner {
fastBridge = fastBridge_;
emit FastBridgeSet(fastBridge_);
}

/// @inheritdoc IFastBridgeRouter
function setSwapQuoter(address swapQuoter_) external onlyOwner {
swapQuoter = swapQuoter_;
emit SwapQuoterSet(swapQuoter_);
}

/// @inheritdoc IFastBridgeRouter
function bridge(
address recipient,
uint256 chainId,
address token,
uint256 amount,
SwapQuery memory originQuery,
SwapQuery memory destQuery
) external payable {
address originSender = _getOriginSender(destQuery.rawParams);
if (originSender == address(0)) {
revert FastBridgeRouterV2__OriginSenderNotSpecified();
}
if (originQuery.hasAdapter()) {
// Perform a swap using the swap adapter, set this contract as recipient
(token, amount) = _doSwap(address(this), token, amount, originQuery);
} else {
// Otherwise, pull the token from the user to this contract
// We still need to perform the deadline and amountOut checks
// solhint-disable-next-line not-rely-on-time
if (block.timestamp > originQuery.deadline) {
revert DeadlineExceeded();
}
if (amount < originQuery.minAmountOut) {
revert InsufficientOutputAmount();
}
amount = _pullToken(address(this), token, amount);
}
IFastBridge.BridgeParams memory params = IFastBridge.BridgeParams({
dstChainId: uint32(chainId),
sender: originSender,
to: recipient,
originToken: token,
destToken: destQuery.tokenOut,
originAmount: amount,
destAmount: destQuery.minAmountOut,
sendChainGas: _chainGasRequested(destQuery.rawParams),
deadline: destQuery.deadline
});
token.universalApproveInfinity(fastBridge, amount);
uint256 msgValue = token == UniversalTokenLib.ETH_ADDRESS ? amount : 0;
IFastBridge(fastBridge).bridge{value: msgValue}(params);
}

/// @inheritdoc IFastBridgeRouter
function getOriginAmountOut(
address tokenIn,
address[] memory rfqTokens,
uint256 amountIn
) external view returns (SwapQuery[] memory originQueries) {
uint256 len = rfqTokens.length;
originQueries = new SwapQuery[](len);
for (uint256 i = 0; i < len; ++i) {
originQueries[i] = ISwapQuoter(swapQuoter).getAmountOut(
LimitedToken({actionMask: ActionLib.allActions(), token: tokenIn}),
rfqTokens[i],
amountIn
);
// Adjust the Adapter address if it exists
if (originQueries[i].hasAdapter()) {
originQueries[i].routerAdapter = address(this);
}
}
}

/// @dev Retrieves the origin sender from the raw params.
/// Note: falls back to msg.sender if origin sender is not specified in the raw params, but
/// msg.sender is an EOA.
function _getOriginSender(bytes memory rawParams) internal view returns (address originSender) {
// Origin sender (if present) is encoded as 20 bytes following the rebate flag
if (rawParams.length >= 21) {
// The easiest way to read from memory is to use assembly
// solhint-disable-next-line no-inline-assembly
assembly {
// Skip the rawParams.length (32 bytes) and the rebate flag (1 byte)
originSender := mload(add(rawParams, 33))
// The address is in the highest 160 bits. Shift right by 96 to get it in the lowest 160 bits
originSender := shr(96, originSender)
}
}
if (originSender == address(0) && msg.sender.code.length == 0) {
// Fall back to msg.sender if it is an EOA. This maintains backward compatibility
// for cases where we can safely assume that the origin sender is the same as msg.sender.
originSender = msg.sender;
}
}

/// @dev Checks if the explicit instruction to send gas to the destination chain was provided.
function _chainGasRequested(bytes memory rawParams) internal pure returns (bool) {
return rawParams.length > 0 && rawParams[0] == GAS_REBATE_FLAG;
}
}
Loading

0 comments on commit 193f470

Please sign in to comment.