wormhole-nativeswap-example/contracts/contracts/CrossChainSwapV2.sol

461 lines
15 KiB
Solidity
Raw Normal View History

// SPDX-License-Identifier: Apache 2
2022-01-11 08:29:49 -08:00
pragma solidity ^0.7.6;
pragma abicoder v2;
2022-06-30 14:49:49 -07:00
import './shared/IWormhole.sol';
import './shared/SwapHelper.sol';
import './shared/TokenBridge.sol';
import './shared/WETH.sol';
2022-01-20 13:40:38 -08:00
import 'solidity-bytes-utils/contracts/BytesLib.sol';
import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import '@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol';
import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
2022-01-11 08:29:49 -08:00
2022-01-22 11:25:01 -08:00
/// @title A cross-chain UniswapV2 example
/// @notice Swaps against UniswapV2 pools and uses Wormhole TokenBridge
/// for cross-chain transfers
2022-01-11 08:29:49 -08:00
contract CrossChainSwapV2 {
using SafeERC20 for IERC20;
using BytesLib for bytes;
2022-06-30 14:49:49 -07:00
uint8 public immutable TypeExactIn = 1;
uint8 public immutable TypeExactOut = 2;
IUniswapV2Router02 public immutable SWAP_ROUTER;
address public immutable FEE_TOKEN_ADDRESS;
address public immutable TOKEN_BRIDGE_ADDRESS;
address public immutable WRAPPED_NATIVE;
2022-01-20 08:52:35 -08:00
constructor(
address _swapRouterAddress,
address _feeTokenAddress,
address _tokenBridgeAddress,
2022-01-21 10:37:14 -08:00
address _wrappedNativeAddress
2022-01-20 08:52:35 -08:00
) {
2022-06-30 14:49:49 -07:00
SWAP_ROUTER = IUniswapV2Router02(_swapRouterAddress);
FEE_TOKEN_ADDRESS = _feeTokenAddress;
TOKEN_BRIDGE_ADDRESS = _tokenBridgeAddress;
WRAPPED_NATIVE = _wrappedNativeAddress;
2022-01-20 08:52:35 -08:00
}
2022-01-22 11:25:01 -08:00
/// @dev Used to communicate information about executed swaps to UI/user
2022-01-20 13:40:38 -08:00
event SwapResult(
2022-01-21 10:37:14 -08:00
address indexed _recipient,
address _tokenOut,
address _from,
uint256 _amountOut,
uint8 _success
2022-01-20 13:40:38 -08:00
);
2022-01-22 11:25:01 -08:00
/// @dev Returns the parsed TokenBridge payload which contains swap
/// instructions after redeeming the VAA from the TokenBridge
2022-01-20 13:40:38 -08:00
function _getParsedPayload(
bytes calldata encodedVaa,
2022-06-30 14:49:49 -07:00
uint8 swapFunctionType
2022-01-20 13:40:38 -08:00
) private returns (SwapHelper.DecodedVaaParameters memory payload) {
2022-01-20 08:52:35 -08:00
// complete the transfer on the token bridge
bytes memory vmPayload = TokenBridge(
2022-06-30 14:49:49 -07:00
TOKEN_BRIDGE_ADDRESS
2022-06-28 16:03:50 -07:00
).completeTransferWithPayload(encodedVaa);
2022-01-20 08:52:35 -08:00
// parse the payload
payload = SwapHelper.decodeVaaPayload(vmPayload);
2022-01-20 13:40:38 -08:00
// sanity check payload parameters
require(
payload.swapFunctionType==swapFunctionType,
2022-01-22 11:25:01 -08:00
"incorrect swapFunctionType in payload"
2022-01-20 13:40:38 -08:00
);
}
2022-01-22 11:25:01 -08:00
/// @dev Executes exactIn native asset swap and pays the relayer
2022-01-20 13:40:38 -08:00
function recvAndSwapExactNativeIn(
bytes calldata encodedVaa
) external payable returns (uint256[] memory amounts) {
// redeem and fetch parsed payload
SwapHelper.DecodedVaaParameters memory payload =
_getParsedPayload(
encodedVaa,
2022-06-30 14:49:49 -07:00
TypeExactIn
2022-01-20 13:40:38 -08:00
);
2022-01-20 08:52:35 -08:00
2022-01-22 11:25:01 -08:00
// create dynamic address array
// uniswap won't take fixed size array
2022-01-20 08:52:35 -08:00
address[] memory uniPath = new address[](2);
uniPath[0] = payload.path[0];
uniPath[1] = payload.path[1];
2022-01-22 11:25:01 -08:00
// sanity check path
require(
2022-06-30 14:49:49 -07:00
uniPath[0]==FEE_TOKEN_ADDRESS,
"tokenIn must be feeToken"
2022-01-22 11:25:01 -08:00
);
2022-01-21 10:37:14 -08:00
require(
2022-06-30 14:49:49 -07:00
uniPath[1]==WRAPPED_NATIVE,
2022-01-21 10:37:14 -08:00
"tokenOut must be wrapped native asset"
);
2022-01-20 08:52:35 -08:00
// approve the router to spend tokens
2022-01-20 13:40:38 -08:00
TransferHelper.safeApprove(
uniPath[0],
2022-06-30 14:49:49 -07:00
address(SWAP_ROUTER),
payload.swapAmount
2022-01-20 13:40:38 -08:00
);
2022-01-20 08:52:35 -08:00
2022-01-21 10:37:14 -08:00
// try to execute the swap
2022-06-30 14:49:49 -07:00
try SWAP_ROUTER.swapExactTokensForTokens(
payload.swapAmount,
2022-01-20 08:52:35 -08:00
payload.estimatedAmount,
uniPath,
2022-01-20 13:40:38 -08:00
address(this),
2022-01-20 08:52:35 -08:00
payload.deadline
) returns (uint256[] memory amounts) {
// calculate how much to pay the relayer in the native token
uint256 nativeRelayerFee = amounts[1] * payload.relayerFee / payload.swapAmount;
uint256 nativeAmountOut = amounts[1] - nativeRelayerFee;
2022-01-20 13:40:38 -08:00
// unwrap native and send to recipient
2022-06-30 14:49:49 -07:00
IWETH(WRAPPED_NATIVE).withdraw(amounts[1]);
payable(payload.recipientAddress).transfer(nativeAmountOut);
/// pay the relayer in the native token
payable(msg.sender).transfer(nativeRelayerFee);
2022-01-20 13:40:38 -08:00
// used in UI to tell user they're getting
// their desired token
emit SwapResult(
payload.recipientAddress,
uniPath[1],
msg.sender,
nativeAmountOut,
2022-01-21 10:37:14 -08:00
1
2022-01-20 13:40:38 -08:00
);
2022-01-20 08:52:35 -08:00
return amounts;
} catch {
// pay relayer in the feeToken since the swap failed
IERC20 feeToken = IERC20(FEE_TOKEN_ADDRESS);
feeToken.safeTransfer(msg.sender, payload.relayerFee);
2022-06-30 14:49:49 -07:00
// swap failed - return feeToken to recipient
feeToken.safeTransfer(
2022-01-20 13:40:38 -08:00
payload.recipientAddress,
payload.swapAmount - payload.relayerFee
2022-01-20 13:40:38 -08:00
);
// used in UI to tell user they're getting
2022-06-30 14:49:49 -07:00
// feeToken instead of their desired native asset
2022-01-20 13:40:38 -08:00
emit SwapResult(
payload.recipientAddress,
uniPath[0],
msg.sender,
payload.swapAmount - payload.relayerFee,
2022-01-21 10:37:14 -08:00
0
2022-01-20 13:40:38 -08:00
);
2022-01-20 08:52:35 -08:00
}
2022-01-11 08:29:49 -08:00
}
2022-06-28 16:03:50 -07:00
/// @dev Executes exactOut native asset swap and pays the relayer
function recvAndSwapExactNativeOut(
2022-01-11 08:29:49 -08:00
bytes calldata encodedVaa
2022-06-28 16:03:50 -07:00
) external returns (uint256 amountInUsed) {
// redeem and fetch parsed payload
2022-01-20 13:40:38 -08:00
SwapHelper.DecodedVaaParameters memory payload =
_getParsedPayload(
encodedVaa,
2022-06-30 14:49:49 -07:00
TypeExactOut
2022-01-20 13:40:38 -08:00
);
2022-01-11 08:29:49 -08:00
// create dynamic address array - uniswap won't take fixed size array
address[] memory uniPath = new address[](2);
uniPath[0] = payload.path[0];
uniPath[1] = payload.path[1];
2022-01-22 11:25:01 -08:00
2022-06-28 16:03:50 -07:00
// sanity check path
require(
2022-06-30 14:49:49 -07:00
uniPath[0]==FEE_TOKEN_ADDRESS,
"tokenIn must be feeToken"
2022-06-28 16:03:50 -07:00
);
require(
2022-06-30 14:49:49 -07:00
payload.path[1]==WRAPPED_NATIVE,
2022-06-28 16:03:50 -07:00
"tokenOut must be wrapped native asset"
);
// pay the relayer in feeToken so that user gets desired exact amount out
2022-06-30 14:49:49 -07:00
IERC20 feeToken = IERC20(FEE_TOKEN_ADDRESS);
2022-06-28 16:03:50 -07:00
feeToken.safeTransfer(msg.sender, payload.relayerFee);
uint256 maxAmountInLessFees = payload.swapAmount - payload.relayerFee;
// amountOut is the estimated swap amount for exact out methods
uint256 amountOut = payload.estimatedAmount;
2022-01-11 08:29:49 -08:00
// approve the router to spend tokens
2022-01-20 13:40:38 -08:00
TransferHelper.safeApprove(
uniPath[0],
2022-06-30 14:49:49 -07:00
address(SWAP_ROUTER),
2022-06-28 16:03:50 -07:00
maxAmountInLessFees
);
2022-01-11 08:29:49 -08:00
2022-06-28 16:03:50 -07:00
// try to perform the swap
2022-06-30 14:49:49 -07:00
try SWAP_ROUTER.swapTokensForExactTokens(
2022-06-28 16:03:50 -07:00
amountOut,
maxAmountInLessFees,
2022-01-11 08:29:49 -08:00
uniPath,
2022-06-28 16:03:50 -07:00
address(this),
2022-01-11 08:29:49 -08:00
payload.deadline
) returns (uint256[] memory amounts) {
2022-06-28 16:03:50 -07:00
// amountIn used is first element in array
amountInUsed = amounts[0];
2022-06-30 14:49:49 -07:00
// refund recipient with any feeToken not used in the swap
2022-06-28 16:03:50 -07:00
if (amountInUsed < maxAmountInLessFees) {
TransferHelper.safeApprove(
2022-06-30 14:49:49 -07:00
FEE_TOKEN_ADDRESS,
address(SWAP_ROUTER),
2022-06-28 16:03:50 -07:00
0
);
2022-06-30 14:49:49 -07:00
IERC20(FEE_TOKEN_ADDRESS).safeTransfer(
2022-06-28 16:03:50 -07:00
payload.recipientAddress,
maxAmountInLessFees - amountInUsed
);
}
// unwrap native and send to recipient
2022-06-30 14:49:49 -07:00
IWETH(WRAPPED_NATIVE).withdraw(amounts[1]);
2022-06-28 16:03:50 -07:00
payable(payload.recipientAddress).transfer(amounts[1]);
2022-01-20 13:40:38 -08:00
// used in UI to tell user they're getting
2022-06-28 16:03:50 -07:00
// their desired native asset
2022-01-20 13:40:38 -08:00
emit SwapResult(
payload.recipientAddress,
uniPath[1],
msg.sender,
2022-01-21 10:37:14 -08:00
amounts[1],
1
2022-01-20 13:40:38 -08:00
);
2022-06-28 16:03:50 -07:00
return amountInUsed;
2022-01-11 08:29:49 -08:00
} catch {
2022-06-30 14:49:49 -07:00
// swap failed - return feeToken to recipient
IERC20(FEE_TOKEN_ADDRESS).safeTransfer(
2022-01-20 13:40:38 -08:00
payload.recipientAddress,
2022-06-28 16:03:50 -07:00
maxAmountInLessFees
2022-01-20 13:40:38 -08:00
);
2022-01-22 11:25:01 -08:00
2022-01-20 13:40:38 -08:00
// used in UI to tell user they're getting
2022-06-30 14:49:49 -07:00
// feeToken instead of their desired native asset
2022-01-20 13:40:38 -08:00
emit SwapResult(
payload.recipientAddress,
uniPath[0],
msg.sender,
2022-06-28 16:03:50 -07:00
maxAmountInLessFees,
2022-01-21 10:37:14 -08:00
0
2022-01-20 13:40:38 -08:00
);
2022-06-28 16:03:50 -07:00
}
2022-01-11 08:29:49 -08:00
}
2022-06-28 16:03:50 -07:00
2022-06-30 14:49:49 -07:00
/// @dev Executes exactIn native asset swap
2022-01-11 08:29:49 -08:00
function _swapExactInBeforeTransfer(
uint256 amountIn,
uint256 amountOutMinimum,
address contractCaller,
address[] calldata path,
uint256 deadline
) internal returns (uint256 amountOut) {
// approve the router to spend tokens
2022-01-21 10:37:14 -08:00
TransferHelper.safeApprove(
path[0],
2022-06-30 14:49:49 -07:00
address(SWAP_ROUTER),
2022-01-21 10:37:14 -08:00
amountIn
);
2022-01-11 08:29:49 -08:00
// perform the swap
2022-06-30 14:49:49 -07:00
uint256[] memory amounts = SWAP_ROUTER.swapExactTokensForTokens(
2022-01-11 08:29:49 -08:00
amountIn,
amountOutMinimum,
path,
address(this),
deadline
);
amountOut = amounts[1];
}
2022-01-22 11:25:01 -08:00
/// @dev Calls _swapExactInBeforeTransfer and encodes custom payload with
/// instructions for executing native asset swaps on the destination chain
2022-01-20 13:40:38 -08:00
function swapExactNativeInAndTransfer(
2022-01-20 08:52:35 -08:00
SwapHelper.ExactInParameters calldata swapParams,
address[] calldata path,
uint256 relayerFee,
uint16 targetChainId,
bytes32 targetContractAddress,
uint32 nonce
) external payable {
2022-01-21 10:37:14 -08:00
require(
swapParams.amountOutMinimum > relayerFee,
"insufficient amountOutMinimum to pay relayer"
);
require(
2022-06-30 14:49:49 -07:00
path[0]==WRAPPED_NATIVE,
2022-01-21 10:37:14 -08:00
"tokenIn must be wrapped native asset for first swap"
);
require(
2022-06-30 14:49:49 -07:00
path[1]==FEE_TOKEN_ADDRESS,
"tokenOut must be feeToken for first swap"
2022-01-21 10:37:14 -08:00
);
require(msg.value > 0, "must pass non 0 native asset amount");
2022-01-20 08:52:35 -08:00
2022-01-21 10:37:14 -08:00
// wrap native asset
2022-06-30 14:49:49 -07:00
IWETH(WRAPPED_NATIVE).deposit{
2022-01-20 08:52:35 -08:00
value : msg.value
}();
// peform the first swap
uint256 amountOut = _swapExactInBeforeTransfer(
2022-01-22 11:25:01 -08:00
msg.value,
2022-01-20 08:52:35 -08:00
swapParams.amountOutMinimum,
msg.sender,
path[0:2],
swapParams.deadline
);
2022-06-28 16:03:50 -07:00
// create payload with target swap instructions
bytes memory payload = abi.encodePacked(
swapParams.targetAmountOutMinimum,
swapParams.targetChainRecipient,
path[2],
path[3],
swapParams.deadline,
swapParams.poolFee,
2022-06-30 14:49:49 -07:00
TypeExactIn,
2022-06-28 16:03:50 -07:00
relayerFee
);
2022-06-30 14:49:49 -07:00
// approve token bridge to spend feeTokens
2022-01-21 10:37:14 -08:00
TransferHelper.safeApprove(
2022-06-30 14:49:49 -07:00
FEE_TOKEN_ADDRESS,
TOKEN_BRIDGE_ADDRESS,
2022-01-21 10:37:14 -08:00
amountOut
);
2022-01-11 08:29:49 -08:00
// send transfer with payload to the TokenBridge
2022-06-30 14:49:49 -07:00
TokenBridge(TOKEN_BRIDGE_ADDRESS).transferTokensWithPayload(
FEE_TOKEN_ADDRESS,
2022-01-21 10:37:14 -08:00
amountOut,
targetChainId,
2022-06-28 16:03:50 -07:00
targetContractAddress,
2022-01-21 10:37:14 -08:00
nonce,
payload
2022-01-11 08:29:49 -08:00
);
}
2022-06-28 16:03:50 -07:00
2022-06-30 14:49:49 -07:00
/// @dev Executes exactOut native asset swaps
2022-01-11 08:29:49 -08:00
function _swapExactOutBeforeTransfer(
uint256 amountOut,
uint256 amountInMaximum,
address contractCaller,
address[] calldata path,
2022-06-30 14:49:49 -07:00
uint256 deadline
2022-01-22 11:25:01 -08:00
) internal {
2022-01-11 08:29:49 -08:00
// approve the router to spend tokens
2022-01-21 10:37:14 -08:00
TransferHelper.safeApprove(
path[0],
2022-06-30 14:49:49 -07:00
address(SWAP_ROUTER),
2022-01-21 10:37:14 -08:00
amountInMaximum
);
2022-01-11 08:29:49 -08:00
// perform the swap
2022-06-30 14:49:49 -07:00
uint256[] memory amounts = SWAP_ROUTER.swapTokensForExactTokens(
2022-01-11 08:29:49 -08:00
amountOut,
amountInMaximum,
path,
address(this),
deadline
);
// amountIn used is first element in array
uint256 amountInUsed = amounts[0];
// refund contractCaller with any amountIn that wasn't spent
if (amountInUsed < amountInMaximum) {
2022-06-30 14:49:49 -07:00
// unwrap remaining native asset and send to contractCaller
TransferHelper.safeApprove(path[0], address(SWAP_ROUTER), 0);
IWETH(WRAPPED_NATIVE).withdraw(
amountInMaximum - amountInUsed
);
payable(contractCaller).transfer(
amountInMaximum - amountInUsed
);
2022-01-11 08:29:49 -08:00
}
}
2022-01-22 11:25:01 -08:00
/// @dev Calls _swapExactOutBeforeTransfer and encodes custom payload with
/// instructions for executing native asset swaps on the destination chain
2022-01-20 13:40:38 -08:00
function swapExactNativeOutAndTransfer(
2022-01-20 08:52:35 -08:00
SwapHelper.ExactOutParameters calldata swapParams,
address[] calldata path,
uint256 relayerFee,
uint16 targetChainId,
bytes32 targetContractAddress,
uint32 nonce
) external payable {
2022-01-21 10:37:14 -08:00
require(
swapParams.amountOut > relayerFee,
"insufficient amountOut to pay relayer"
);
require(
2022-06-30 14:49:49 -07:00
path[0]==WRAPPED_NATIVE,
2022-01-21 10:37:14 -08:00
"tokenIn must be wrapped native asset for first swap"
);
require(
2022-06-30 14:49:49 -07:00
path[1]==FEE_TOKEN_ADDRESS,
"tokenOut must be feeToken for first swap"
2022-01-21 10:37:14 -08:00
);
require(msg.value > 0, "must pass non 0 native asset amount");
2022-01-20 08:52:35 -08:00
2022-01-21 10:37:14 -08:00
// wrap native asset
2022-06-30 14:49:49 -07:00
IWETH(WRAPPED_NATIVE).deposit{
2022-01-20 08:52:35 -08:00
value : msg.value
}();
// peform the first swap
2022-01-20 13:40:38 -08:00
_swapExactOutBeforeTransfer(
2022-01-20 08:52:35 -08:00
swapParams.amountOut,
2022-01-22 11:25:01 -08:00
msg.value,
2022-01-20 08:52:35 -08:00
msg.sender,
path[0:2],
2022-06-30 14:49:49 -07:00
swapParams.deadline
2022-01-20 08:52:35 -08:00
);
2022-06-28 16:03:50 -07:00
// create payload with target swap instructions
bytes memory payload = abi.encodePacked(
swapParams.targetAmountOut,
swapParams.targetChainRecipient,
path[2],
path[3],
swapParams.deadline,
swapParams.poolFee,
2022-06-30 14:49:49 -07:00
TypeExactOut,
2022-06-28 16:03:50 -07:00
relayerFee
);
2022-01-20 08:52:35 -08:00
2022-06-30 14:49:49 -07:00
// approve token bridge to spend feeTokens
2022-01-21 10:37:14 -08:00
TransferHelper.safeApprove(
2022-06-30 14:49:49 -07:00
FEE_TOKEN_ADDRESS,
TOKEN_BRIDGE_ADDRESS,
2022-01-21 10:37:14 -08:00
swapParams.amountOut
);
2022-01-20 08:52:35 -08:00
// send transfer with payload to the TokenBridge
2022-06-30 14:49:49 -07:00
TokenBridge(TOKEN_BRIDGE_ADDRESS).transferTokensWithPayload(
FEE_TOKEN_ADDRESS,
2022-01-21 10:37:14 -08:00
swapParams.amountOut,
targetChainId,
targetContractAddress,
nonce,
payload
2022-01-20 08:52:35 -08:00
);
}
2022-01-22 11:25:01 -08:00
// necessary for receiving native assets
2022-01-20 08:52:35 -08:00
receive() external payable {}
2022-01-11 08:29:49 -08:00
}