Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
133 changes: 133 additions & 0 deletions contracts/rfq/FastBridgeRouterV2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

import {DefaultRouter} 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
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;
}
}
10 changes: 10 additions & 0 deletions test/mocks/MockSenderContract.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {Address} from "@openzeppelin/contracts-4.5.0/utils/Address.sol";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using a more recent version of OpenZeppelin contracts.

The import statement uses version 4.5.0 of the OpenZeppelin contracts. Consider using a more recent version if available to benefit from the latest features and security fixes.

import {Address} from "@openzeppelin/contracts-4.5.0/utils/Address.sol";

Committable suggestion was skipped due to low confidence.


contract MockSenderContract {
function doCall(address target, bytes memory data) external payable {
Address.functionCallWithValue(target, data, msg.value);
}
}
17 changes: 11 additions & 6 deletions test/rfq/FBRTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ import {MockDefaultPool} from "../mocks/MockDefaultPool.sol";
import {Test} from "forge-std/Test.sol";

abstract contract FBRTest is Test {
uint256 constant RFQ_DEADLINE = 12 hours;
address constant TOKEN_OUT = address(1337);
uint256 constant FIXED_FEE = 0.01 ether;
uint32 constant DST_CHAIN_ID = 420;
uint8 public constant REBATE_FLAG = 42;
uint256 public constant RFQ_DEADLINE = 12 hours;
address public constant TOKEN_OUT = address(1337);
uint256 public constant FIXED_FEE = 0.01 ether;
uint32 public constant DST_CHAIN_ID = 420;

FastBridgeRouter public router;
MockFastBridge public fastBridge;
Expand All @@ -29,11 +30,15 @@ abstract contract FBRTest is Test {
recipient = makeAddr("Recipient");
user = makeAddr("User");
fastBridge = new MockFastBridge();
router = new FastBridgeRouter(owner);
router = FastBridgeRouter(deployRouter());
vm.prank(owner);
router.setFastBridge(address(fastBridge));
}

function deployRouter() public virtual returns (address payable) {
return payable(new FastBridgeRouter(owner));
}

function test_constructor() public {
assertEq(address(router.fastBridge()), address(fastBridge));
assertEq(router.owner(), owner);
Expand Down Expand Up @@ -75,7 +80,7 @@ abstract contract FBRTest is Test {
tokenOut: TOKEN_OUT,
minAmountOut: amount - FIXED_FEE,
deadline: block.timestamp + RFQ_DEADLINE,
rawParams: abi.encodePacked(uint8(42))
rawParams: abi.encodePacked(REBATE_FLAG)
});
}

Expand Down
Loading