Skip to content

Commit 6aa9402

Browse files
authored
Merge pull request #32 from skip-mev/jw/update-axelar-handler
Update Axelar handler to support new swap router
2 parents 033273d + 2b7c0e2 commit 6aa9402

File tree

12 files changed

+1001
-225
lines changed

12 files changed

+1001
-225
lines changed

AxelarHandler/src/AxelarHandler.sol

Lines changed: 92 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: UNLICENSED
2-
pragma solidity 0.8.18;
2+
pragma solidity ^0.8.0;
33

44
import {IWETH} from "./interfaces/IWETH.sol";
55

@@ -13,7 +13,6 @@ import {Ownable2StepUpgradeable} from
1313
import {UUPSUpgradeable} from "lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol";
1414
import {Initializable} from "lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol";
1515

16-
import {ISwapRouter02} from "./interfaces/ISwapRouter02.sol";
1716
import {SkipSwapRouter} from "./libraries/SkipSwapRouter.sol";
1817

1918
/// @title AxelarHandler
@@ -32,7 +31,7 @@ contract AxelarHandler is AxelarExecutableUpgradeable, Ownable2StepUpgradeable,
3231
error NonNativeCannotBeUnwrapped();
3332
error NativePaymentFailed();
3433
error WrappingNotEnabled();
35-
error SwapFailed();
34+
error SwapFailedError();
3635
error InsufficientSwapOutput();
3736
error InsufficientNativeToken();
3837
error ETHSendFailed();
@@ -45,8 +44,7 @@ contract AxelarHandler is AxelarExecutableUpgradeable, Ownable2StepUpgradeable,
4544
enum Commands {
4645
SendToken,
4746
SendNative,
48-
Swap,
49-
MultiSwap
47+
Swap
5048
}
5149

5250
bytes32 private _wETHSymbolHash;
@@ -58,7 +56,7 @@ contract AxelarHandler is AxelarExecutableUpgradeable, Ownable2StepUpgradeable,
5856

5957
bytes32 public constant DISABLED_SYMBOL = keccak256(abi.encodePacked("DISABLED"));
6058

61-
ISwapRouter02 public swapRouter;
59+
address public swapRouter;
6260

6361
bool internal reentrant;
6462

@@ -96,7 +94,7 @@ contract AxelarHandler is AxelarExecutableUpgradeable, Ownable2StepUpgradeable,
9694
function setSwapRouter(address _swapRouter) external onlyOwner {
9795
if (_swapRouter == address(0)) revert ZeroAddress();
9896

99-
swapRouter = ISwapRouter02(_swapRouter);
97+
swapRouter = _swapRouter;
10098
}
10199

102100
/// @notice Sends native currency to other chains through the axelar gateway.
@@ -230,73 +228,13 @@ contract AxelarHandler is AxelarExecutableUpgradeable, Ownable2StepUpgradeable,
230228
if (bytes(symbol).length == 0) revert EmptySymbol();
231229

232230
// Get the address of the output token based on the symbol provided
233-
IERC20 outputToken = IERC20(_getTokenAddress(symbol));
231+
address outputToken = _getTokenAddress(symbol);
234232

235233
uint256 outputAmount;
236234
if (inputToken == address(0)) {
237-
// Native Token
238-
if (amount + gasPaymentAmount != msg.value) revert InsufficientNativeToken();
239-
240-
// Get the contract's balances previous to the swap
241-
uint256 preInputBalance = address(this).balance - msg.value;
242-
uint256 preOutputBalance = outputToken.balanceOf(address(this));
243-
244-
// Call the swap router and perform the swap
245-
(bool success,) = address(swapRouter).call{value: amount}(swapCalldata);
246-
if (!success) revert SwapFailed();
247-
248-
// Get the contract's balances after the swap
249-
uint256 postInputBalance = address(this).balance;
250-
uint256 postOutputBalance = outputToken.balanceOf(address(this));
251-
252-
// Check that the contract's native token balance has increased
253-
if (preOutputBalance >= postOutputBalance) revert InsufficientSwapOutput();
254-
outputAmount = postOutputBalance - preOutputBalance;
255-
256-
// Refund the remaining ETH
257-
uint256 dust = postInputBalance - preInputBalance - gasPaymentAmount;
258-
if (dust != 0) {
259-
(bool ethSuccess,) = msg.sender.call{value: dust}("");
260-
if (!ethSuccess) revert ETHSendFailed();
261-
}
262-
263-
emit SwapSuccess(inputToken, address(outputToken), amount, outputAmount);
235+
outputAmount = _swapAndRefundNative(amount, outputToken, gasPaymentAmount, swapCalldata);
264236
} else {
265-
// ERC20 Token
266-
if (gasPaymentAmount != msg.value) revert();
267-
268-
// Transfer input ERC20 tokens to the contract
269-
IERC20 token = IERC20(inputToken);
270-
token.safeTransferFrom(msg.sender, address(this), amount);
271-
272-
// Approve the swap router to spend the input tokens
273-
token.safeApprove(address(swapRouter), amount);
274-
275-
// Get the contract's balances previous to the swap
276-
uint256 preInputBalance = token.balanceOf(address(this));
277-
uint256 preOutputBalance = outputToken.balanceOf(address(this));
278-
279-
// Call the swap router and perform the swap
280-
(bool success,) = address(swapRouter).call(swapCalldata);
281-
if (!success) revert SwapFailed();
282-
283-
// Get the contract's balances after the swap
284-
uint256 dust = token.balanceOf(address(this)) + amount - preInputBalance;
285-
uint256 postOutputBalance = outputToken.balanceOf(address(this));
286-
287-
// Check that the contract's output token balance has increased
288-
if (preOutputBalance >= postOutputBalance) revert InsufficientSwapOutput();
289-
outputAmount = postOutputBalance - preOutputBalance;
290-
291-
// Refund the remaining amount
292-
if (dust != 0) {
293-
token.safeTransfer(msg.sender, dust);
294-
295-
// Revoke approval
296-
token.safeApprove(address(swapRouter), 0);
297-
}
298-
299-
emit SwapSuccess(inputToken, address(outputToken), amount, outputAmount);
237+
outputAmount = _swapAndRefund(amount, inputToken, outputToken, gasPaymentAmount, swapCalldata);
300238
}
301239

302240
// Pay the gas for the GMP transfer
@@ -390,30 +328,13 @@ contract AxelarHandler is AxelarExecutableUpgradeable, Ownable2StepUpgradeable,
390328

391329
_sendNative(token, amount, destination);
392330
} else if (command == Commands.Swap) {
393-
(address destination, bool unwrapOut, bytes memory swap) = abi.decode(data, (address, bool, bytes));
394-
395-
try SkipSwapRouter.swap(swapRouter, destination, tokenIn, amount, swap) returns (
396-
IERC20 tokenOut, uint256 amountOut
397-
) {
398-
if (unwrapOut) {
399-
_sendNative(address(tokenOut), amountOut, destination);
400-
emit SwapSuccess(address(tokenIn), address(0), amount, amountOut);
401-
} else {
402-
_sendToken(address(tokenOut), amountOut, destination);
403-
emit SwapSuccess(address(tokenIn), address(tokenOut), amount, amountOut);
404-
}
405-
} catch {
406-
_sendToken(token, amount, destination);
407-
emit SwapFailed();
408-
}
409-
} else if (command == Commands.MultiSwap) {
410-
(address destination, bool unwrapOut, bytes[] memory swaps) = abi.decode(data, (address, bool, bytes[]));
331+
(address tokenOut, address destination, bytes memory swap) = abi.decode(data, (address, address, bytes));
411332

412-
try SkipSwapRouter.multiSwap(swapRouter, destination, tokenIn, amount, swaps) returns (
413-
IERC20 tokenOut, uint256 amountOut
333+
try SkipSwapRouter.swap(swapRouter, destination, address(tokenIn), tokenOut, amount, swap) returns (
334+
uint256 amountOut
414335
) {
415-
if (unwrapOut) {
416-
_sendNative(address(tokenOut), amountOut, destination);
336+
if (tokenOut == address(0)) {
337+
address(destination).call{value: amountOut}("");
417338
emit SwapSuccess(address(tokenIn), address(0), amount, amountOut);
418339
} else {
419340
_sendToken(address(tokenOut), amountOut, destination);
@@ -428,6 +349,85 @@ contract AxelarHandler is AxelarExecutableUpgradeable, Ownable2StepUpgradeable,
428349
}
429350
}
430351

352+
function _swapAndRefund(
353+
uint256 amount,
354+
address inputToken,
355+
address outputToken,
356+
uint256 gasPaymentAmount,
357+
bytes memory swapCalldata
358+
) internal returns (uint256 outputAmount) {
359+
// ERC20 Token
360+
if (gasPaymentAmount != msg.value) revert();
361+
362+
// Transfer input ERC20 tokens to the contract
363+
IERC20 token = IERC20(inputToken);
364+
token.safeTransferFrom(msg.sender, address(this), amount);
365+
366+
// Approve the swap router to spend the input tokens
367+
token.safeApprove(address(swapRouter), amount);
368+
369+
// Get the contract's balances previous to the swap
370+
uint256 preInputBalance = token.balanceOf(address(this));
371+
uint256 preOutputBalance = IERC20(outputToken).balanceOf(address(this));
372+
373+
// Call the swap router and perform the swap
374+
(bool success,) = address(swapRouter).call(swapCalldata);
375+
if (!success) revert SwapFailedError();
376+
377+
// Get the contract's balances after the swap
378+
uint256 dust = token.balanceOf(address(this)) + amount - preInputBalance;
379+
uint256 postOutputBalance = IERC20(outputToken).balanceOf(address(this));
380+
381+
// Check that the contract's output token balance has increased
382+
if (preOutputBalance >= postOutputBalance) revert InsufficientSwapOutput();
383+
outputAmount = postOutputBalance - preOutputBalance;
384+
385+
// Refund the remaining amount
386+
if (dust != 0) {
387+
token.safeTransfer(msg.sender, dust);
388+
389+
// Revoke approval
390+
token.safeApprove(address(swapRouter), 0);
391+
}
392+
393+
emit SwapSuccess(inputToken, address(outputToken), amount, outputAmount);
394+
}
395+
396+
function _swapAndRefundNative(
397+
uint256 amount,
398+
address outputToken,
399+
uint256 gasPaymentAmount,
400+
bytes memory swapCalldata
401+
) internal returns (uint256 outputAmount) {
402+
// Native Token
403+
if (amount + gasPaymentAmount != msg.value) revert InsufficientNativeToken();
404+
405+
// Get the contract's balances previous to the swap
406+
uint256 preInputBalance = address(this).balance - msg.value;
407+
uint256 preOutputBalance = IERC20(outputToken).balanceOf(address(this));
408+
409+
// Call the swap router and perform the swap
410+
(bool success,) = address(swapRouter).call{value: amount}(swapCalldata);
411+
if (!success) revert SwapFailedError();
412+
413+
// Get the contract's balances after the swap
414+
uint256 postInputBalance = address(this).balance;
415+
uint256 postOutputBalance = IERC20(outputToken).balanceOf(address(this));
416+
417+
// Check that the contract's native token balance has increased
418+
if (preOutputBalance >= postOutputBalance) revert InsufficientSwapOutput();
419+
outputAmount = postOutputBalance - preOutputBalance;
420+
421+
// Refund the remaining ETH
422+
uint256 dust = postInputBalance - preInputBalance - gasPaymentAmount;
423+
if (dust != 0) {
424+
(bool ethSuccess,) = msg.sender.call{value: dust}("");
425+
if (!ethSuccess) revert ETHSendFailed();
426+
}
427+
428+
emit SwapSuccess(address(0), address(outputToken), amount, outputAmount);
429+
}
430+
431431
function _sendToken(address token, uint256 amount, address destination) internal {
432432
IERC20(token).safeTransfer(destination, amount);
433433
}

AxelarHandler/src/libraries/SkipSwapRouter.sol

Lines changed: 30 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -14,105 +14,38 @@ library SkipSwapRouter {
1414
error InsufficientOutputAmount();
1515
error NativePaymentFailed();
1616

17-
enum SwapCommands {
18-
ExactInputSingle,
19-
ExactInput,
20-
ExactTokensForTokens,
21-
ExactOutputSingle,
22-
ExactOutput,
23-
TokensForExactTokens
24-
}
25-
26-
function multiSwap(
27-
ISwapRouter02 router,
17+
function swap(
18+
address router,
2819
address destination,
29-
IERC20 inputToken,
20+
address inputToken,
21+
address outputToken,
3022
uint256 amountIn,
31-
bytes[] memory swaps
32-
) external returns (IERC20 outputToken, uint256 amountOut) {
33-
outputToken = inputToken;
34-
amountOut = amountIn;
35-
36-
uint256 numSwaps = swaps.length;
37-
for (uint256 i; i < numSwaps; i++) {
38-
// The output token and amount of each iteration is the input token and amount of the next.
39-
(outputToken, amountOut) = swap(router, destination, outputToken, amountOut, swaps[i]);
40-
}
41-
}
23+
bytes memory swapData
24+
) public returns (uint256 amountOut) {
25+
uint256 preBalIn = IERC20(inputToken).balanceOf(address(this)) - amountIn;
26+
27+
uint256 preBalOut =
28+
outputToken == address(0) ? address(this).balance : IERC20(outputToken).balanceOf(address(this));
29+
30+
IERC20(inputToken).forceApprove(router, amountIn);
4231

43-
function swap(ISwapRouter02 router, address destination, IERC20 inputToken, uint256 amountIn, bytes memory payload)
44-
public
45-
returns (IERC20 outputToken, uint256 outputAmount)
46-
{
47-
(SwapCommands command, address tokenOut, uint256 amountOut, bytes memory swapData) =
48-
abi.decode(payload, (SwapCommands, address, uint256, bytes));
49-
50-
outputToken = IERC20(tokenOut);
51-
52-
uint256 preBalIn = inputToken.balanceOf(address(this)) - amountIn;
53-
uint256 preBalOut = outputToken.balanceOf(address(this));
54-
55-
inputToken.forceApprove(address(router), amountIn);
56-
57-
if (command == SwapCommands.ExactInputSingle) {
58-
ISwapRouter02.ExactInputSingleParams memory params;
59-
params.tokenIn = address(inputToken);
60-
params.tokenOut = tokenOut;
61-
params.recipient = address(this);
62-
params.amountIn = amountIn;
63-
params.amountOutMinimum = amountOut;
64-
65-
(params.fee, params.sqrtPriceLimitX96) = abi.decode(swapData, (uint24, uint160));
66-
67-
router.exactInputSingle(params);
68-
} else if (command == SwapCommands.ExactInput) {
69-
ISwapRouter02.ExactInputParams memory params;
70-
params.path = _fixPath(address(inputToken), tokenOut, swapData);
71-
params.path = swapData;
72-
params.recipient = address(this);
73-
params.amountIn = amountIn;
74-
params.amountOutMinimum = amountOut;
75-
76-
router.exactInput(params);
77-
} else if (command == SwapCommands.ExactTokensForTokens) {
78-
address[] memory path = _fixPath(address(inputToken), tokenOut, abi.decode(swapData, (address[])));
79-
80-
router.swapExactTokensForTokens(amountIn, amountOut, path, address(this));
81-
} else if (command == SwapCommands.ExactOutputSingle) {
82-
ISwapRouter02.ExactOutputSingleParams memory params;
83-
params.tokenIn = address(inputToken);
84-
params.tokenOut = tokenOut;
85-
params.recipient = address(this);
86-
params.amountInMaximum = amountIn;
87-
params.amountOut = amountOut;
88-
89-
(params.fee, params.sqrtPriceLimitX96) = abi.decode(swapData, (uint24, uint160));
90-
91-
router.exactOutputSingle(params);
92-
} else if (command == SwapCommands.ExactOutput) {
93-
ISwapRouter02.ExactOutputParams memory params;
94-
params.path = _fixPath(tokenOut, address(inputToken), swapData);
95-
params.path = swapData;
96-
params.recipient = address(this);
97-
params.amountInMaximum = amountIn;
98-
params.amountOut = amountOut;
99-
100-
router.exactOutput(params);
101-
} else if (command == SwapCommands.TokensForExactTokens) {
102-
address[] memory path = _fixPath(address(inputToken), address(tokenOut), abi.decode(swapData, (address[])));
103-
104-
router.swapTokensForExactTokens(amountOut, amountIn, path, address(this));
32+
(bool success, bytes memory returnData) = router.call(swapData);
33+
34+
if (!success) {
35+
_revertWithData(returnData);
10536
}
10637

107-
outputAmount = outputToken.balanceOf(address(this)) - preBalOut;
108-
if (outputAmount < amountOut) {
109-
revert InsufficientOutputAmount();
38+
if (outputToken == address(0)) {
39+
amountOut = address(this).balance - preBalOut;
40+
} else {
41+
amountOut = IERC20(outputToken).balanceOf(address(this)) - preBalOut;
11042
}
11143

112-
uint256 dust = inputToken.balanceOf(address(this)) - preBalIn;
44+
uint256 dust = IERC20(inputToken).balanceOf(address(this)) - preBalIn;
45+
11346
if (dust != 0) {
114-
inputToken.forceApprove(address(router), 0);
115-
inputToken.safeTransfer(destination, dust);
47+
IERC20(inputToken).forceApprove(router, 0);
48+
IERC20(inputToken).safeTransfer(destination, dust);
11649
}
11750
}
11851

@@ -155,4 +88,10 @@ library SkipSwapRouter {
15588

15689
return path;
15790
}
91+
92+
function _revertWithData(bytes memory data) private pure {
93+
assembly {
94+
revert(add(data, 32), mload(data))
95+
}
96+
}
15897
}

0 commit comments

Comments
 (0)