-
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.
* Add interface with TODO notes * Scaffold FastBridgeRouter * Scaffold tests for FBRouter * Setup tokens, pool * Setup user * Tests: bridge w/o origin swap * Tests: bridge with origin swap * Test for `getOriginAmountOut` * Reduce amount of copypasta in the test * Move common test stuff into abstract parent * Tests: starting from ETH * Tests: starting from WETH * Tests: starting from token paired with WETH * Tests: getOriginAmountOut for ETH/WETH and paired token * Add tests for the revert scenarios * Check both msg.value and data for expected FastBridge call * First draft for `bridge()` * Proper implementation of gas rebate flag * Add configurable SwapQuoter * Update tests to use SwapQuoter V1 and V2 for setup * Implement `getOriginAmountOut()` * Add docs, bridgeTokens -> rfqTokens * Make FastBridge address configurable * Script for FastBridgeRouter deployment * Add FastBridge deployment artifacts * Deploy FastBridgeRouter * Update FastBridge deployment artifacts * Deploy on Mainnet * Update fast bridge address
- Loading branch information
1 parent
895270c
commit c6ebc2e
Showing
23 changed files
with
4,655 additions
and
1 deletion.
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,105 @@ | ||
// 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 FastBridgeRouter is DefaultRouter, Ownable, IFastBridgeRouter { | ||
using UniversalTokenLib for address; | ||
|
||
/// @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 { | ||
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: msg.sender, | ||
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 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; | ||
} | ||
} |
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,93 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
// TODO: This should be pulled from the sanguine repo (requires publish and relaxing the ^0.8.20 pragma) | ||
interface IFastBridge { | ||
struct BridgeTransaction { | ||
uint32 originChainId; | ||
uint32 destChainId; | ||
address originSender; // user (origin) | ||
address destRecipient; // user (dest) | ||
address originToken; | ||
address destToken; | ||
uint256 originAmount; // amount in on origin bridge less originFeeAmount | ||
uint256 destAmount; | ||
uint256 originFeeAmount; | ||
bool sendChainGas; | ||
uint256 deadline; | ||
uint256 nonce; | ||
} | ||
|
||
struct BridgeProof { | ||
uint96 timestamp; | ||
address relayer; | ||
} | ||
|
||
// ============ Events ============ | ||
|
||
event BridgeRequested(bytes32 transactionId, address sender, bytes request); | ||
event BridgeRelayed( | ||
bytes32 transactionId, | ||
address relayer, | ||
address to, | ||
address token, | ||
uint256 amount, | ||
uint256 chainGasAmount | ||
); | ||
event BridgeProofProvided(bytes32 transactionId, address relayer, bytes32 transactionHash); | ||
event BridgeProofDisputed(bytes32 transactionId, address relayer); | ||
event BridgeDepositClaimed(bytes32 transactionId, address relayer, address to, address token, uint256 amount); | ||
event BridgeDepositRefunded(bytes32 transactionId, address to, address token, uint256 amount); | ||
|
||
// ============ Methods ============ | ||
|
||
struct BridgeParams { | ||
uint32 dstChainId; | ||
address sender; | ||
address to; | ||
address originToken; | ||
address destToken; | ||
uint256 originAmount; // should include protocol fee (if any) | ||
uint256 destAmount; // should include relayer fee | ||
bool sendChainGas; | ||
uint256 deadline; | ||
} | ||
|
||
/// @notice Initiates bridge on origin chain to be relayed by off-chain relayer | ||
/// @param params The parameters required to bridge | ||
function bridge(BridgeParams memory params) external payable; | ||
|
||
/// @notice Relays destination side of bridge transaction by off-chain relayer | ||
/// @param request The encoded bridge transaction to relay on destination chain | ||
function relay(bytes memory request) external payable; | ||
|
||
/// @notice Provides proof on origin side that relayer provided funds on destination side of bridge transaction | ||
/// @param request The encoded bridge transaction to prove on origin chain | ||
/// @param destTxHash The destination tx hash proving bridge transaction was relayed | ||
function prove(bytes memory request, bytes32 destTxHash) external; | ||
|
||
/// @notice Completes bridge transaction on origin chain by claiming originally deposited capital | ||
/// @param request The encoded bridge transaction to claim on origin chain | ||
/// @param to The recipient address of the funds | ||
function claim(bytes memory request, address to) external; | ||
|
||
/// @notice Disputes an outstanding proof in case relayer provided dest chain tx is invalid | ||
/// @param transactionId The transaction id associated with the encoded bridge transaction to dispute | ||
function dispute(bytes32 transactionId) external; | ||
|
||
/// @notice Refunds an outstanding bridge transaction in case optimistic bridging failed | ||
/// @param request The encoded bridge transaction to refund | ||
/// @param to The recipient address of the funds | ||
function refund(bytes memory request, address to) external; | ||
|
||
// ============ Views ============ | ||
|
||
/// @notice Decodes bridge request into a bridge transaction | ||
/// @param request The bridge request to decode | ||
function getBridgeTransaction(bytes memory request) external pure returns (BridgeTransaction memory); | ||
|
||
/// @notice Checks if the dispute period has passed so bridge deposit can be claimed | ||
/// @param transactionId The transaction id associated with the encoded bridge transaction to check | ||
/// @param relayer The address of the relayer attempting to claim | ||
function canClaim(bytes32 transactionId, address relayer) external view returns (bool); | ||
} |
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,78 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
import {SwapQuery} from "../../router/libs/Structs.sol"; | ||
|
||
interface IFastBridgeRouter { | ||
/// @notice Sets the address of the FastBridge contract | ||
/// @dev This function is only callable by the owner | ||
/// @param fastBridge_ The address of the FastBridge contract | ||
function setFastBridge(address fastBridge_) external; | ||
|
||
/// @notice Sets the address of the SwapQuoter contract | ||
/// @dev This function is only callable by the owner | ||
/// @param swapQuoter_ The address of the SwapQuoter contract | ||
function setSwapQuoter(address swapQuoter_) external; | ||
|
||
/// @notice Initiate an RFQ transaction with an optional swap on origin chain, | ||
/// and an optional gas rebate on destination chain. | ||
/// @dev Note that method is payable. | ||
/// If token is ETH_ADDRESS, this method should be invoked with `msg.value = amountIn`. | ||
/// If token is ERC20, the tokens will be pulled from msg.sender (use `msg.value = 0`). | ||
/// Make sure to approve this contract for spending `token` beforehand. | ||
/// | ||
/// `originQuery` is supposed to be fetched using FastBridgeRouter.getOriginAmountOut(). | ||
/// Alternatively one could use an external adapter for more complex swaps on the origin chain. | ||
/// `destQuery.rawParams` signals whether the user wants to receive a gas rebate on the destination chain: | ||
/// - If the first byte of `destQuery.rawParams` is GAS_REBATE_FLAG, the user wants to receive a gas rebate. | ||
/// - Otherwise, the user does not want to receive a gas rebate. | ||
/// | ||
/// Cross-chain RFQ swap will be performed between tokens: `originQuery.tokenOut` and `destQuery.tokenOut`. | ||
/// Note: both tokens could be ETH_ADDRESS or ERC20. | ||
/// Full proceeds of the origin swap are considered the bid for the cross-chain swap. | ||
/// `destQuery.minAmountOut` is considered the ask for the cross-chain swap. | ||
/// Note: applying slippage to `destQuery.minAmountOut` will result in a worse price for the user, | ||
/// the full Relayer quote should be used instead. | ||
/// @param recipient Address to receive tokens on destination chain | ||
/// @param chainId Destination chain id | ||
/// @param token Initial token to be pulled from the user | ||
/// @param amount Amount of the initial tokens to be pulled from the user | ||
/// @param originQuery Origin swap query (see above) | ||
/// @param destQuery Destination swap query (see above) | ||
function bridge( | ||
address recipient, | ||
uint256 chainId, | ||
address token, | ||
uint256 amount, | ||
SwapQuery memory originQuery, | ||
SwapQuery memory destQuery | ||
) external payable; | ||
|
||
// ═══════════════════════════════════════════════════ VIEWS ═══════════════════════════════════════════════════════ | ||
|
||
/// @notice Finds the best path between `tokenIn` and every RFQ token from the given list, | ||
/// treating the swap as "origin swap", without putting any restrictions on the swap. | ||
/// @dev Check (query.minAmountOut != 0): this is true only if the swap is possible. | ||
/// The returned queries with minAmountOut != 0 could be used as `originQuery` with FastBridgeRouter. | ||
/// Note: it is possible to form a SwapQuery off-chain using alternative SwapAdapter for the origin swap. | ||
/// @param tokenIn Initial token that user wants to bridge/swap | ||
/// @param rfqTokens List of RFQ tokens | ||
/// @param amountIn Amount of tokens user wants to bridge/swap | ||
/// @return originQueries List of structs that could be used as `originQuery` in FastBridgeRouter. | ||
/// minAmountOut and deadline fields will need to be adjusted based on the user settings. | ||
function getOriginAmountOut( | ||
address tokenIn, | ||
address[] memory rfqTokens, | ||
uint256 amountIn | ||
) external view returns (SwapQuery[] memory originQueries); | ||
|
||
/// @notice Magic value that indicates that the user wants to receive gas rebate on the destination chain. | ||
/// This is the answer to the ultimate question of life, the universe, and everything. | ||
function GAS_REBATE_FLAG() external view returns (bytes1); | ||
|
||
/// @notice Address of the FastBridge contract, used to initiate cross-chain RFQ swaps. | ||
function fastBridge() external view returns (address); | ||
|
||
/// @notice Address of the SwapQuoter contract, used to fetch quotes for the origin swap. | ||
function swapQuoter() external view returns (address); | ||
} |
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,12 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
import {LimitedToken, SwapQuery} from "../../router/libs/Structs.sol"; | ||
|
||
interface ISwapQuoter { | ||
function getAmountOut( | ||
LimitedToken memory tokenIn, | ||
address tokenOut, | ||
uint256 amountIn | ||
) external view returns (SwapQuery memory query); | ||
} |
Oops, something went wrong.