Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: fast bridge router v2 #330

Merged
merged 16 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading