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

XReceive fails to record msg if no gas #638

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
35 changes: 32 additions & 3 deletions packages/protocol/src/routers/ConnextRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ contract ConnextRouter is BaseRouter, IXReceiver {
*/
event NewRouterAdded(address indexed router, uint256 indexed domain);

/**
* @dev Emitted when a new _catchGasCost value is set.
*
* @param newCatchGasCost value
*/
event CatchGasCostChanged(uint256 newCatchGasCost);

/**
* @dev Emitted when Connext `xCall` is invoked.
*
Expand Down Expand Up @@ -74,9 +81,11 @@ contract ConnextRouter is BaseRouter, IXReceiver {
/// @dev Custom Errors
error ConnextRouter__setRouter_invalidInput();
error ConnextRouter__xReceive_notReceivedAssetBalance();
error ConnextRouter__xReceive_notAllowedCaller();
error ConnextRouter__xReceiver_noValueTransferUseXbundle();
error ConnnextRouter__xReceive_notEnoughGasForTryCatch();
error ConnnextRouter__xBundleConnext_notSelfCalled();
error ConnextRouter__setCatchExecutionGasCost_zeroValue();

uint256 private _catchGasCost;

/// @dev The connext contract on the origin domain.
IConnext public immutable connext;
Expand All @@ -102,6 +111,7 @@ contract ConnextRouter is BaseRouter, IXReceiver {
connext = connext_;
handler = new ConnextHandler(address(this));
_allowCaller(msg.sender, true);
_catchGasCost = 670000;
}

/*////////////////////////////////////
Expand Down Expand Up @@ -168,7 +178,7 @@ contract ConnextRouter is BaseRouter, IXReceiver {
* to `xReceive` fails. That's why we need to ensure the funds are not stuck at Connext.
* Therefore we try/catch instead of directly calling _bundleInternal(...).
*/
try this.xBundleConnext(actions, args, beforeSlipped) {
try this.xBundleConnext{gas: gasleft() - _catchGasCost}(actions, args, beforeSlipped) {
emit XReceived(transferId, originDomain, true, asset, amount, callData);
} catch {
if (balance > 0) {
Expand Down Expand Up @@ -465,4 +475,23 @@ contract ConnextRouter is BaseRouter, IXReceiver {

emit NewRouterAdded(router, domain);
}

/**
* @notice Sets a new value for `_catchGasCost`.
*
* @param newCatchGasCost value
*
* @dev Requirements:
* - Must be restricted to timelock.
* - Must ensure through testing that `newCatchGasCost` > than most
* gas heavy call in {ConnextHandler-recordFailed(...)}.
*/
function setCatchGasExecutionCost(uint256 newCatchGasCost) external onlyTimelock {
if (newCatchGasCost == 0) {
revert ConnextRouter__setCatchExecutionGasCost_zeroValue();
}
_catchGasCost = newCatchGasCost;

emit CatchGasCostChanged(newCatchGasCost);
}
}
35 changes: 35 additions & 0 deletions packages/protocol/test/forking/goerli/ConnextRouter.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ contract MockTestFlasher is Routines, IFlasher {
}
}

contract MaliciousReceiver {
receive() external payable {
while (true) {}
}
}

contract ConnextRouterForkingTests is Routines, ForkingSetup {
event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);

Expand Down Expand Up @@ -265,6 +271,35 @@ contract ConnextRouterForkingTests is Routines, ForkingSetup {
assertEq(IERC20(collateralAsset).balanceOf(address(connextHandler)), amount);
}

function test_gasGriefingInboundXBundle() public {
MaliciousReceiver maliciousReceiver = new MaliciousReceiver();

uint256 amount = 1 ether;

// make the callData with malicious receiver
IRouter.Action[] memory actions_ = new IRouter.Action[](1);
actions_[0] = IRouter.Action.WithdrawETH;

bytes[] memory args_ = new bytes[](1);
args_[0] = abi.encode(amount, address(maliciousReceiver));

bytes memory callData = abi.encode(actions_, args_);

// send directly the bridged funds to our router
// thus mocking Connext behavior
deal(collateralAsset, address(connextRouter), amount);

vm.startPrank(registry[domain].connext);
// Limit the amount of gas being passed
connextRouter.xReceive{gas: 750000}(
"", amount, vault.asset(), address(connextRouter), OPTIMISM_GOERLI_DOMAIN, callData
);
vm.stopPrank();

// Funds were moved to ConnextHandler regardles of maliciousReceiver
assertEq(IERC20(collateralAsset).balanceOf(address(connextHandler)), amount);
}

function test_retryFailedInboundXReceive() public {
uint256 amount = 2 ether;
uint256 borrowAmount = 1000e6;
Expand Down