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(rfq-relayer): support FastBridgeV2 with arbitrary calls [SLT-320] #3258

Open
wants to merge 45 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
36a42bd
feat: add `callValue` to bridge params
ChiTimesChi Oct 2, 2024
919a0a9
feat: add `callValue` to bridge tx V2
ChiTimesChi Oct 2, 2024
867d5d9
test: add cases for SRC calls with `callValue`
ChiTimesChi Oct 3, 2024
c93c403
refactor: simplify tests further
ChiTimesChi Oct 3, 2024
9575246
test: add cases for DST relays with `callValue`
ChiTimesChi Oct 3, 2024
7872f2a
feat: support `callValue` in `bridge()`
ChiTimesChi Oct 3, 2024
ad7dd4c
feat: support `callValue` in `relay()`
ChiTimesChi Oct 3, 2024
5ca5ad1
test: update revert message for failed ETH transfers
ChiTimesChi Oct 3, 2024
e8355c3
refactor: always forward full msg.value for the hook call
ChiTimesChi Oct 3, 2024
eb2bbb8
refactor: use `_pullToken` only in `bridge()`
ChiTimesChi Oct 3, 2024
c50042a
refactor: isolate validation of relay params
ChiTimesChi Oct 3, 2024
9fb4461
refactor: isolate validation of the bridge params
ChiTimesChi Oct 3, 2024
156e333
docs: could -> can
ChiTimesChi Oct 8, 2024
2b77b4a
test: enable the backwards compatibility encoding test
ChiTimesChi Oct 8, 2024
271f59d
fix: getBridgeTransaction partial support for V2 structs
ChiTimesChi Oct 8, 2024
2317a58
test: add clarifications about expected reverts
ChiTimesChi Oct 8, 2024
c6a1fdc
Feat: initial fastbridgev2 bindings
dwasse Oct 8, 2024
e5e8646
Feat: abigen helpers
dwasse Oct 8, 2024
ab63286
Fix: generate
dwasse Oct 8, 2024
93d9b7d
Fix: bridge enum test
dwasse Oct 8, 2024
e34a08b
Feat: relayer integrates fastbridgev2
dwasse Oct 8, 2024
1056ef1
Feat: testutils uses v2
dwasse Oct 8, 2024
1301270
Fix: deployer uses v2
dwasse Oct 8, 2024
1081e0a
Fix: current e2e tests with v2 bridge
dwasse Oct 8, 2024
467d5c7
Feat: add recipient mock test contract
dwasse Oct 9, 2024
f456bb7
Feat: add TestUSDCtoUSDCWithCallData
dwasse Oct 9, 2024
de621a7
Rename: TestArbitraryCall
dwasse Oct 9, 2024
e3c1604
Feat: add TransactionV1 in QuoteRequest struct for access to SendChai…
dwasse Oct 9, 2024
bff4f72
Cleanup: bridge tx fetching
dwasse Oct 9, 2024
4ed813b
Cleanup: remove guard check for now
dwasse Oct 9, 2024
5b6ec12
Feat: add v2 of fastbridgemock
dwasse Oct 11, 2024
d3dbeb0
Fix: build
dwasse Oct 11, 2024
0bc22c5
Cleanup: lint
dwasse Oct 11, 2024
117ce59
Cleanup: remove unnecessary test
dwasse Oct 11, 2024
6dee3d1
Fix: mock fast bridge deployer
dwasse Oct 11, 2024
f57312b
Revert "Cleanup: remove unnecessary test"
dwasse Oct 11, 2024
4b8dc68
Fix: flatten all files
dwasse Oct 11, 2024
b24d31d
Revert "Fix: flatten all files"
dwasse Oct 11, 2024
91a6b8f
Feat: flatten mocks
dwasse Oct 11, 2024
b6a4609
Fix: tests
dwasse Oct 11, 2024
05ab3dd
Fix: test
dwasse Oct 11, 2024
94ee810
Fix: use nativeTokenDecimals instead of origin decimals for call value
dwasse Oct 15, 2024
337782c
Merge branch 'master' into feat/relayer-arb-call
dwasse Oct 15, 2024
8759318
Update abigen
dwasse Oct 15, 2024
f44c7ae
Fix: use native token decimals for CallValue
dwasse Oct 16, 2024
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
247 changes: 143 additions & 104 deletions packages/contracts-rfq/contracts/FastBridgeV2.sol

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ interface IFastBridge {

/// @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);
function getBridgeTransaction(bytes memory request) external view 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
Expand Down
7 changes: 5 additions & 2 deletions packages/contracts-rfq/contracts/interfaces/IFastBridgeV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,17 @@ interface IFastBridgeV2 is IFastBridge {
/// for backwards compatibility.
/// Note: quoteRelayer and quoteExclusivitySeconds are either both zero (indicating no exclusivity)
/// or both non-zero (indicating exclusivity for the given period).
/// Note: callValue > 0 can NOT be used with destToken = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE (ETH_ADDRESS)
/// @param quoteRelayer Relayer that provided the quote for the transaction
/// @param quoteExclusivitySeconds Period of time the quote relayer is guaranteed exclusivity after user's deposit
/// @param quoteId Unique quote identifier used for tracking the quote
/// @param callValue ETH value to send to the recipient (if any)
/// @param callParams Parameters for the arbitrary call to the destination recipient (if any)
struct BridgeParamsV2 {
address quoteRelayer;
int256 quoteExclusivitySeconds;
bytes quoteId;
uint256 callValue;
bytes callParams;
}

Expand All @@ -54,7 +57,7 @@ interface IFastBridgeV2 is IFastBridge {
uint256 originAmount; // amount in on origin bridge less originFeeAmount
uint256 destAmount;
uint256 originFeeAmount;
bool sendChainGas;
uint256 callValue; // ETH value to send to the recipient (if any) - replaces V1's sendChainGas flag
uint256 deadline; // user specified deadline for destination relay
uint256 nonce;
address exclusivityRelayer;
Expand All @@ -67,7 +70,7 @@ interface IFastBridgeV2 is IFastBridge {
/// @notice Initiates bridge on origin chain to be relayed by off-chain relayer, with the ability
/// to provide temporary exclusivity fill rights for the quote relayer.
/// @param params The parameters required to bridge
/// @param paramsV2 The parameters for exclusivity fill rights (optional, could be left empty)
/// @param paramsV2 The parameters for exclusivity fill rights (optional, can be left empty)
function bridge(BridgeParams memory params, BridgeParamsV2 memory paramsV2) external payable;

/// @notice Relays destination side of bridge transaction by off-chain relayer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ interface IFastBridgeV2Errors {
error ChainIncorrect();
error ExclusivityParamsIncorrect();
error MsgValueIncorrect();
error NativeTokenCallValueNotSupported();
error SenderIncorrect();
error StatusIncorrect();
error ZeroAddress();
Expand Down
2 changes: 1 addition & 1 deletion packages/contracts-rfq/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"lint": "forge fmt && npm run solhint",
"lint:check": "forge fmt --check && npm run solhint:check",
"ci:lint": "npm run lint:check",
"build:go": "./flatten.sh contracts/*.sol test/*.sol",
"build:go": "./flatten.sh contracts/**/*.sol test/**/*.sol",
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Build script patterns do not include any Solidity files

The updated build script patterns contracts/**/*.sol and test/**/*.sol do not match any Solidity files in packages/contracts-rfq/contracts/ or packages/contracts-rfq/test/. As a result, no Solidity files will be included in the build process.

  • Verify and update the paths in the build script to correctly point to the directories containing Solidity files, such as packages/contracts-core/contracts/**/*.sol and services/rfq/contracts/**/*.sol.
🔗 Analysis chain

LGTM! Verify the impact of the expanded file pattern.

The change to include all Solidity files in subdirectories of contracts and test is a good improvement. It ensures that no Solidity files are missed in the build process, which is especially helpful as the project grows and potentially adds more nested directories.

To ensure this change doesn't unintentionally include files that shouldn't be part of the build, please run the following script:

This will help you verify that only the intended files are being included in the build process.


Please run the following script to list all Solidity files in the repository. This will help identify the correct paths and ensure that the build process includes the intended files.


Please run the following script to list and count all Solidity files included by the updated build patterns in packages/contracts-rfq/package.json. This will help verify that the expanded patterns correctly include all intended files.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: List all Solidity files that will now be included in the build

echo "Newly included Solidity files:"
fd -e sol -d 2 . contracts test | grep -v -E '^(contracts/|test/)[^/]+\.sol$'

echo "Total Solidity files:"
fd -e sol . contracts test | wc -l

Length of output: 538


Script:

#!/bin/bash
# Description: List all Solidity files in the repository

echo "All Solidity files in the repository:"
fd -e sol . 

Length of output: 16116


Script:

#!/bin/bash
# Description: List and count Solidity files included by the build script patterns in packages/contracts-rfq/package.json

# Define the base directory relative to the repository root
BASE_DIR="packages/contracts-rfq"

echo "Solidity files matching 'contracts/**/*.sol' within ${BASE_DIR}:"
fd -e sol "$BASE_DIR/contracts" --type f

echo ""
echo "Solidity files matching 'test/**/*.sol' within ${BASE_DIR}:"
fd -e sol "$BASE_DIR/test" --type f

echo ""
echo "Total Solidity files included by the build script:"
fd -e sol "$BASE_DIR/contracts" "$BASE_DIR/test" --type f | wc -l

Length of output: 1967

"solhint": "solhint '{contracts,script,test}/**/*.sol' --fix --noPrompt --max-warnings 3",
"solhint:check": "solhint '{contracts,script,test}/**/*.sol' --max-warnings 3"
}
Expand Down
111 changes: 108 additions & 3 deletions packages/contracts-rfq/test/FastBridgeV2.Dst.ArbitraryCall.t.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {FastBridgeV2DstExclusivityTest, IFastBridgeV2} from "./FastBridgeV2.Dst.Exclusivity.t.sol";
import {IFastBridgeV2} from "../contracts/interfaces/IFastBridgeV2.sol";
import {FastBridgeV2DstExclusivityTest} from "./FastBridgeV2.Dst.Exclusivity.t.sol";
import {RecipientMock} from "./mocks/RecipientMock.sol";

import {Address} from "@openzeppelin/contracts/utils/Address.sol";
Expand Down Expand Up @@ -98,6 +99,28 @@ contract FastBridgeV2DstArbitraryCallTest is FastBridgeV2DstExclusivityTest {
relayWithAddress({caller: relayerB, relayer: relayerA, msgValue: 0, bridgeTx: tokenTx});
}

function test_relay_token_withCallValue_excessiveReturnValueRecipient_revertWhenCallParamsPresent()
public
virtual
override
{
setTokenTestCallValue(CALL_VALUE);
setTokenTestRecipient(excessiveReturnValueRecipient);
vm.expectRevert(RecipientIncorrectReturnValue.selector);
relay({caller: relayerA, msgValue: CALL_VALUE, bridgeTx: tokenTx});
}

function test_relay_token_withRelayerAddressCallValue_excessiveReturnValueRecipient_revertWhenCallParamsPresent()
public
virtual
override
{
setTokenTestCallValue(CALL_VALUE);
setTokenTestRecipient(excessiveReturnValueRecipient);
vm.expectRevert(RecipientIncorrectReturnValue.selector);
relayWithAddress({caller: relayerB, relayer: relayerA, msgValue: CALL_VALUE, bridgeTx: tokenTx});
}

function test_relay_eth_excessiveReturnValueRecipient_revertWhenCallParamsPresent() public virtual override {
setEthTestRecipient(excessiveReturnValueRecipient);
vm.expectRevert(RecipientIncorrectReturnValue.selector);
Expand Down Expand Up @@ -132,6 +155,28 @@ contract FastBridgeV2DstArbitraryCallTest is FastBridgeV2DstExclusivityTest {
relayWithAddress({caller: relayerB, relayer: relayerA, msgValue: 0, bridgeTx: tokenTx});
}

function test_relay_token_withCallValue_incorrectReturnValueRecipient_revertWhenCallParamsPresent()
public
virtual
override
{
setTokenTestCallValue(CALL_VALUE);
setTokenTestRecipient(incorrectReturnValueRecipient);
vm.expectRevert(RecipientIncorrectReturnValue.selector);
relay({caller: relayerA, msgValue: CALL_VALUE, bridgeTx: tokenTx});
}

function test_relay_token_withRelayerAddressCallValue_incorrectReturnValueRecipient_revertWhenCallParamsPresent()
public
virtual
override
{
setTokenTestCallValue(CALL_VALUE);
setTokenTestRecipient(incorrectReturnValueRecipient);
vm.expectRevert(RecipientIncorrectReturnValue.selector);
relayWithAddress({caller: relayerB, relayer: relayerA, msgValue: CALL_VALUE, bridgeTx: tokenTx});
}

function test_relay_eth_incorrectReturnValueRecipient_revertWhenCallParamsPresent() public virtual override {
setEthTestRecipient(incorrectReturnValueRecipient);
vm.expectRevert(RecipientIncorrectReturnValue.selector);
Expand All @@ -150,6 +195,8 @@ contract FastBridgeV2DstArbitraryCallTest is FastBridgeV2DstExclusivityTest {

// ══════════════════════════════════════════════ NO-OP RECIPIENT ══════════════════════════════════════════════════

// Note: in these tests NoOpRecipient doesn't implement hook function, so we expect a generic OZ library revert.

function test_relay_token_noOpRecipient_revertWhenCallParamsPresent() public virtual override {
setTokenTestRecipient(noOpRecipient);
vm.expectRevert(Address.FailedInnerCall.selector);
Expand All @@ -162,6 +209,24 @@ contract FastBridgeV2DstArbitraryCallTest is FastBridgeV2DstExclusivityTest {
relayWithAddress({caller: relayerB, relayer: relayerA, msgValue: 0, bridgeTx: tokenTx});
}

function test_relay_token_withCallValue_noOpRecipient_revertWhenCallParamsPresent() public virtual override {
setTokenTestCallValue(CALL_VALUE);
setTokenTestRecipient(noOpRecipient);
vm.expectRevert(Address.FailedInnerCall.selector);
relay({caller: relayerA, msgValue: CALL_VALUE, bridgeTx: tokenTx});
}

function test_relay_token_withRelayerAddressCallValue_noOpRecipient_revertWhenCallParamsPresent()
public
virtual
override
{
setTokenTestCallValue(CALL_VALUE);
setTokenTestRecipient(noOpRecipient);
vm.expectRevert(Address.FailedInnerCall.selector);
relayWithAddress({caller: relayerB, relayer: relayerA, msgValue: CALL_VALUE, bridgeTx: tokenTx});
}

function test_relay_eth_noOpRecipient_revertWhenCallParamsPresent() public virtual override {
setEthTestRecipient(noOpRecipient);
vm.expectRevert(Address.FailedInnerCall.selector);
Expand Down Expand Up @@ -192,6 +257,28 @@ contract FastBridgeV2DstArbitraryCallTest is FastBridgeV2DstExclusivityTest {
relayWithAddress({caller: relayerB, relayer: relayerA, msgValue: 0, bridgeTx: tokenTx});
}

function test_relay_token_withCallValue_noReturnValueRecipient_revertWhenCallParamsPresent()
public
virtual
override
{
setTokenTestCallValue(CALL_VALUE);
setTokenTestRecipient(noReturnValueRecipient);
vm.expectRevert(RecipientNoReturnValue.selector);
relay({caller: relayerA, msgValue: CALL_VALUE, bridgeTx: tokenTx});
}

function test_relay_token_withRelayerAddressCallValue_noReturnValueRecipient_revertWhenCallParamsPresent()
public
virtual
override
{
setTokenTestCallValue(CALL_VALUE);
setTokenTestRecipient(noReturnValueRecipient);
vm.expectRevert(RecipientNoReturnValue.selector);
relayWithAddress({caller: relayerB, relayer: relayerA, msgValue: CALL_VALUE, bridgeTx: tokenTx});
}

function test_relay_eth_noReturnValueRecipient_revertWhenCallParamsPresent() public virtual override {
setEthTestRecipient(noReturnValueRecipient);
vm.expectRevert(RecipientNoReturnValue.selector);
Expand Down Expand Up @@ -222,6 +309,20 @@ contract FastBridgeV2DstArbitraryCallTest is FastBridgeV2DstExclusivityTest {
relayWithAddress({caller: relayerB, relayer: relayerA, msgValue: 0, bridgeTx: tokenTx});
}

function test_relay_token_withCallValue_revert_recipientReverts() public {
setTokenTestCallValue(CALL_VALUE);
mockRecipientRevert(tokenTx);
vm.expectRevert(REVERT_MSG);
relay({caller: relayerA, msgValue: CALL_VALUE, bridgeTx: tokenTx});
}

function test_relay_token_withRelayerAddressCallValue_revert_recipientReverts() public {
setTokenTestCallValue(CALL_VALUE);
mockRecipientRevert(tokenTx);
vm.expectRevert(REVERT_MSG);
relayWithAddress({caller: relayerB, relayer: relayerA, msgValue: CALL_VALUE, bridgeTx: tokenTx});
}

function test_relay_eth_revert_recipientReverts() public {
mockRecipientRevert(ethTx);
vm.expectRevert(REVERT_MSG);
Expand All @@ -237,14 +338,18 @@ contract FastBridgeV2DstArbitraryCallTest is FastBridgeV2DstExclusivityTest {
function test_relay_eth_noCallParams_revert_recipientReverts() public {
setEthTestCallParams("");
vm.mockCallRevert({callee: userB, data: "", revertData: bytes(REVERT_MSG)});
vm.expectRevert("ETH transfer failed");
// Note: OZ library doesn't bubble the revert message for just sending ETH
// (as opposed to doing an external hook call). Therefore we expect a generic library revert.
vm.expectRevert(Address.FailedInnerCall.selector);
relay({caller: relayerB, msgValue: ethParams.destAmount, bridgeTx: ethTx});
}

function test_relay_eth_withRelayerAddress_noCallParams_revert_recipientReverts() public {
setEthTestCallParams("");
vm.mockCallRevert({callee: userB, data: "", revertData: bytes(REVERT_MSG)});
vm.expectRevert("ETH transfer failed");
// Note: OZ library doesn't bubble the revert message for just sending ETH
// (as opposed to doing an external hook call). Therefore we expect a generic library revert.
vm.expectRevert(Address.FailedInnerCall.selector);
relayWithAddress({caller: relayerA, relayer: relayerB, msgValue: ethParams.destAmount, bridgeTx: ethTx});
}
}
21 changes: 3 additions & 18 deletions packages/contracts-rfq/test/FastBridgeV2.Dst.Exclusivity.t.sol
Original file line number Diff line number Diff line change
@@ -1,30 +1,15 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {FastBridgeV2DstTest, IFastBridgeV2} from "./FastBridgeV2.Dst.t.sol";
import {FastBridgeV2DstTest} from "./FastBridgeV2.Dst.t.sol";

// solhint-disable func-name-mixedcase, ordering
contract FastBridgeV2DstExclusivityTest is FastBridgeV2DstTest {
uint256 public constant EXCLUSIVITY_PERIOD = 60 seconds;

function createFixturesV2() public virtual override {
tokenParamsV2 = IFastBridgeV2.BridgeParamsV2({
quoteRelayer: relayerA,
quoteExclusivitySeconds: int256(EXCLUSIVITY_PERIOD),
quoteId: "",
callParams: ""
});
ethParamsV2 = IFastBridgeV2.BridgeParamsV2({
quoteRelayer: relayerB,
quoteExclusivitySeconds: int256(EXCLUSIVITY_PERIOD),
quoteId: "",
callParams: ""
});

tokenTx.exclusivityRelayer = relayerA;
tokenTx.exclusivityEndTime = block.timestamp + EXCLUSIVITY_PERIOD;
ethTx.exclusivityRelayer = relayerB;
ethTx.exclusivityEndTime = block.timestamp + EXCLUSIVITY_PERIOD;
setTokenTestExclusivityParams(relayerA, EXCLUSIVITY_PERIOD);
setEthTestExclusivityParams(relayerB, EXCLUSIVITY_PERIOD);
}

// ═══════════════════════════════════════════════ RELAY: TOKEN ════════════════════════════════════════════════════
Expand Down
Loading
Loading