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

[API-3529] Implement GoFastHandler contract #15

Merged
merged 10 commits into from
Nov 13, 2024
5 changes: 4 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
{}
{
"solidity.formatter": "forge",
"solidity.linter": ""
}
7 changes: 7 additions & 0 deletions AxelarHandler/remappings.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
lib/axelar-gmp-sdk-solidity/=lib/axelar-gmp-sdk-solidity/
lib/ds-test/=lib/forge-std/lib/ds-test/src/
lib/erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/
lib/forge-std/=lib/forge-std
lib/openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/
lib/openzeppelin-contracts/=lib/openzeppelin-contracts/
lib/openzeppelin/=lib/openzeppelin-contracts-upgradeable/contracts/
2 changes: 1 addition & 1 deletion AxelarHandler/src/AxelarExecutableUpgradeable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,4 @@ contract AxelarExecutableUpgradeable is IAxelarExecutable, Initializable {
string calldata tokenSymbol,
uint256 amount
) internal virtual {}
}
}
2 changes: 1 addition & 1 deletion AxelarHandler/src/AxelarHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -427,4 +427,4 @@ contract AxelarHandler is AxelarExecutableUpgradeable, Ownable2StepUpgradeable,
}

function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
}
}
127 changes: 127 additions & 0 deletions AxelarHandler/src/GoFastHandler.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

import {IERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
import {Ownable} from "lib/openzeppelin-contracts/contracts/access/Ownable.sol";

import {ISwapRouter02} from "./interfaces/ISwapRouter02.sol";
import {IFastTransferGateway} from "./interfaces/IFastTransferGateway.sol";

contract GoFastHandler is Ownable {
using SafeERC20 for IERC20;

ISwapRouter02 public swapRouter;
IFastTransferGateway public fastTransferGateway;
dhfang marked this conversation as resolved.
Show resolved Hide resolved

constructor(address _swapRouter, address _fastTransferGateway) {
swapRouter = ISwapRouter02(_swapRouter);
fastTransferGateway = IFastTransferGateway(_fastTransferGateway);
}

function setSwapRouter(address _swapRouter) public onlyOwner {
swapRouter = ISwapRouter02(_swapRouter);
}

function setFastTransferGateway(address _fastTransferGateway) public onlyOwner {
fastTransferGateway = IFastTransferGateway(_fastTransferGateway);
}

function swapAndSubmitOrder(
address tokenIn,
uint256 swapAmountIn,
bytes memory swapCalldata,
uint256 executionFeeAmount,
uint256 solverFeeBPS,
bytes32 sender,
bytes32 recipient,
uint32 destinationDomain,
uint64 timeoutTimestamp,
bytes calldata destinationCalldata
) public payable returns (bytes32) {
require(executionFeeAmount != 0, "execution fee cannot be zero");
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not sure this a safe assumption to make. @NotJeremyLiu

Copy link
Member

Choose a reason for hiding this comment

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

If this is the source + dest gas fee, then I think there could be cases where it is 0

Copy link
Collaborator

@dhfang dhfang Nov 11, 2024

Choose a reason for hiding this comment

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

@thal0x if we remove this check i think we have a potential fund loss bug if the swapCallData ouput denom does not match the fast transfer gateway asset as the check in line 54 can pass on a zero swapAmountOut. can we rewrite things to avoid that possibility here?

it also seems like we should just make the contract upgradeable - seems like cheap insurance against stuck funds in general. wdyt?

require(solverFeeBPS != 0, "solver fee cannot be zero");

uint256 swapAmountOut;
uint256 swapAmountOutAfterFee;

{
swapAmountOut = _swap(tokenIn, swapAmountIn, swapCalldata);

uint256 solverFeeAmount = (swapAmountOut * solverFeeBPS) / 10000;
dhfang marked this conversation as resolved.
Show resolved Hide resolved
uint256 totalFee = executionFeeAmount + solverFeeAmount;

require(swapAmountOut >= totalFee, "amount received from swap is less than fee");

// this is the amount that the recipient will receive on the destination chain
swapAmountOutAfterFee = swapAmountOut - totalFee;
}

bytes32 orderId = fastTransferGateway.submitOrder(
sender,
recipient,
swapAmountOut,
swapAmountOutAfterFee,
destinationDomain,
timeoutTimestamp,
destinationCalldata
);

_refundToken(tokenIn);

return orderId;
}

function submitOrder(
bytes32 sender,
bytes32 recipient,
uint256 amountIn,
uint256 amountOut,
uint32 destinationDomain,
uint64 timeoutTimestamp,
bytes calldata data
) external returns (bytes32) {
return fastTransferGateway.submitOrder(
sender, recipient, amountIn, amountOut, destinationDomain, timeoutTimestamp, data
);
}

function _swap(address tokenIn, uint256 amountIn, bytes memory swapCalldata) internal returns (uint256 amountOut) {
address tokenOut = fastTransferGateway.token();
Copy link
Collaborator

@dhfang dhfang Nov 9, 2024

Choose a reason for hiding this comment

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

Its a little strange that the caller needs to match their swapCallData to swap into a token whose address is stored in the gateway. This is probably fine in practice since we are generating the swap payload, but if we mess up, can we run into situations where funds are stuck if the swap output token in the swapCallData does not match the gateway token?


uint256 tokenOutBalanceBefore = IERC20(tokenOut).balanceOf(address(this));

if (tokenIn != address(0)) {
dhfang marked this conversation as resolved.
Show resolved Hide resolved
IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn);

IERC20(tokenIn).safeApprove(address(swapRouter), amountIn);
}

(bool success,) = address(swapRouter).call{value: msg.value}(swapCalldata);
dhfang marked this conversation as resolved.
Show resolved Hide resolved
if (!success) {
assembly {
returndatacopy(0, 0, returndatasize())
revert(0, returndatasize())
}
}

amountOut = IERC20(tokenOut).balanceOf(address(this)) - tokenOutBalanceBefore;
}

function _tokenBalance(address token) internal view returns (uint256) {
if (token != address(0)) {
return IERC20(token).balanceOf(address(this));
} else {
return address(this).balance;
}
}

function _refundToken(address token) internal {
uint256 amount = _tokenBalance(token);
if (token != address(0)) {
IERC20(token).safeTransfer(msg.sender, amount);
} else {
payable(msg.sender).transfer(amount);
}
}
}
16 changes: 16 additions & 0 deletions AxelarHandler/src/interfaces/IFastTransferGateway.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

interface IFastTransferGateway {
function submitOrder(
bytes32 sender,
bytes32 recipient,
uint256 amountIn,
uint256 amountOut,
uint32 destinationDomain,
uint64 timeoutTimestamp,
bytes calldata data
) external returns (bytes32);

function token() external view returns (address);
}
Loading
Loading