Skip to content

Commit

Permalink
Add optional contract call
Browse files Browse the repository at this point in the history
  • Loading branch information
viatrix committed Oct 24, 2024
1 parent abe40cf commit e46a7f3
Show file tree
Hide file tree
Showing 3 changed files with 188 additions and 73 deletions.
140 changes: 86 additions & 54 deletions contracts/adapters/SwapAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,21 @@ contract SwapAdapter is AccessControl {

mapping(address => bytes32) public tokenToResourceID;

// Used to avoid "stack too deep" error
struct LocalVars {
bytes32 resourceID;
bytes depositDataAfterAmount;
uint256 fee;
address feeHandlerRouter;
uint256 amountOut;
uint256 swapAmount;
bytes path;
IV3SwapRouter.ExactInputParams params;
bytes depositData;
address ERC20HandlerAddress;
uint256 leftover;
}

error CallerNotAdmin();
error AlreadySet();
error TokenInvalid();
Expand Down Expand Up @@ -68,15 +83,23 @@ contract SwapAdapter is AccessControl {
@notice Function for depositing tokens, performing swap to ETH and bridging the ETH.
@param destinationDomainID ID of chain deposit will be bridged to.
@param recipient Recipient of the deposit.
@param token Input token to be swapped.
@param tokenAmount Amount of tokens to be swapped.
@param gas The amount of gas needed to successfully execute the call to recipient on the destination. Fee amount is
directly affected by this value.
@param message Arbitrary encoded bytes array that will be passed as the third argument in the
ISygmaMessageReceiver(recipient).handleSygmaMessage(_, _, message) call. If you intend to use the
DefaultMessageReceiver, make sure to encode the message to comply with the
DefaultMessageReceiver.handleSygmaMessage() message decoding implementation.
@param token Input token to be swapped.
@param tokenAmount Amount of tokens to be swapped.
@param amountOutMinimum Minimal amount of ETH to be accepted as a swap output.
@param pathTokens Addresses of the tokens for Uniswap swap. WETH address is used for ETH.
@param pathFees Fees for Uniswap pools.
*/
function depositTokensToEth(
uint8 destinationDomainID,
address recipient,
uint256 gas,
bytes calldata message,
address token,
uint256 tokenAmount,
uint256 amountOutMinimum,
Expand Down Expand Up @@ -108,13 +131,16 @@ contract SwapAdapter is AccessControl {
amount = _swapRouter.exactInput(params);
}

if (amount == 0) revert InsufficientAmount(amount);

emit TokensSwapped(_weth, amount);
IWETH(_weth).withdraw(amount);

// Make Native Token deposit
_nativeTokenAdapter.depositToEVM{value: amount}(destinationDomainID, recipient);
_nativeTokenAdapter.depositToEVMWithMessage{value: amount}(
destinationDomainID,
recipient,
gas,
message
);

// Return unspent fee to msg.sender
uint256 leftover = address(this).balance;
Expand All @@ -128,78 +154,84 @@ contract SwapAdapter is AccessControl {
@notice Function for depositing tokens, performing swap to ETH and bridging the ETH.
@param destinationDomainID ID of chain deposit will be bridged to.
@param recipient Recipient of the deposit.
@param token Output token to be deposited after swapping.
@param gas The amount of gas needed to successfully execute the call to recipient on the destination. Fee amount is
directly affected by this value.
@param message Arbitrary encoded bytes array that will be passed as the third argument in the
ISygmaMessageReceiver(recipient).handleSygmaMessage(_, _, message) call. If you intend to use the
DefaultMessageReceiver, make sure to encode the message to comply with the
DefaultMessageReceiver.handleSygmaMessage() message decoding implementation.
@param token Output token to be deposited after swapping.
@param amountOutMinimum Minimal amount of tokens to be accepted as a swap output.
@param pathTokens Addresses of the tokens for Uniswap swap. WETH address is used for ETH.
@param pathFees Fees for Uniswap pools.
*/
function depositEthToTokens(
uint8 destinationDomainID,
address recipient,
uint256 gas,
bytes calldata message,
address token,
uint256 amountOutMinimum,
address[] memory pathTokens,
uint24[] memory pathFees
) external payable {
bytes32 resourceID = tokenToResourceID[token];
if (resourceID == bytes32(0)) revert TokenInvalid();
LocalVars memory vars;
vars.resourceID = tokenToResourceID[token];
if (vars.resourceID == bytes32(0)) revert TokenInvalid();

// Compose depositData
bytes memory depositDataAfterAmount = abi.encodePacked(
vars.depositDataAfterAmount = abi.encodePacked(
uint256(20),
recipient
recipient,
gas,
uint256(message.length),
message
);
if (msg.value == 0) revert InsufficientAmount(msg.value);
uint256 fee;
{
address feeHandlerRouter = _bridge._feeHandler();
(fee, ) = IFeeHandler(feeHandlerRouter).calculateFee(
address(this),
_bridge._domainID(),
destinationDomainID,
resourceID,
abi.encodePacked(msg.value, depositDataAfterAmount),
"" // feeData - not parsed
);
}

if (msg.value < fee) revert MsgValueLowerThanFee(msg.value);
uint256 amountOut;
{
uint256 swapAmount = msg.value - fee;
// Convert everything except the fee

// Swap ETH to tokens (exact input)
bytes memory path = _verifyAndEncodePath(
pathTokens,
pathFees,
_weth,
token
);
IV3SwapRouter.ExactInputParams memory params = IV3SwapRouter.ExactInputParams({
path: path,
recipient: address(this),
amountIn: swapAmount,
amountOutMinimum: amountOutMinimum
});
vars.feeHandlerRouter = _bridge._feeHandler();
(vars.fee, ) = IFeeHandler(vars.feeHandlerRouter).calculateFee(
address(this),
_bridge._domainID(),
destinationDomainID,
vars.resourceID,
abi.encodePacked(msg.value, vars.depositDataAfterAmount),
"" // feeData - not parsed
);

amountOut = _swapRouter.exactInput{value: swapAmount}(params);
emit TokensSwapped(token, amountOut);
}
if (msg.value < vars.fee) revert MsgValueLowerThanFee(msg.value);
vars.swapAmount = msg.value - vars.fee;
// Convert everything except the fee

bytes memory depositData = abi.encodePacked(
amountOut,
depositDataAfterAmount
// Swap ETH to tokens (exact input)
vars.path = _verifyAndEncodePath(
pathTokens,
pathFees,
_weth,
token
);
vars.params = IV3SwapRouter.ExactInputParams({
path: vars.path,
recipient: address(this),
amountIn: vars.swapAmount,
amountOutMinimum: amountOutMinimum
});

vars.amountOut = _swapRouter.exactInput{value: vars.swapAmount}(vars.params);
emit TokensSwapped(token, vars.amountOut);

vars.depositData = abi.encodePacked(
vars.amountOut,
vars.depositDataAfterAmount
);

address ERC20HandlerAddress = _bridge._resourceIDToHandlerAddress(resourceID);
IERC20(token).safeApprove(address(ERC20HandlerAddress), amountOut);
_bridge.deposit{value: fee}(destinationDomainID, resourceID, depositData, "");
vars.ERC20HandlerAddress = _bridge._resourceIDToHandlerAddress(vars.resourceID);
IERC20(token).safeApprove(address(vars.ERC20HandlerAddress), vars.amountOut);
_bridge.deposit{value: vars.fee}(destinationDomainID, vars.resourceID, vars.depositData, "");

// Return unspent fee to msg.sender
uint256 leftover = address(this).balance;
if (leftover > 0) {
payable(msg.sender).call{value: leftover}("");
vars.leftover = address(this).balance;
if (vars.leftover > 0) {
payable(msg.sender).call{value: vars.leftover}("");
// Do not revert if sender does not want to receive.
}
}
Expand Down
11 changes: 4 additions & 7 deletions contracts/adapters/interfaces/INativeTokenAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,11 @@ pragma solidity 0.8.11;
@author ChainSafe Systems.
*/
interface INativeTokenAdapter {
/**
@notice Makes a native token deposit with an included message.
@param destinationDomainID ID of destination chain.
@param recipientAddress Address that will receive native tokens on destination chain.
*/

function depositToEVM(
function depositToEVMWithMessage(
uint8 destinationDomainID,
address recipientAddress
address recipient,
uint256 gas,
bytes calldata message
) external payable;
}
Loading

0 comments on commit e46a7f3

Please sign in to comment.