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

598 lines
21 KiB
Solidity
Raw Normal View History

2022-01-11 08:29:49 -08:00
pragma solidity ^0.7.6;
pragma abicoder v2;
import './IWormhole.sol';
import './SwapHelper.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
interface TokenBridge {
function transferTokensWithPayload(
address token,
uint256 amount,
uint16 recipientChain,
bytes32 recipient,
uint256 arbiterFee,
uint32 nonce,
bytes memory payload
) external payable returns (uint64);
2022-01-20 13:40:38 -08:00
function completeTransferWithPayload(
bytes memory encodedVm
) external returns (IWormhole.VM memory);
2022-01-11 08:29:49 -08:00
}
2022-01-20 13:40:38 -08:00
2022-01-20 08:52:35 -08:00
interface IWMATIC is IERC20 {
function deposit() external payable;
function withdraw(uint amount) external;
}
2022-01-11 08:29:49 -08:00
2022-01-20 13:40:38 -08:00
/// @title A cross-chain UniswapV2 example
2022-01-11 08:29:49 -08:00
contract CrossChainSwapV2 {
using SafeERC20 for IERC20;
using BytesLib for bytes;
uint8 public immutable typeExactIn = 1;
uint8 public immutable typeExactOut = 2;
2022-01-20 08:52:35 -08:00
uint8 public immutable typeNativeSwap = 1;
uint8 public immutable typeTokenSwap = 2;
uint16 public immutable expectedVaaLength = 262;
2022-01-11 08:29:49 -08:00
IUniswapV2Router02 public immutable swapRouter;
address public immutable feeTokenAddress;
address public immutable tokenBridgeAddress;
2022-01-20 08:52:35 -08:00
address public immutable wrappedMatic;
constructor(
address _swapRouterAddress,
address _feeTokenAddress,
address _tokenBridgeAddress,
address _wrappedMaticAddress
) {
2022-01-11 08:29:49 -08:00
swapRouter = IUniswapV2Router02(_swapRouterAddress);
feeTokenAddress = _feeTokenAddress;
tokenBridgeAddress = _tokenBridgeAddress;
2022-01-20 08:52:35 -08:00
wrappedMatic = _wrappedMaticAddress;
}
2022-01-20 13:40:38 -08:00
event SwapResult(
address indexed _recipient,
address indexed _tokenOut,
address indexed _from,
uint256 _amountOut
);
function _getParsedPayload(
bytes calldata encodedVaa,
uint8 swapFunctionType,
uint8 swapCurrencyType
) private returns (SwapHelper.DecodedVaaParameters memory payload) {
2022-01-20 08:52:35 -08:00
// complete the transfer on the token bridge
IWormhole.VM memory vm = TokenBridge(tokenBridgeAddress).completeTransferWithPayload(encodedVaa);
2022-01-20 13:40:38 -08:00
require(
vm.payload.length==expectedVaaLength,
"VAA has the wrong number of bytes"
);
2022-01-20 08:52:35 -08:00
// parse the payload
2022-01-20 13:40:38 -08:00
payload = SwapHelper.decodeVaaPayload(vm);
// sanity check payload parameters
require(
payload.swapFunctionType==swapFunctionType,
"swap must be type ExactIn"
);
require(
payload.swapCurrencyType==swapCurrencyType,
"incorrect swapCurrencyType in payload"
);
}
//function swapExactNativeInFromV3(
function recvAndSwapExactNativeIn(
bytes calldata encodedVaa
) external payable returns (uint256[] memory amounts) {
// redeem and fetch parsed payload
SwapHelper.DecodedVaaParameters memory payload =
_getParsedPayload(
encodedVaa,
typeExactIn,
typeNativeSwap
);
2022-01-20 08:52:35 -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];
require(uniPath[0]==feeTokenAddress, "tokenIn must be UST");
require(uniPath[1]==wrappedMatic, "tokenOut must be wMATIC");
// pay relayer before attempting to do the swap
// reflect payment in second swap amount
IERC20 feeToken = IERC20(feeTokenAddress);
feeToken.safeTransfer(msg.sender, payload.relayerFee);
2022-01-20 13:40:38 -08:00
uint256 swapAmountLessFees = (
payload.swapAmount - payload.relayerFee
);
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],
address(swapRouter),
swapAmountLessFees
);
2022-01-20 08:52:35 -08:00
// try to perform the swap
try swapRouter.swapExactTokensForTokens(
swapAmountLessFees,
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) {
2022-01-20 13:40:38 -08:00
// unwrap native and send to recipient
2022-01-20 08:52:35 -08:00
IWMATIC(wrappedMatic).withdraw(amounts[1]);
payable(payload.recipientAddress).transfer(amounts[1]);
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,
amounts[1]
);
2022-01-20 08:52:35 -08:00
return amounts;
} catch {
// swap failed - return UST to recipient
2022-01-20 13:40:38 -08:00
feeToken.safeTransfer(
payload.recipientAddress,
swapAmountLessFees
);
// used in UI to tell user they're getting
// UST instead of their desired native asset
emit SwapResult(
payload.recipientAddress,
uniPath[0],
msg.sender,
swapAmountLessFees
);
2022-01-20 08:52:35 -08:00
}
2022-01-11 08:29:49 -08:00
}
2022-01-20 13:40:38 -08:00
//function swapExactInFromV3(
function recvAndSwapExactIn(
2022-01-11 08:29:49 -08:00
bytes calldata encodedVaa
) external returns (uint256[] memory amounts) {
2022-01-20 13:40:38 -08:00
// redeem and fetch the parsed payload
SwapHelper.DecodedVaaParameters memory payload =
_getParsedPayload(
encodedVaa,
typeExactIn,
typeTokenSwap
);
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];
require(uniPath[0]==feeTokenAddress, "tokenIn must be UST");
// pay relayer before attempting to do the swap
// reflect payment in second swap amount
IERC20 feeToken = IERC20(feeTokenAddress);
feeToken.safeTransfer(msg.sender, payload.relayerFee);
2022-01-20 13:40:38 -08:00
uint256 swapAmountLessFees = (
payload.swapAmount - payload.relayerFee
);
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],
address(swapRouter),
swapAmountLessFees
);
2022-01-11 08:29:49 -08:00
// try to perform the swap
try swapRouter.swapExactTokensForTokens(
swapAmountLessFees,
payload.estimatedAmount,
uniPath,
payload.recipientAddress,
payload.deadline
) returns (uint256[] memory amounts) {
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,
amounts[1]
);
2022-01-11 08:29:49 -08:00
return amounts;
} catch {
// swap failed - return UST to recipient
2022-01-20 13:40:38 -08:00
feeToken.safeTransfer(
payload.recipientAddress,
swapAmountLessFees
);
// used in UI to tell user they're getting
// UST instead of their desired token
emit SwapResult(
payload.recipientAddress,
uniPath[0],
msg.sender,
swapAmountLessFees
);
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) {
// path[0] is the tokenIn in
IERC20 token = IERC20(path[0]);
token.safeTransferFrom(contractCaller, address(this), amountIn);
// approve the router to spend tokens
TransferHelper.safeApprove(path[0], address(swapRouter), amountIn);
// perform the swap
uint256[] memory amounts = swapRouter.swapExactTokensForTokens(
amountIn,
amountOutMinimum,
path,
address(this),
deadline
);
amountOut = amounts[1];
}
2022-01-20 13:40:38 -08:00
//function swapExactInToV3(
function swapExactInAndTransfer(
2022-01-11 08:29:49 -08:00
SwapHelper.ExactInParameters calldata swapParams,
address[] calldata path,
uint256 relayerFee,
uint16 targetChainId,
bytes32 targetContractAddress,
uint32 nonce
) external {
require(swapParams.amountOutMinimum > relayerFee, "insufficient amountOutMinimum to pay relayer");
require(path[1]==feeTokenAddress, "tokenOut must be UST for first swap");
// peform the first swap
uint256 amountOut = _swapExactInBeforeTransfer(
swapParams.amountIn,
swapParams.amountOutMinimum,
msg.sender,
path[0:2],
swapParams.deadline
);
// encode payload for second swap
bytes memory payload = abi.encodePacked(
swapParams.targetAmountOutMinimum,
swapParams.targetChainRecipient,
path[2],
path[3],
swapParams.deadline,
swapParams.poolFee,
2022-01-20 08:52:35 -08:00
typeExactIn,
typeTokenSwap
);
// approve token bridge to spend feeTokens (UST)
TransferHelper.safeApprove(feeTokenAddress, tokenBridgeAddress, amountOut);
// send transfer with payload to the TokenBridge
TokenBridge(tokenBridgeAddress).transferTokensWithPayload(
feeTokenAddress, amountOut, targetChainId, targetContractAddress, relayerFee, nonce, payload
);
}
2022-01-20 13:40:38 -08:00
//function swapExactNativeInToV3(
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 {
require(swapParams.amountOutMinimum > relayerFee, "insufficient amountOutMinimum to pay relayer");
require(path[0]==wrappedMatic, "tokenIn must be wMATIC for first swap");
require(path[1]==feeTokenAddress, "tokenOut must be UST for first swap");
require(msg.value > 0, "must pass non 0 MATIC amount");
// wrap MATIC
IWMATIC(wrappedMatic).deposit{
value : msg.value
}();
// peform the first swap
uint256 amountOut = _swapExactInBeforeTransfer(
2022-01-20 13:40:38 -08:00
msg.value, // MATIC value sent in transaction is the amountIn
2022-01-20 08:52:35 -08:00
swapParams.amountOutMinimum,
msg.sender,
path[0:2],
swapParams.deadline
);
// encode payload for second swap
bytes memory payload = abi.encodePacked(
swapParams.targetAmountOutMinimum,
swapParams.targetChainRecipient,
path[2],
path[3],
swapParams.deadline,
swapParams.poolFee,
typeExactIn,
typeNativeSwap
2022-01-11 08:29:49 -08:00
);
// approve token bridge to spend feeTokens (UST)
TransferHelper.safeApprove(feeTokenAddress, tokenBridgeAddress, amountOut);
// send transfer with payload to the TokenBridge
TokenBridge(tokenBridgeAddress).transferTokensWithPayload(
feeTokenAddress, amountOut, targetChainId, targetContractAddress, relayerFee, nonce, payload
);
}
2022-01-20 13:40:38 -08:00
//function swapExactNativeOutFromV3(
function recvAndSwapExactNativeOut(
2022-01-20 08:52:35 -08:00
bytes calldata encodedVaa
) external returns (uint256 amountInUsed) {
2022-01-20 13:40:38 -08:00
// redeem and fetch parsed payload
SwapHelper.DecodedVaaParameters memory payload =
_getParsedPayload(
encodedVaa,
typeExactOut,
typeNativeSwap
);
2022-01-20 08:52:35 -08:00
// amountOut is the estimated swap amount for exact out methods
uint256 amountOut = payload.estimatedAmount;
// 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];
require(uniPath[0]==feeTokenAddress, "tokenIn must be UST");
require(payload.path[1]==wrappedMatic, "tokenOut must be wMATIC");
// pay relayer before attempting to do the swap
// reflect payment in second swap amount
IERC20 feeToken = IERC20(feeTokenAddress);
feeToken.safeTransfer(msg.sender, payload.relayerFee);
uint256 maxAmountInLessFees = payload.swapAmount - payload.relayerFee;
// approve the router to spend tokens
TransferHelper.safeApprove(uniPath[0], address(swapRouter), maxAmountInLessFees);
// try to perform the swap
try swapRouter.swapTokensForExactTokens(
amountOut,
maxAmountInLessFees,
uniPath,
address(this), // send wMATIC to this contract, then unwrap and send to recipient
payload.deadline
) returns (uint256[] memory amounts) {
// amountIn used is first element in array
amountInUsed = amounts[0];
// refund recipient with any UST not used in the swap
if (amountInUsed < maxAmountInLessFees) {
TransferHelper.safeApprove(feeTokenAddress, address(swapRouter), 0);
feeToken.safeTransfer(payload.recipientAddress, maxAmountInLessFees - amountInUsed);
}
// unwrap the wMATIC this contract received from the swap and send to the recipient
IWMATIC(wrappedMatic).withdraw(amounts[1]);
payable(payload.recipientAddress).transfer(amounts[1]);
2022-01-20 13:40:38 -08:00
emit SwapResult(payload.recipientAddress, uniPath[1], msg.sender, amounts[1]);
2022-01-20 08:52:35 -08:00
return amountInUsed;
} catch {
feeToken.safeTransfer(payload.recipientAddress, maxAmountInLessFees);
2022-01-20 13:40:38 -08:00
emit SwapResult(payload.recipientAddress, uniPath[0], msg.sender, maxAmountInLessFees);
2022-01-20 08:52:35 -08:00
}
}
2022-01-20 13:40:38 -08:00
//function swapExactOutFromV3(
function recvAndSwapExactOut(
2022-01-11 08:29:49 -08:00
bytes calldata encodedVaa
) external returns (uint256 amountInUsed) {
2022-01-20 13:40:38 -08:00
// redeem and fetch parsed payload
SwapHelper.DecodedVaaParameters memory payload =
_getParsedPayload(
encodedVaa,
typeExactOut,
typeTokenSwap
);
2022-01-11 08:29:49 -08:00
// amountOut is the estimated swap amount for exact out methods
uint256 amountOut = payload.estimatedAmount;
// 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];
require(uniPath[0]==feeTokenAddress, "tokenIn must be UST");
// pay relayer before attempting to do the swap
// reflect payment in second swap amount
IERC20 feeToken = IERC20(feeTokenAddress);
feeToken.safeTransfer(msg.sender, payload.relayerFee);
uint256 maxAmountInLessFees = payload.swapAmount - payload.relayerFee;
// approve the router to spend tokens
TransferHelper.safeApprove(uniPath[0], address(swapRouter), maxAmountInLessFees);
// try to perform the swap
try swapRouter.swapTokensForExactTokens(
amountOut,
maxAmountInLessFees,
uniPath,
payload.recipientAddress,
payload.deadline
) returns (uint256[] memory amounts) {
// amountIn used is first element in array
amountInUsed = amounts[0];
// refund recipient with any UST not used in the swap
if (amountInUsed < maxAmountInLessFees) {
TransferHelper.safeApprove(feeTokenAddress, address(swapRouter), 0);
feeToken.safeTransfer(payload.recipientAddress, maxAmountInLessFees - amountInUsed);
}
2022-01-20 13:40:38 -08:00
emit SwapResult(payload.recipientAddress, uniPath[1], msg.sender, amounts[1]);
2022-01-11 08:29:49 -08:00
return amountInUsed;
} catch {
feeToken.safeTransfer(payload.recipientAddress, maxAmountInLessFees);
2022-01-20 13:40:38 -08:00
emit SwapResult(payload.recipientAddress, uniPath[0], msg.sender, maxAmountInLessFees);
2022-01-11 08:29:49 -08:00
}
}
function _swapExactOutBeforeTransfer(
uint256 amountOut,
uint256 amountInMaximum,
address contractCaller,
address[] calldata path,
2022-01-20 13:40:38 -08:00
uint256 deadline,
uint8 swapType
) public payable {
2022-01-11 08:29:49 -08:00
// path[0] is the tokenIn
IERC20 token = IERC20(path[0]);
token.safeTransferFrom(contractCaller, address(this), amountInMaximum);
// approve the router to spend tokens
TransferHelper.safeApprove(path[0], address(swapRouter), amountInMaximum);
// perform the swap
uint256[] memory amounts = swapRouter.swapTokensForExactTokens(
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) {
TransferHelper.safeApprove(path[0], address(swapRouter), 0);
2022-01-20 13:40:38 -08:00
if (swapType == typeTokenSwap) {
// send remaining tokens to contractCaller
token.safeTransfer(contractCaller, amountInMaximum - amountInUsed);
} else {
// unwrap remaining matic and send to contractCaller
IWMATIC(wrappedMatic).withdraw(amountInMaximum - amountInUsed);
payable(contractCaller).transfer(amountInMaximum - amountInUsed);
}
2022-01-11 08:29:49 -08:00
}
}
2022-01-20 13:40:38 -08:00
//function swapExactOutToV3(
function swapExactOutAndTransfer(
2022-01-11 08:29:49 -08:00
SwapHelper.ExactOutParameters calldata swapParams,
address[] calldata path,
uint256 relayerFee,
uint16 targetChainId,
bytes32 targetContractAddress,
uint32 nonce
) external {
require(swapParams.amountOut > relayerFee, "insufficient amountOut to pay relayer");
require(path[1]==feeTokenAddress, "tokenOut must be UST for first swap");
// peform the first swap
_swapExactOutBeforeTransfer(
swapParams.amountOut,
swapParams.amountInMaximum,
msg.sender,
path[0:2],
2022-01-20 13:40:38 -08:00
swapParams.deadline,
typeTokenSwap
2022-01-11 08:29:49 -08:00
);
// encode payload for second swap
bytes memory payload = abi.encodePacked(
swapParams.targetAmountOut,
swapParams.targetChainRecipient,
path[2],
path[3],
swapParams.deadline,
swapParams.poolFee,
2022-01-20 08:52:35 -08:00
typeExactOut,
typeTokenSwap
2022-01-11 08:29:49 -08:00
);
// approve token bridge to spend feeTokens (UST)
TransferHelper.safeApprove(feeTokenAddress, tokenBridgeAddress, swapParams.amountOut);
// send transfer with payload to the TokenBridge
TokenBridge(tokenBridgeAddress).transferTokensWithPayload(
feeTokenAddress, swapParams.amountOut, targetChainId, targetContractAddress, relayerFee, nonce, payload
);
2022-01-20 13:40:38 -08:00
}
2022-01-20 08:52:35 -08:00
2022-01-20 13:40:38 -08:00
//function swapExactNativeOutToV3(
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 {
require(swapParams.amountOut > relayerFee, "insufficient amountOut to pay relayer");
require(path[0]==wrappedMatic, "tokenIn must be wMATIC for first swap");
require(path[1]==feeTokenAddress, "tokenOut must be UST for first swap");
require(msg.value > 0, "must pass non 0 MATIC amount");
// wrap MATIC
IWMATIC(wrappedMatic).deposit{
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,
msg.value, // MATIC value sent in transaction is the maximumAmountIn
msg.sender,
path[0:2],
2022-01-20 13:40:38 -08:00
swapParams.deadline,
typeNativeSwap
2022-01-20 08:52:35 -08:00
);
// encode payload for second swap
bytes memory payload = abi.encodePacked(
swapParams.targetAmountOut,
swapParams.targetChainRecipient,
path[2],
path[3],
swapParams.deadline,
swapParams.poolFee,
typeExactOut,
typeNativeSwap
);
// approve token bridge to spend feeTokens (UST)
TransferHelper.safeApprove(feeTokenAddress, tokenBridgeAddress, swapParams.amountOut);
// send transfer with payload to the TokenBridge
TokenBridge(tokenBridgeAddress).transferTokensWithPayload(
feeTokenAddress, swapParams.amountOut, targetChainId, targetContractAddress, relayerFee, nonce, payload
);
}
// we need to accept ETH sends to unwrap WETH
receive() external payable {}
2022-01-11 08:29:49 -08:00
}