-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 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
1 parent
182e046
commit 193f470
Showing
27 changed files
with
5,105 additions
and
260 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
Oops, something went wrong.