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

chore(contracts-rfq): integration tests for legacy router [SLT-360] #3304

Merged
merged 13 commits into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from 8 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
6 changes: 6 additions & 0 deletions .github/workflows/solidity.yml
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,12 @@ jobs:
env:
FOUNDRY_FUZZ_RUNS: 10

# Some of the packages may want to exclude certain files from the coverage report (legacy code, scripts, tests)
- name: Apply filters to coverage report
if: ${{ matrix.package != 'solidity-devops' }}
working-directory: './packages/${{matrix.package}}'
run: npm run coverage:filter --if-present

- name: Send Coverage (Codecov)
if: ${{ matrix.package != 'solidity-devops' }}
uses: Wandalen/[email protected]
Expand Down
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@
"nohoist": [
"**/typechain",
"**/@typechain/*",
"**/@openzeppelin/contracts-upgradeable",
"**/@openzeppelin/contracts",
"**/@openzeppelin/*",
"**/@synapsecns/solidity-devops",
"**/ds-test",
"**/forge-std"
Expand Down
1 change: 1 addition & 0 deletions packages/contracts-rfq/.solhintignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
contracts/FastBridge.sol
contracts/interfaces/IFastBridge.sol
contracts/legacy/**/*.sol
script/FastBridge.s.sol
test/FastBridge.t.sol
test/FastBridgeMock.sol
141 changes: 141 additions & 0 deletions packages/contracts-rfq/contracts/legacy/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;

Check warning

Code scanning / Slither

Different pragma directives are used Warning

4 different versions of Solidity are used:
- Version constraint 0.8.17 is used by:
-0.8.17
-0.8.17
-0.8.17
-0.8.17
-0.8.17
-0.8.17
-0.8.17
-0.8.17
-0.8.17
- Version constraint ^0.8.0 is used by:
-^0.8.0
-^0.8.0
-^0.8.0
-^0.8.0
-^0.8.0
-^0.8.0
-^0.8.0
- Version constraint >=0.8.13 is used by:
->=0.8.13
- Version constraint ^0.8.1 is used by:
-^0.8.1

Check warning

Code scanning / Slither

Incorrect versions of Solidity Warning

Version constraint 0.8.17 contains known severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)
- VerbatimInvalidDeduplication
- FullInlinerNonExpressionSplitArgumentEvaluationOrder
- MissingSideEffectsOnSelectorAccess.
It is used by:
- 0.8.17
- 0.8.17
- 0.8.17
- 0.8.17
- 0.8.17
- 0.8.17
- 0.8.17
- 0.8.17
- 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 {

Check notice

Code scanning / Slither

Missing zero address validation Low

fastBridge = fastBridge_;
emit FastBridgeSet(fastBridge_);
}

/// @inheritdoc IFastBridgeRouter
function setSwapQuoter(address swapQuoter_) external onlyOwner {

Check notice

Code scanning / Slither

Missing zero address validation Low

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);
}

Check notice

Code scanning / Slither

Block timestamp Low


/// @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;
}
}

Check warning

Code scanning / Slither

Assembly usage Warning


/// @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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

Check warning

Code scanning / Slither

Incorrect versions of Solidity Warning

Version constraint ^0.8.0 contains known severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)
- FullInlinerNonExpressionSplitArgumentEvaluationOrder
- MissingSideEffectsOnSelectorAccess
- AbiReencodingHeadOverflowWithStaticArrayCleanup
- DirtyBytesArrayToStorage
- DataLocationChangeInInternalOverride
- NestedCalldataArrayAbiReencodingSizeValidation
- SignedImmutables
- ABIDecodeTwoDimensionalArrayMemory
- KeccakCaching.
It is used by:
- ^0.8.0
- ^0.8.0
- ^0.8.0
- ^0.8.0
- ^0.8.0
- ^0.8.0
- ^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);
}
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);

Check warning

Code scanning / Slither

Conformance to Solidity naming conventions Warning

Function IFastBridgeRouter.GAS_REBATE_FLAG() is not in mixedCase

/// @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);
}
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);
}
Loading
Loading