Merge pull request #3 from certusone/evm-payload3-update

Evm payload3 update
This commit is contained in:
Reptile 2022-08-24 07:54:39 -05:00 committed by GitHub
commit 37649e5eaa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 623 additions and 1520 deletions

View File

@ -1,7 +1,6 @@
const HDWalletProvider = require('@truffle/hdwallet-provider'); const HDWalletProvider = require("@truffle/hdwallet-provider");
require("dotenv").config({ path: ".env" });
require('dotenv').config({path:'.env'});
/** /**
* Use this file to configure your truffle project. It's seeded with some * Use this file to configure your truffle project. It's seeded with some
@ -29,9 +28,9 @@ require('dotenv').config({path:'.env'});
// const mnemonic = fs.readFileSync('.secret').toString().trim(); // const mnemonic = fs.readFileSync('.secret').toString().trim();
module.exports = { module.exports = {
contracts_directory: './contracts', contracts_directory: "./contracts",
contracts_build_directory: './build/contracts', contracts_build_directory: "./build/contracts",
migrations_directory: './migrations/polygon', migrations_directory: "./migrations/polygon",
/** /**
* Networks define how you connect to your ethereum client and let you set the * Networks define how you connect to your ethereum client and let you set the
* defaults web3 uses to send transactions. If you don't specify one truffle * defaults web3 uses to send transactions. If you don't specify one truffle
@ -42,18 +41,18 @@ module.exports = {
* $ truffle test --network <network-name> * $ truffle test --network <network-name>
*/ */
networks: { networks: {
// Useful for testing. The `development` name is special - truffle uses it by default // Useful for testing. The `development` name is special - truffle uses it by default
// if it's defined here and no other network is specified at the command line. // if it's defined here and no other network is specified at the command line.
// You should run a client (like ganache-cli, geth or parity) in a separate terminal // You should run a client (like ganache-cli, geth or parity) in a separate terminal
// tab if you use this network and you must also set the `host`, `port` and `network_id` // tab if you use this network and you must also set the `host`, `port` and `network_id`
// options below to some value. // options below to some value.
// //
development: { development: {
host: '127.0.0.1', // Localhost (default: none) host: "127.0.0.1", // Localhost (default: none)
port: 8545, // Standard Ethereum port (default: none) port: 8545, // Standard Ethereum port (default: none)
network_id: '*', // Any network (default: none) network_id: "*", // Any network (default: none)
}, },
// Another network with more advanced options... // Another network with more advanced options...
// advanced: { // advanced: {
// port: 8777, // Custom port // port: 8777, // Custom port
@ -65,14 +64,15 @@ module.exports = {
// }, // },
// Useful for deploying to a public network. // Useful for deploying to a public network.
// NB: It's important to wrap the provider as a function. // NB: It's important to wrap the provider as a function.
mumbai: { mumbai: {
provider: () => new HDWalletProvider(process.env.ETH_PRIVATE_KEY, process.env.MUMBAI_PROVIDER), provider: () => new HDWalletProvider(process.env.ETH_PRIVATE_KEY, process.env.MUMBAI_PROVIDER),
network_id: 80001, network_id: 80001,
//gas: 4465030, gasPrice: 80000000000,
//confirmations: 2, // # of confs to wait between deployments. (default: 0) gas: 7000000,
//timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50) //confirmations: 2, // # of confs to wait between deployments. (default: 0)
//skipDryRun: true // Skip dry run before migrations? (default: false for public nets ) timeoutBlocks: 50, // # of blocks before a deployment times out (minimum/default: 50)
}, skipDryRun: true, // Skip dry run before migrations? (default: false for public nets )
},
// Useful for private networks // Useful for private networks
// private: { // private: {
// provider: () => new HDWalletProvider(mnemonic, `https://network.io`), // provider: () => new HDWalletProvider(mnemonic, `https://network.io`),
@ -89,16 +89,16 @@ module.exports = {
// Configure your compilers // Configure your compilers
compilers: { compilers: {
solc: { solc: {
version: '0.7.6', // Fetch exact version from solc-bin (default: truffle's version) version: "0.7.6", // Fetch exact version from solc-bin (default: truffle's version)
// docker: true, // Use '0.5.1' you've installed locally with docker (default: false) // docker: true, // Use '0.5.1' you've installed locally with docker (default: false)
// settings: { // See the solidity docs for advice about optimization and evmVersion // settings: { // See the solidity docs for advice about optimization and evmVersion
optimizer: { optimizer: {
enabled: false, enabled: false,
runs: 200 runs: 200,
}, },
// evmVersion: 'byzantium' // evmVersion: 'byzantium'
// } // }
} },
}, },
// Truffle DB is currently disabled by default; to enable it, change enabled: // Truffle DB is currently disabled by default; to enable it, change enabled:
@ -108,17 +108,17 @@ module.exports = {
// NOTE: It is not possible to migrate your contracts to truffle DB and you should // NOTE: It is not possible to migrate your contracts to truffle DB and you should
// make a backup of your artifacts to a safe location before enabling this feature. // make a backup of your artifacts to a safe location before enabling this feature.
// //
// After you backed up your artifacts you can utilize db by running migrate as follows: // After you backed up your artifacts you can utilize db by running migrate as follows:
// $ truffle migrate --reset --compile-all // $ truffle migrate --reset --compile-all
// //
// db: { // db: {
// enabled: false, // enabled: false,
// host: '127.0.0.1', // host: '127.0.0.1',
// adapter: { // adapter: {
// name: 'sqlite', // name: 'sqlite',
// settings: { // settings: {
// directory: '.db' // directory: '.db'
// } // }
// } // }
// } // }
}; };

View File

@ -0,0 +1,123 @@
const HDWalletProvider = require("@truffle/hdwallet-provider");
require("dotenv").config({ path: ".env" });
/**
* Use this file to configure your truffle project. It's seeded with some
* common settings for different networks and features like migrations,
* compilation and testing. Uncomment the ones you need or modify
* them to suit your project as necessary.
*
* More information about configuration can be found at:
*
* trufflesuite.com/docs/advanced/configuration
*
* To deploy via Infura you'll need a wallet provider (like @truffle/hdwallet-provider)
* to sign your transactions before they're sent to a remote public node. Infura accounts
* are available for free at: infura.io/register.
*
* You'll also need a mnemonic - the twelve word phrase the wallet uses to generate
* public/private key pairs. If you're publishing your code to GitHub make sure you load this
* phrase from a file you've .gitignored so it doesn't accidentally become public.
*
*/
// const HDWalletProvider = require('@truffle/hdwallet-provider');
//
// const fs = require('fs');
// const mnemonic = fs.readFileSync('.secret').toString().trim();
module.exports = {
contracts_directory: "./contracts",
contracts_build_directory: "./build/contracts",
migrations_directory: "./migrations/tokens",
/**
* Networks define how you connect to your ethereum client and let you set the
* defaults web3 uses to send transactions. If you don't specify one truffle
* will spin up a development blockchain for you on port 9545 when you
* run `develop` or `test`. You can ask a truffle command to use a specific
* network from the command line, e.g
*
* $ truffle test --network <network-name>
*/
networks: {
// Useful for testing. The `development` name is special - truffle uses it by default
// if it's defined here and no other network is specified at the command line.
// You should run a client (like ganache-cli, geth or parity) in a separate terminal
// tab if you use this network and you must also set the `host`, `port` and `network_id`
// options below to some value.
//
development: {
host: "127.0.0.1", // Localhost (default: none)
port: 8545, // Standard Ethereum port (default: none)
network_id: "*", // Any network (default: none)
},
// Another network with more advanced options...
// advanced: {
// port: 8777, // Custom port
// network_id: 1342, // Custom network
// gas: 8500000, // Gas sent with each transaction (default: ~6700000)
// gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei)
// from: <address>, // Account to send txs from (default: accounts[0])
// websocket: true // Enable EventEmitter interface for web3 (default: false)
// },
// Useful for deploying to a public network.
// NB: It's important to wrap the provider as a function.
goerli: {
provider: () => new HDWalletProvider(process.env.ETH_PRIVATE_KEY, process.env.GOERLI_PROVIDER),
network_id: 5,
//gas: 4465030,
//confirmations: 2, // # of confs to wait between deployments. (default: 0)
//timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50)
//skipDryRun: true // Skip dry run before migrations? (default: false for public nets )
},
// Useful for private networks
// private: {
// provider: () => new HDWalletProvider(mnemonic, `https://network.io`),
// network_id: 2111, // This network is yours, in the cloud.
// production: true // Treats this network as if it was a public net. (default: false)
// }
},
// Set default mocha options here, use special reporters etc.
mocha: {
// timeout: 100000
},
// Configure your compilers
compilers: {
solc: {
version: "0.7.6", // Fetch exact version from solc-bin (default: truffle's version)
// docker: true, // Use '0.5.1' you've installed locally with docker (default: false)
// settings: { // See the solidity docs for advice about optimization and evmVersion
optimizer: {
enabled: false,
runs: 200,
},
// evmVersion: 'byzantium'
// }
},
},
// Truffle DB is currently disabled by default; to enable it, change enabled:
// false to enabled: true. The default storage location can also be
// overridden by specifying the adapter settings, as shown in the commented code below.
//
// NOTE: It is not possible to migrate your contracts to truffle DB and you should
// make a backup of your artifacts to a safe location before enabling this feature.
//
// After you backed up your artifacts you can utilize db by running migrate as follows:
// $ truffle migrate --reset --compile-all
//
// db: {
// enabled: false,
// host: '127.0.0.1',
// adapter: {
// name: 'sqlite',
// settings: {
// directory: '.db'
// }
// }
// }
};

View File

@ -3,8 +3,10 @@
pragma solidity ^0.7.6; pragma solidity ^0.7.6;
pragma abicoder v2; pragma abicoder v2;
import './IWormhole.sol'; import './shared/IWormhole.sol';
import './SwapHelper.sol'; import './shared/SwapHelper.sol';
import './shared/TokenBridge.sol';
import './shared/WETH.sol';
import 'solidity-bytes-utils/contracts/BytesLib.sol'; import 'solidity-bytes-utils/contracts/BytesLib.sol';
import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol'; import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
@ -12,45 +14,18 @@ import '@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol';
import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol"; import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
interface TokenBridge {
function transferTokensWithPayload(
address token,
uint256 amount,
uint16 recipientChain,
bytes32 recipient,
uint256 arbiterFee,
uint32 nonce,
bytes memory payload
) external payable returns (uint64);
function completeTransferWithPayload(
bytes memory encodedVm,
address feeRecipient
) external returns (bytes memory);
}
interface IWETH is IERC20 {
function deposit() external payable;
function withdraw(uint amount) external;
}
/// @title A cross-chain UniswapV2 example /// @title A cross-chain UniswapV2 example
/// @notice Swaps against UniswapV2 pools and uses Wormhole TokenBridge /// @notice Swaps against UniswapV2 pools and uses Wormhole TokenBridge
/// for cross-chain transfers /// for cross-chain transfers
contract CrossChainSwapV2 { contract CrossChainSwapV2 {
using SafeERC20 for IERC20; using SafeERC20 for IERC20;
using BytesLib for bytes; using BytesLib for bytes;
uint8 public immutable typeExactIn = 1; uint8 public immutable TypeExactIn = 1;
uint8 public immutable typeExactOut = 2; uint8 public immutable TypeExactOut = 2;
uint8 public immutable typeNativeSwap = 1; IUniswapV2Router02 public immutable SWAP_ROUTER;
uint8 public immutable typeTokenSwap = 2; address public immutable FEE_TOKEN_ADDRESS;
uint16 public immutable expectedVaaLength = 274; address public immutable TOKEN_BRIDGE_ADDRESS;
uint8 public immutable terraChainId = 3; address public immutable WRAPPED_NATIVE;
IUniswapV2Router02 public immutable swapRouter;
address public immutable feeTokenAddress;
address public immutable tokenBridgeAddress;
address public immutable wrappedNative;
constructor( constructor(
address _swapRouterAddress, address _swapRouterAddress,
@ -58,10 +33,10 @@ contract CrossChainSwapV2 {
address _tokenBridgeAddress, address _tokenBridgeAddress,
address _wrappedNativeAddress address _wrappedNativeAddress
) { ) {
swapRouter = IUniswapV2Router02(_swapRouterAddress); SWAP_ROUTER = IUniswapV2Router02(_swapRouterAddress);
feeTokenAddress = _feeTokenAddress; FEE_TOKEN_ADDRESS = _feeTokenAddress;
tokenBridgeAddress = _tokenBridgeAddress; TOKEN_BRIDGE_ADDRESS = _tokenBridgeAddress;
wrappedNative = _wrappedNativeAddress; WRAPPED_NATIVE = _wrappedNativeAddress;
} }
/// @dev Used to communicate information about executed swaps to UI/user /// @dev Used to communicate information about executed swaps to UI/user
@ -77,20 +52,12 @@ contract CrossChainSwapV2 {
/// instructions after redeeming the VAA from the TokenBridge /// instructions after redeeming the VAA from the TokenBridge
function _getParsedPayload( function _getParsedPayload(
bytes calldata encodedVaa, bytes calldata encodedVaa,
uint8 swapFunctionType, uint8 swapFunctionType
uint8 swapCurrencyType,
address feeRecipient
) private returns (SwapHelper.DecodedVaaParameters memory payload) { ) private returns (SwapHelper.DecodedVaaParameters memory payload) {
// complete the transfer on the token bridge // complete the transfer on the token bridge
bytes memory vmPayload = TokenBridge( bytes memory vmPayload = TokenBridge(
tokenBridgeAddress TOKEN_BRIDGE_ADDRESS
).completeTransferWithPayload(encodedVaa, feeRecipient); ).completeTransferWithPayload(encodedVaa);
// make sure payload is the right size
require(
vmPayload.length==expectedVaaLength,
"VAA has the wrong number of bytes"
);
// parse the payload // parse the payload
payload = SwapHelper.decodeVaaPayload(vmPayload); payload = SwapHelper.decodeVaaPayload(vmPayload);
@ -100,42 +67,19 @@ contract CrossChainSwapV2 {
payload.swapFunctionType==swapFunctionType, payload.swapFunctionType==swapFunctionType,
"incorrect swapFunctionType in payload" "incorrect swapFunctionType in payload"
); );
require(
payload.swapCurrencyType==swapCurrencyType,
"incorrect swapCurrencyType in payload"
);
} }
/// @dev Executes exactIn native asset swap and pays the relayer /// @dev Executes exactIn native asset swap and pays the relayer
function recvAndSwapExactNativeIn( function recvAndSwapExactNativeIn(
bytes calldata encodedVaa bytes calldata encodedVaa
) external payable returns (uint256[] memory amounts) { ) external payable returns (uint256[] memory amounts) {
// check token balance before redeeming the payload
(,bytes memory queriedBalanceBefore) = feeTokenAddress.staticcall(
abi.encodeWithSelector(IERC20.balanceOf.selector,
address(this)
));
uint256 balanceBefore = abi.decode(queriedBalanceBefore, (uint256));
// redeem and fetch parsed payload // redeem and fetch parsed payload
SwapHelper.DecodedVaaParameters memory payload = SwapHelper.DecodedVaaParameters memory payload =
_getParsedPayload( _getParsedPayload(
encodedVaa, encodedVaa,
typeExactIn, TypeExactIn
typeNativeSwap,
msg.sender // feeRecipient
); );
// query token balance after redeeming the payload
(,bytes memory queriedBalanceAfter) = feeTokenAddress.staticcall(
abi.encodeWithSelector(IERC20.balanceOf.selector,
address(this)
));
uint256 balanceAfter = abi.decode(queriedBalanceAfter, (uint256));
// the balance change is the swap amount (less relayer fees)
uint256 swapAmountLessFees = balanceAfter - balanceBefore;
// create dynamic address array // create dynamic address array
// uniswap won't take fixed size array // uniswap won't take fixed size array
address[] memory uniPath = new address[](2); address[] memory uniPath = new address[](2);
@ -144,32 +88,39 @@ contract CrossChainSwapV2 {
// sanity check path // sanity check path
require( require(
uniPath[0]==feeTokenAddress, uniPath[0]==FEE_TOKEN_ADDRESS,
"tokenIn must be UST" "tokenIn must be feeToken"
); );
require( require(
uniPath[1]==wrappedNative, uniPath[1]==WRAPPED_NATIVE,
"tokenOut must be wrapped native asset" "tokenOut must be wrapped native asset"
); );
// approve the router to spend tokens // approve the router to spend tokens
TransferHelper.safeApprove( TransferHelper.safeApprove(
uniPath[0], uniPath[0],
address(swapRouter), address(SWAP_ROUTER),
swapAmountLessFees payload.swapAmount
); );
// try to execute the swap // try to execute the swap
try swapRouter.swapExactTokensForTokens( try SWAP_ROUTER.swapExactTokensForTokens(
swapAmountLessFees, payload.swapAmount,
payload.estimatedAmount, payload.estimatedAmount,
uniPath, uniPath,
address(this), address(this),
payload.deadline payload.deadline
) returns (uint256[] memory amounts) { ) 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;
// unwrap native and send to recipient // unwrap native and send to recipient
IWETH(wrappedNative).withdraw(amounts[1]); IWETH(WRAPPED_NATIVE).withdraw(amounts[1]);
payable(payload.recipientAddress).transfer(amounts[1]); payable(payload.recipientAddress).transfer(nativeAmountOut);
/// pay the relayer in the native token
payable(msg.sender).transfer(nativeRelayerFee);
// used in UI to tell user they're getting // used in UI to tell user they're getting
// their desired token // their desired token
@ -177,84 +128,104 @@ contract CrossChainSwapV2 {
payload.recipientAddress, payload.recipientAddress,
uniPath[1], uniPath[1],
msg.sender, msg.sender,
amounts[1], nativeAmountOut,
1 1
); );
return amounts; return amounts;
} catch { } catch {
// swap failed - return UST to recipient // pay relayer in the feeToken since the swap failed
IERC20(feeTokenAddress).safeTransfer( IERC20 feeToken = IERC20(FEE_TOKEN_ADDRESS);
feeToken.safeTransfer(msg.sender, payload.relayerFee);
// swap failed - return feeToken to recipient
feeToken.safeTransfer(
payload.recipientAddress, payload.recipientAddress,
swapAmountLessFees payload.swapAmount - payload.relayerFee
); );
// used in UI to tell user they're getting // used in UI to tell user they're getting
// UST instead of their desired native asset // feeToken instead of their desired native asset
emit SwapResult( emit SwapResult(
payload.recipientAddress, payload.recipientAddress,
uniPath[0], uniPath[0],
msg.sender, msg.sender,
swapAmountLessFees, payload.swapAmount - payload.relayerFee,
0 0
); );
} }
} }
/// @dev Executes exactIn token swap and pays the relayer /// @dev Executes exactOut native asset swap and pays the relayer
function recvAndSwapExactIn( function recvAndSwapExactNativeOut(
bytes calldata encodedVaa bytes calldata encodedVaa
) external returns (uint256[] memory amounts) { ) external returns (uint256 amountInUsed) {
// check token balance before redeeming the payload // redeem and fetch parsed payload
(,bytes memory queriedBalanceBefore) = feeTokenAddress.staticcall(
abi.encodeWithSelector(IERC20.balanceOf.selector,
address(this)
));
uint256 balanceBefore = abi.decode(queriedBalanceBefore, (uint256));
// redeem and fetch the parsed payload
SwapHelper.DecodedVaaParameters memory payload = SwapHelper.DecodedVaaParameters memory payload =
_getParsedPayload( _getParsedPayload(
encodedVaa, encodedVaa,
typeExactIn, TypeExactOut
typeTokenSwap,
msg.sender // feeRecipient
); );
// query token balance after redeeming the payload
(,bytes memory queriedBalanceAfter) = feeTokenAddress.staticcall(
abi.encodeWithSelector(IERC20.balanceOf.selector,
address(this)
));
uint256 balanceAfter = abi.decode(queriedBalanceAfter, (uint256));
// the balance change is the swap amount (less relayer fees)
uint256 swapAmountLessFees = balanceAfter - balanceBefore;
// create dynamic address array - uniswap won't take fixed size array // create dynamic address array - uniswap won't take fixed size array
address[] memory uniPath = new address[](2); address[] memory uniPath = new address[](2);
uniPath[0] = payload.path[0]; uniPath[0] = payload.path[0];
uniPath[1] = payload.path[1]; uniPath[1] = payload.path[1];
// make sure first element in path is UST // sanity check path
require(uniPath[0]==feeTokenAddress, "tokenIn must be UST"); require(
uniPath[0]==FEE_TOKEN_ADDRESS,
"tokenIn must be feeToken"
);
require(
payload.path[1]==WRAPPED_NATIVE,
"tokenOut must be wrapped native asset"
);
// pay the relayer in feeToken so that user gets desired exact amount out
IERC20 feeToken = IERC20(FEE_TOKEN_ADDRESS);
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;
// approve the router to spend tokens // approve the router to spend tokens
TransferHelper.safeApprove( TransferHelper.safeApprove(
uniPath[0], uniPath[0],
address(swapRouter), address(SWAP_ROUTER),
swapAmountLessFees maxAmountInLessFees
); );
// try to perform the swap // try to perform the swap
try swapRouter.swapExactTokensForTokens( try SWAP_ROUTER.swapTokensForExactTokens(
swapAmountLessFees, amountOut,
payload.estimatedAmount, maxAmountInLessFees,
uniPath, uniPath,
payload.recipientAddress, address(this),
payload.deadline payload.deadline
) returns (uint256[] memory amounts) { ) returns (uint256[] memory amounts) {
// amountIn used is first element in array
amountInUsed = amounts[0];
// refund recipient with any feeToken not used in the swap
if (amountInUsed < maxAmountInLessFees) {
TransferHelper.safeApprove(
FEE_TOKEN_ADDRESS,
address(SWAP_ROUTER),
0
);
IERC20(FEE_TOKEN_ADDRESS).safeTransfer(
payload.recipientAddress,
maxAmountInLessFees - amountInUsed
);
}
// unwrap native and send to recipient
IWETH(WRAPPED_NATIVE).withdraw(amounts[1]);
payable(payload.recipientAddress).transfer(amounts[1]);
// used in UI to tell user they're getting // used in UI to tell user they're getting
// their desired token // their desired native asset
emit SwapResult( emit SwapResult(
payload.recipientAddress, payload.recipientAddress,
uniPath[1], uniPath[1],
@ -262,28 +233,28 @@ contract CrossChainSwapV2 {
amounts[1], amounts[1],
1 1
); );
return amounts; return amountInUsed;
} catch { } catch {
// swap failed - return UST to recipient // swap failed - return feeToken to recipient
IERC20(feeTokenAddress).safeTransfer( IERC20(FEE_TOKEN_ADDRESS).safeTransfer(
payload.recipientAddress, payload.recipientAddress,
swapAmountLessFees maxAmountInLessFees
); );
// used in UI to tell user they're getting // used in UI to tell user they're getting
// UST instead of their desired token // feeToken instead of their desired native asset
emit SwapResult( emit SwapResult(
payload.recipientAddress, payload.recipientAddress,
uniPath[0], uniPath[0],
msg.sender, msg.sender,
swapAmountLessFees, maxAmountInLessFees,
0 0
); );
} }
} }
/// @dev Executes exactIn native asset and token swaps before
/// sending a custom payload to the TokenBridge /// @dev Executes exactIn native asset swap
function _swapExactInBeforeTransfer( function _swapExactInBeforeTransfer(
uint256 amountIn, uint256 amountIn,
uint256 amountOutMinimum, uint256 amountOutMinimum,
@ -294,12 +265,12 @@ contract CrossChainSwapV2 {
// approve the router to spend tokens // approve the router to spend tokens
TransferHelper.safeApprove( TransferHelper.safeApprove(
path[0], path[0],
address(swapRouter), address(SWAP_ROUTER),
amountIn amountIn
); );
// perform the swap // perform the swap
uint256[] memory amounts = swapRouter.swapExactTokensForTokens( uint256[] memory amounts = SWAP_ROUTER.swapExactTokensForTokens(
amountIn, amountIn,
amountOutMinimum, amountOutMinimum,
path, path,
@ -309,69 +280,6 @@ contract CrossChainSwapV2 {
amountOut = amounts[1]; amountOut = amounts[1];
} }
/// @dev Calls _swapExactInBeforeTransfer and encodes custom payload with
/// instructions for executing token swaps on the destination chain
function swapExactInAndTransfer(
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"
);
// send tokens to this contract
IERC20 token = IERC20(path[0]);
token.safeTransferFrom(msg.sender, address(this), swapParams.amountIn);
// 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,
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
);
}
/// @dev Calls _swapExactInBeforeTransfer and encodes custom payload with /// @dev Calls _swapExactInBeforeTransfer and encodes custom payload with
/// instructions for executing native asset swaps on the destination chain /// instructions for executing native asset swaps on the destination chain
function swapExactNativeInAndTransfer( function swapExactNativeInAndTransfer(
@ -387,17 +295,17 @@ contract CrossChainSwapV2 {
"insufficient amountOutMinimum to pay relayer" "insufficient amountOutMinimum to pay relayer"
); );
require( require(
path[0]==wrappedNative, path[0]==WRAPPED_NATIVE,
"tokenIn must be wrapped native asset for first swap" "tokenIn must be wrapped native asset for first swap"
); );
require( require(
path[1]==feeTokenAddress, path[1]==FEE_TOKEN_ADDRESS,
"tokenOut must be UST for first swap" "tokenOut must be feeToken for first swap"
); );
require(msg.value > 0, "must pass non 0 native asset amount"); require(msg.value > 0, "must pass non 0 native asset amount");
// wrap native asset // wrap native asset
IWETH(wrappedNative).deposit{ IWETH(WRAPPED_NATIVE).deposit{
value : msg.value value : msg.value
}(); }();
@ -410,276 +318,53 @@ contract CrossChainSwapV2 {
swapParams.deadline swapParams.deadline
); );
// create payload variable // create payload with target swap instructions
bytes memory payload; bytes memory payload = abi.encodePacked(
swapParams.targetAmountOutMinimum,
// UST is native to Terra - no need for swap instructions swapParams.targetChainRecipient,
if (targetChainId == terraChainId) { path[2],
payload = abi.encodePacked( path[3],
swapParams.targetChainRecipient swapParams.deadline,
); swapParams.poolFee,
} else { TypeExactIn,
payload = abi.encodePacked( relayerFee
swapParams.targetAmountOutMinimum, );
swapParams.targetChainRecipient,
path[2], // approve token bridge to spend feeTokens
path[3],
swapParams.deadline,
swapParams.poolFee,
typeExactIn,
typeNativeSwap
);
}
// approve token bridge to spend feeTokens (UST)
TransferHelper.safeApprove( TransferHelper.safeApprove(
feeTokenAddress, FEE_TOKEN_ADDRESS,
tokenBridgeAddress, TOKEN_BRIDGE_ADDRESS,
amountOut amountOut
); );
// send transfer with payload to the TokenBridge // send transfer with payload to the TokenBridge
TokenBridge(tokenBridgeAddress).transferTokensWithPayload( TokenBridge(TOKEN_BRIDGE_ADDRESS).transferTokensWithPayload(
feeTokenAddress, FEE_TOKEN_ADDRESS,
amountOut, amountOut,
targetChainId, targetChainId,
targetContractAddress, targetContractAddress,
relayerFee,
nonce, nonce,
payload payload
); );
} }
/// @dev Executes exactOut native asset swap and pays the relayer /// @dev Executes exactOut native asset swaps
function recvAndSwapExactNativeOut(
bytes calldata encodedVaa
) external returns (uint256 amountInUsed) {
// check token balance before redeeming the payload
(,bytes memory queriedBalanceBefore) = feeTokenAddress.staticcall(
abi.encodeWithSelector(IERC20.balanceOf.selector,
address(this)
));
uint256 balanceBefore = abi.decode(queriedBalanceBefore, (uint256));
// redeem and fetch parsed payload
SwapHelper.DecodedVaaParameters memory payload =
_getParsedPayload(
encodedVaa,
typeExactOut,
typeNativeSwap,
msg.sender // feeRecipient
);
// query token balance after redeeming the payload
(,bytes memory queriedBalanceAfter) = feeTokenAddress.staticcall(
abi.encodeWithSelector(IERC20.balanceOf.selector,
address(this)
));
uint256 balanceAfter = abi.decode(queriedBalanceAfter, (uint256));
// the balance change is the swap amount (less relayer fees)
uint256 maxAmountInLessFees = balanceAfter - balanceBefore;
// 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];
// sanity check path
require(
uniPath[0]==feeTokenAddress,
"tokenIn must be UST"
);
require(
payload.path[1]==wrappedNative,
"tokenOut must be wrapped native asset"
);
// 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),
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
);
IERC20(feeTokenAddress).safeTransfer(
payload.recipientAddress,
maxAmountInLessFees - amountInUsed
);
}
// unwrap native and send to recipient
IWETH(wrappedNative).withdraw(amounts[1]);
payable(payload.recipientAddress).transfer(amounts[1]);
// used in UI to tell user they're getting
// their desired native asset
emit SwapResult(
payload.recipientAddress,
uniPath[1],
msg.sender,
amounts[1],
1
);
return amountInUsed;
} catch {
// swap failed - return UST to recipient
IERC20(feeTokenAddress).safeTransfer(
payload.recipientAddress,
maxAmountInLessFees
);
// used in UI to tell user they're getting
// UST instead of their desired native asset
emit SwapResult(
payload.recipientAddress,
uniPath[0],
msg.sender,
maxAmountInLessFees,
0
);
}
}
/// @dev Executes exactOut token swap and pays the relayer
function recvAndSwapExactOut(
bytes calldata encodedVaa
) external returns (uint256 amountInUsed) {
// check token balance before redeeming the payload
(,bytes memory queriedBalanceBefore) = feeTokenAddress.staticcall(
abi.encodeWithSelector(IERC20.balanceOf.selector,
address(this)
));
uint256 balanceBefore = abi.decode(queriedBalanceBefore, (uint256));
// redeem and fetch parsed payload
SwapHelper.DecodedVaaParameters memory payload =
_getParsedPayload(
encodedVaa,
typeExactOut,
typeTokenSwap,
msg.sender // feeRecipient
);
// query token balance after redeeming the payload
(,bytes memory queriedBalanceAfter) = feeTokenAddress.staticcall(
abi.encodeWithSelector(IERC20.balanceOf.selector,
address(this)
));
uint256 balanceAfter = abi.decode(queriedBalanceAfter, (uint256));
// the balance change is the swap amount (less relayer fees)
uint256 maxAmountInLessFees = balanceAfter - balanceBefore;
// 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");
// 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
);
IERC20(feeTokenAddress).safeTransfer(
payload.recipientAddress,
maxAmountInLessFees - amountInUsed
);
}
// used in UI to tell user they're getting
// their desired token
emit SwapResult(
payload.recipientAddress,
uniPath[1],
msg.sender,
amounts[1],
1
);
return amountInUsed;
} catch {
// swap failed - return UST to recipient
IERC20(feeTokenAddress).safeTransfer(
payload.recipientAddress,
maxAmountInLessFees
);
// used in UI to tell user they're getting
// UST instead of their desired token
emit SwapResult(
payload.recipientAddress,
uniPath[0],
msg.sender,
maxAmountInLessFees,
0
);
}
}
/// @dev Executes exactOut native asset and token swaps before
/// sending a custom payload to the TokenBridge
function _swapExactOutBeforeTransfer( function _swapExactOutBeforeTransfer(
uint256 amountOut, uint256 amountOut,
uint256 amountInMaximum, uint256 amountInMaximum,
address contractCaller, address contractCaller,
address[] calldata path, address[] calldata path,
uint256 deadline, uint256 deadline
uint8 swapType
) internal { ) internal {
// approve the router to spend tokens // approve the router to spend tokens
TransferHelper.safeApprove( TransferHelper.safeApprove(
path[0], path[0],
address(swapRouter), address(SWAP_ROUTER),
amountInMaximum amountInMaximum
); );
// perform the swap // perform the swap
uint256[] memory amounts = swapRouter.swapTokensForExactTokens( uint256[] memory amounts = SWAP_ROUTER.swapTokensForExactTokens(
amountOut, amountOut,
amountInMaximum, amountInMaximum,
path, path,
@ -692,94 +377,17 @@ contract CrossChainSwapV2 {
// refund contractCaller with any amountIn that wasn't spent // refund contractCaller with any amountIn that wasn't spent
if (amountInUsed < amountInMaximum) { if (amountInUsed < amountInMaximum) {
TransferHelper.safeApprove(path[0], address(swapRouter), 0); // unwrap remaining native asset and send to contractCaller
if (swapType == typeTokenSwap) { TransferHelper.safeApprove(path[0], address(SWAP_ROUTER), 0);
// send remaining tokens to contractCaller IWETH(WRAPPED_NATIVE).withdraw(
IERC20 token = IERC20(path[0]); amountInMaximum - amountInUsed
token.safeTransfer( );
contractCaller, payable(contractCaller).transfer(
amountInMaximum - amountInUsed amountInMaximum - amountInUsed
); );
} else {
// unwrap remaining native asset and send to contractCaller
IWETH(wrappedNative).withdraw(
amountInMaximum - amountInUsed
);
payable(contractCaller).transfer(
amountInMaximum - amountInUsed
);
}
} }
} }
/// @dev Calls _swapExactOutBeforeTransfer and encodes custom payload with
/// instructions for executing token swaps on the destination chain
function swapExactOutAndTransfer(
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"
);
// send tokens to this contract before swap
IERC20 token = IERC20(path[0]);
token.safeTransferFrom(
msg.sender,
address(this),
swapParams.amountInMaximum
);
// peform the first swap
_swapExactOutBeforeTransfer(
swapParams.amountOut,
swapParams.amountInMaximum,
msg.sender,
path[0:2],
swapParams.deadline,
typeTokenSwap
);
// encode payload for second swap
bytes memory payload = abi.encodePacked(
swapParams.targetAmountOut,
swapParams.targetChainRecipient,
path[2],
path[3],
swapParams.deadline,
swapParams.poolFee,
typeExactOut,
typeTokenSwap
);
// 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
);
}
/// @dev Calls _swapExactOutBeforeTransfer and encodes custom payload with /// @dev Calls _swapExactOutBeforeTransfer and encodes custom payload with
/// instructions for executing native asset swaps on the destination chain /// instructions for executing native asset swaps on the destination chain
function swapExactNativeOutAndTransfer( function swapExactNativeOutAndTransfer(
@ -795,17 +403,17 @@ contract CrossChainSwapV2 {
"insufficient amountOut to pay relayer" "insufficient amountOut to pay relayer"
); );
require( require(
path[0]==wrappedNative, path[0]==WRAPPED_NATIVE,
"tokenIn must be wrapped native asset for first swap" "tokenIn must be wrapped native asset for first swap"
); );
require( require(
path[1]==feeTokenAddress, path[1]==FEE_TOKEN_ADDRESS,
"tokenOut must be UST for first swap" "tokenOut must be feeToken for first swap"
); );
require(msg.value > 0, "must pass non 0 native asset amount"); require(msg.value > 0, "must pass non 0 native asset amount");
// wrap native asset // wrap native asset
IWETH(wrappedNative).deposit{ IWETH(WRAPPED_NATIVE).deposit{
value : msg.value value : msg.value
}(); }();
@ -815,45 +423,34 @@ contract CrossChainSwapV2 {
msg.value, msg.value,
msg.sender, msg.sender,
path[0:2], path[0:2],
swapParams.deadline, swapParams.deadline
typeNativeSwap
); );
// create payload variable // create payload with target swap instructions
bytes memory payload; bytes memory payload = abi.encodePacked(
swapParams.targetAmountOut,
swapParams.targetChainRecipient,
path[2],
path[3],
swapParams.deadline,
swapParams.poolFee,
TypeExactOut,
relayerFee
);
// UST is native to Terra - no need for swap instructions // approve token bridge to spend feeTokens
if (targetChainId == terraChainId) {
payload = abi.encodePacked(
swapParams.targetChainRecipient
);
} else {
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( TransferHelper.safeApprove(
feeTokenAddress, FEE_TOKEN_ADDRESS,
tokenBridgeAddress, TOKEN_BRIDGE_ADDRESS,
swapParams.amountOut swapParams.amountOut
); );
// send transfer with payload to the TokenBridge // send transfer with payload to the TokenBridge
TokenBridge(tokenBridgeAddress).transferTokensWithPayload( TokenBridge(TOKEN_BRIDGE_ADDRESS).transferTokensWithPayload(
feeTokenAddress, FEE_TOKEN_ADDRESS,
swapParams.amountOut, swapParams.amountOut,
targetChainId, targetChainId,
targetContractAddress, targetContractAddress,
relayerFee,
nonce, nonce,
payload payload
); );

View File

@ -3,8 +3,10 @@
pragma solidity ^0.7.6; pragma solidity ^0.7.6;
pragma abicoder v2; pragma abicoder v2;
import './IWormhole.sol'; import './shared/IWormhole.sol';
import './SwapHelper.sol'; import './shared/SwapHelper.sol';
import './shared/TokenBridge.sol';
import './shared/WETH.sol';
import 'solidity-bytes-utils/contracts/BytesLib.sol'; import 'solidity-bytes-utils/contracts/BytesLib.sol';
import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol'; import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
@ -12,28 +14,6 @@ import '@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol';
import '@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol'; import '@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol';
interface TokenBridge {
function transferTokensWithPayload(
address token,
uint256 amount,
uint16 recipientChain,
bytes32 recipient,
uint256 arbiterFee,
uint32 nonce,
bytes memory payload
) external payable returns (uint64);
function completeTransferWithPayload(
bytes memory encodedVm,
address feeRecipient
) external returns (bytes memory);
}
interface IWETH is IERC20 {
function withdraw(uint amount) external;
}
interface IUniswapRouter is ISwapRouter { interface IUniswapRouter is ISwapRouter {
function refundETH() external payable; function refundETH() external payable;
} }
@ -45,16 +25,12 @@ interface IUniswapRouter is ISwapRouter {
contract CrossChainSwapV3 { contract CrossChainSwapV3 {
using SafeERC20 for IERC20; using SafeERC20 for IERC20;
using BytesLib for bytes; using BytesLib for bytes;
uint8 public immutable typeExactIn = 1; uint8 public immutable TypeExactIn = 1;
uint8 public immutable typeExactOut = 2; uint8 public immutable TypeExactOut = 2;
uint8 public immutable typeNativeSwap = 1; IUniswapRouter public immutable SWAP_ROUTER;
uint8 public immutable typeTokenSwap = 2; address public immutable FEE_TOKEN_ADDRESS;
uint16 public immutable expectedVaaLength = 274; address public immutable TOKEN_BRIDGE_ADDRESS;
uint8 public immutable terraChainId = 3; address public immutable WRAPPED_NATIVE;
IUniswapRouter public immutable swapRouter;
address public immutable feeTokenAddress;
address public immutable tokenBridgeAddress;
address public immutable wrappedNative;
constructor( constructor(
address _swapRouterAddress, address _swapRouterAddress,
@ -62,10 +38,10 @@ contract CrossChainSwapV3 {
address _tokenBridgeAddress, address _tokenBridgeAddress,
address _wrappedNativeAddress address _wrappedNativeAddress
) { ) {
swapRouter = IUniswapRouter(_swapRouterAddress); SWAP_ROUTER = IUniswapRouter(_swapRouterAddress);
feeTokenAddress = _feeTokenAddress; FEE_TOKEN_ADDRESS = _feeTokenAddress;
tokenBridgeAddress = _tokenBridgeAddress; TOKEN_BRIDGE_ADDRESS = _tokenBridgeAddress;
wrappedNative = _wrappedNativeAddress; WRAPPED_NATIVE = _wrappedNativeAddress;
} }
/// @dev Used to communicate information about executed swaps to UI/user /// @dev Used to communicate information about executed swaps to UI/user
@ -78,23 +54,15 @@ contract CrossChainSwapV3 {
); );
/// @dev Returns the parsed TokenBridge payload which contains swap /// @dev Returns the parsed TokenBridge payload which contains swap
/// instructions after redeeming from the VAA from the TokenBridge /// instructions after redeeming the VAA from the TokenBridge
function _getParsedPayload( function _getParsedPayload(
bytes calldata encodedVaa, bytes calldata encodedVaa,
uint8 swapFunctionType, uint8 swapFunctionType
uint8 swapCurrencyType,
address feeRecipient
) private returns (SwapHelper.DecodedVaaParameters memory payload) { ) private returns (SwapHelper.DecodedVaaParameters memory payload) {
// complete the transfer on the token bridge // complete the transfer on the token bridge
bytes memory vmPayload = TokenBridge( bytes memory vmPayload = TokenBridge(
tokenBridgeAddress TOKEN_BRIDGE_ADDRESS
).completeTransferWithPayload(encodedVaa, feeRecipient); ).completeTransferWithPayload(encodedVaa);
// make sure payload is the right size
require(
vmPayload.length==expectedVaaLength,
"VAA has the wrong number of bytes"
);
// parse the payload // parse the payload
payload = SwapHelper.decodeVaaPayload(vmPayload); payload = SwapHelper.decodeVaaPayload(vmPayload);
@ -104,56 +72,34 @@ contract CrossChainSwapV3 {
payload.swapFunctionType==swapFunctionType, payload.swapFunctionType==swapFunctionType,
"incorrect swapFunctionType in payload" "incorrect swapFunctionType in payload"
); );
require( }
payload.swapCurrencyType==swapCurrencyType,
"incorrect swapCurrencyType in payload"
);
}
/// @dev Executes exactIn native asset swap and pays the relayer /// @dev Executes exactIn native asset swap and pays the relayer
function recvAndSwapExactNativeIn( function recvAndSwapExactNativeIn(
bytes calldata encodedVaa bytes calldata encodedVaa
) external returns (uint256 amountOut) { ) external returns (uint256 amountOut) {
// check token balance before redeeming the payload // redeem and fetch parsed payload
(,bytes memory queriedBalanceBefore) = feeTokenAddress.staticcall(
abi.encodeWithSelector(IERC20.balanceOf.selector,
address(this)
));
uint256 balanceBefore = abi.decode(queriedBalanceBefore, (uint256));
SwapHelper.DecodedVaaParameters memory payload = SwapHelper.DecodedVaaParameters memory payload =
_getParsedPayload( _getParsedPayload(
encodedVaa, encodedVaa,
typeExactIn, TypeExactIn
typeNativeSwap, );
msg.sender // feeRecipient
);
// query token balance after redeeming the payload
(,bytes memory queriedBalanceAfter) = feeTokenAddress.staticcall(
abi.encodeWithSelector(IERC20.balanceOf.selector,
address(this)
));
uint256 balanceAfter = abi.decode(queriedBalanceAfter, (uint256));
// the balance change is the swap amount (less relayer fees)
uint256 swapAmountLessFees = balanceAfter - balanceBefore;
// sanity check path // sanity check path
require( require(
payload.path[0]==feeTokenAddress, payload.path[0]==FEE_TOKEN_ADDRESS,
"tokenIn must be UST" "tokenIn must be feeToken"
); );
require( require(
payload.path[1]==wrappedNative, payload.path[1]==WRAPPED_NATIVE,
"tokenOut must be wrapped Native" "tokenOut must be wrapped Native"
); );
// approve the router to spend tokens // approve the router to spend tokens
TransferHelper.safeApprove( TransferHelper.safeApprove(
payload.path[0], payload.path[0],
address(swapRouter), address(SWAP_ROUTER),
swapAmountLessFees payload.swapAmount
); );
// set swap options with user params // set swap options with user params
@ -164,16 +110,23 @@ contract CrossChainSwapV3 {
fee: payload.poolFee, fee: payload.poolFee,
recipient: address(this), recipient: address(this),
deadline: payload.deadline, deadline: payload.deadline,
amountIn: swapAmountLessFees, amountIn: payload.swapAmount,
amountOutMinimum: payload.estimatedAmount, amountOutMinimum: payload.estimatedAmount,
sqrtPriceLimitX96: 0 sqrtPriceLimitX96: 0
}); });
// try to execute the swap // try to execute the swap
try swapRouter.exactInputSingle(params) returns (uint256 amountOut) { try SWAP_ROUTER.exactInputSingle(params) returns (uint256 amountOut) {
// calculate how much to pay the relayer in the native token
uint256 nativeRelayerFee = amountOut * payload.relayerFee / payload.swapAmount;
uint256 nativeAmountOut = amountOut - nativeRelayerFee;
// unwrap native and send to recipient // unwrap native and send to recipient
IWETH(wrappedNative).withdraw(amountOut); IWETH(WRAPPED_NATIVE).withdraw(amountOut);
payable(payload.recipientAddress).transfer(amountOut); payable(payload.recipientAddress).transfer(nativeAmountOut);
/// pay the relayer in the native token
payable(msg.sender).transfer(nativeRelayerFee);
// used in UI to tell user they're getting // used in UI to tell user they're getting
// their desired token // their desired token
@ -181,86 +134,103 @@ contract CrossChainSwapV3 {
payload.recipientAddress, payload.recipientAddress,
payload.path[1], payload.path[1],
msg.sender, msg.sender,
amountOut, nativeAmountOut,
1 1
); );
return amountOut; return amountOut;
} catch { } catch {
// swap failed - return UST to recipient // pay relayer in the feeToken since the swap failed
IERC20(feeTokenAddress).safeTransfer( IERC20 feeToken = IERC20(FEE_TOKEN_ADDRESS);
feeToken.safeTransfer(msg.sender, payload.relayerFee);
// swap failed - return feeToken (less relayer fees) to recipient
feeToken.safeTransfer(
payload.recipientAddress, payload.recipientAddress,
swapAmountLessFees payload.swapAmount - payload.relayerFee
); );
// used in UI to tell user they're getting // used in UI to tell user they're getting
// UST instead of their desired native asset // feeToken instead of their desired native asset
emit SwapResult( emit SwapResult(
payload.recipientAddress, payload.recipientAddress,
payload.path[0], payload.path[0],
msg.sender, msg.sender,
swapAmountLessFees, payload.swapAmount - payload.relayerFee,
0 0
); );
} }
} }
/// @dev Executes exactIn token swap and pays the relayer /// @dev Executes exactOut native asset swap and pays the relayer
function recvAndSwapExactIn( function recvAndSwapExactNativeOut(
bytes calldata encodedVaa bytes calldata encodedVaa
) external returns (uint256 amountOut) { ) external returns (uint256 amountInUsed) {
// check token balance before redeeming the payload // redeem and fetch parsed payload
(,bytes memory queriedBalanceBefore) = feeTokenAddress.staticcall(
abi.encodeWithSelector(IERC20.balanceOf.selector,
address(this)
));
uint256 balanceBefore = abi.decode(queriedBalanceBefore, (uint256));
// redeem and fetch the parsed payload
SwapHelper.DecodedVaaParameters memory payload = SwapHelper.DecodedVaaParameters memory payload =
_getParsedPayload( _getParsedPayload(
encodedVaa, encodedVaa,
typeExactIn, TypeExactOut
typeTokenSwap,
msg.sender // feeRecipient
); );
// query token balance after redeeming the payload // sanity check path
(,bytes memory queriedBalanceAfter) = feeTokenAddress.staticcall( require(
abi.encodeWithSelector(IERC20.balanceOf.selector, payload.path[0]==FEE_TOKEN_ADDRESS,
address(this) "tokenIn must be feeToken"
)); );
uint256 balanceAfter = abi.decode(queriedBalanceAfter, (uint256)); require(
payload.path[1]==WRAPPED_NATIVE,
"tokenOut must be wrapped native asset"
);
// the balance change is the swap amount (less relayer fees) // pay the relayer in feeToken so that user gets desired exact amount out
uint256 swapAmountLessFees = balanceAfter - balanceBefore; IERC20 feeToken = IERC20(FEE_TOKEN_ADDRESS);
feeToken.safeTransfer(msg.sender, payload.relayerFee);
uint256 maxAmountInLessFees = payload.swapAmount - payload.relayerFee;
// check path to see if first element is the feeToken // amountOut is the estimated swap amount for exact out methods
require(payload.path[0]==feeTokenAddress, "tokenIn must be UST"); uint256 amountOut = payload.estimatedAmount;
// approve the router to spend tokens // approve the router to spend tokens
TransferHelper.safeApprove( TransferHelper.safeApprove(
payload.path[0], payload.path[0],
address(swapRouter), address(SWAP_ROUTER),
swapAmountLessFees maxAmountInLessFees
); );
// set swap options with user params // set swap options with user params
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactOutputSingleParams memory params =
ISwapRouter.ExactInputSingleParams({ ISwapRouter.ExactOutputSingleParams({
tokenIn: payload.path[0], tokenIn: payload.path[0],
tokenOut: payload.path[1], tokenOut: payload.path[1],
fee: payload.poolFee, fee: payload.poolFee,
recipient: payload.recipientAddress, recipient: address(this),
deadline: payload.deadline, deadline: payload.deadline,
amountIn: swapAmountLessFees, amountOut: amountOut,
amountOutMinimum: payload.estimatedAmount, amountInMaximum: maxAmountInLessFees,
sqrtPriceLimitX96: 0 sqrtPriceLimitX96: 0
}); });
// try to perform the swap // try to perform the swap
try swapRouter.exactInputSingle(params) returns (uint256 amountOut) { try SWAP_ROUTER.exactOutputSingle(params) returns (uint256 amountInUsed) {
// refund recipient with any feeToken not used in the swap
if (amountInUsed < maxAmountInLessFees) {
TransferHelper.safeApprove(
FEE_TOKEN_ADDRESS,
address(SWAP_ROUTER),
0
);
IERC20(FEE_TOKEN_ADDRESS).safeTransfer(
payload.recipientAddress,
maxAmountInLessFees - amountInUsed
);
}
// unwrap native and send to recipient
IWETH(WRAPPED_NATIVE).withdraw(amountOut);
payable(payload.recipientAddress).transfer(amountOut);
// used in UI to tell user they're getting // used in UI to tell user they're getting
// their desired token // their desired native asset
emit SwapResult( emit SwapResult(
payload.recipientAddress, payload.recipientAddress,
payload.path[1], payload.path[1],
@ -268,50 +238,35 @@ contract CrossChainSwapV3 {
amountOut, amountOut,
1 1
); );
return amountOut; return amountInUsed;
} catch { } catch {
// swap failed - return UST to recipient // swap failed - return feeToken to recipient
IERC20(feeTokenAddress).safeTransfer( IERC20(FEE_TOKEN_ADDRESS).safeTransfer(
payload.recipientAddress, payload.recipientAddress,
swapAmountLessFees maxAmountInLessFees
); );
// used in UI to tell user they're getting // used in UI to tell user they're getting
// UST instead of their desired token // feeToken instead of their desired native asset
emit SwapResult( emit SwapResult(
payload.recipientAddress, payload.recipientAddress,
payload.path[0], payload.path[0],
msg.sender, msg.sender,
swapAmountLessFees, maxAmountInLessFees,
0 0
); );
} }
} }
/// @dev Executes exactIn native asset and token swaps before /// @dev Executes exactIn native asset swap
/// sending a custom payload to the TokenBridge
function _swapExactInBeforeTransfer( function _swapExactInBeforeTransfer(
uint256 amountIn, uint256 amountIn,
uint256 amountOutMinimum, uint256 amountOutMinimum,
address contractCaller, address contractCaller,
address[] calldata path, address[] calldata path,
uint256 deadline, uint256 deadline,
uint24 poolFee, uint24 poolFee
uint8 swapType
) internal returns (uint256 amountOut) { ) internal returns (uint256 amountOut) {
if (swapType == typeTokenSwap) {
// transfer the allowed amount of tokens to this contract
IERC20 token = IERC20(path[0]);
token.safeTransferFrom(contractCaller, address(this), amountIn);
// approve the router to spend tokens
TransferHelper.safeApprove(
path[0],
address(swapRouter),
amountIn
);
}
// set swap options with user params // set swap options with user params
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams memory params =
ISwapRouter.ExactInputSingleParams({ ISwapRouter.ExactInputSingleParams({
@ -326,74 +281,9 @@ contract CrossChainSwapV3 {
}); });
// perform the swap // perform the swap
if (swapType == typeTokenSwap) { amountOut = SWAP_ROUTER.exactInputSingle{value: amountIn}(params);
amountOut = swapRouter.exactInputSingle(params);
} else { // native swap
amountOut = swapRouter.exactInputSingle{value: amountIn}(params);
}
} }
/// @dev Calls _swapExactInBeforeTransfer and encodes custom payload with
/// instructions for executing token swaps on the destination chain
function swapExactInAndTransfer(
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,
swapParams.poolFee,
typeTokenSwap
);
// encode payload for second swap
bytes memory payload = abi.encodePacked(
swapParams.targetAmountOutMinimum,
swapParams.targetChainRecipient,
path[2],
path[3],
swapParams.deadline,
swapParams.poolFee,
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
);
}
/// @dev Calls _swapExactInBeforeTransfer and encodes custom payload with /// @dev Calls _swapExactInBeforeTransfer and encodes custom payload with
/// instructions for executing native asset swaps on the destination chain /// instructions for executing native asset swaps on the destination chain
function swapExactNativeInAndTransfer( function swapExactNativeInAndTransfer(
@ -409,12 +299,12 @@ contract CrossChainSwapV3 {
"insufficient amountOutMinimum to pay relayer" "insufficient amountOutMinimum to pay relayer"
); );
require( require(
path[0]==wrappedNative, path[0]==WRAPPED_NATIVE,
"tokenIn must be wrapped native asset for first swap" "tokenIn must be wrapped native asset for first swap"
); );
require( require(
path[1]==feeTokenAddress, path[1]==FEE_TOKEN_ADDRESS,
"tokenOut must be UST for first swap" "tokenOut must be feeToken for first swap"
); );
require(msg.value > 0, "must pass non 0 native asset amount"); require(msg.value > 0, "must pass non 0 native asset amount");
@ -425,291 +315,48 @@ contract CrossChainSwapV3 {
msg.sender, msg.sender,
path[0:2], path[0:2],
swapParams.deadline, swapParams.deadline,
swapParams.poolFee, swapParams.poolFee
typeNativeSwap
); );
// create payload variable // create payload with target swap instructions
bytes memory payload; bytes memory payload = abi.encodePacked(
swapParams.targetAmountOutMinimum,
swapParams.targetChainRecipient,
path[2],
path[3],
swapParams.deadline,
swapParams.poolFee,
TypeExactIn,
relayerFee
);
// UST is native to Terra - no need for swap instructions // approve token bridge to spend feeTokens
if (targetChainId == terraChainId) {
payload = abi.encodePacked(
swapParams.targetChainRecipient
);
} else {
payload = abi.encodePacked(
swapParams.targetAmountOutMinimum,
swapParams.targetChainRecipient,
path[2],
path[3],
swapParams.deadline,
swapParams.poolFee,
typeExactIn,
typeNativeSwap
);
}
// approve token bridge to spend feeTokens (UST)
TransferHelper.safeApprove( TransferHelper.safeApprove(
feeTokenAddress, FEE_TOKEN_ADDRESS,
tokenBridgeAddress, TOKEN_BRIDGE_ADDRESS,
amountOut amountOut
); );
// send transfer with payload to the TokenBridge // send transfer with payload to the TokenBridge
TokenBridge(tokenBridgeAddress).transferTokensWithPayload( TokenBridge(TOKEN_BRIDGE_ADDRESS).transferTokensWithPayload(
feeTokenAddress, FEE_TOKEN_ADDRESS,
amountOut, amountOut,
targetChainId, targetChainId,
targetContractAddress, targetContractAddress,
relayerFee,
nonce, nonce,
payload payload
); );
} }
/// @dev Executes exactOut native asset swap and pays the relayer /// @dev Executes exactOut native asset swaps
function recvAndSwapExactNativeOut(
bytes calldata encodedVaa
) external returns (uint256 amountInUsed) {
// check token balance before redeeming the payload
(,bytes memory queriedBalanceBefore) = feeTokenAddress.staticcall(
abi.encodeWithSelector(IERC20.balanceOf.selector,
address(this)
));
uint256 balanceBefore = abi.decode(queriedBalanceBefore, (uint256));
// redeem and fetch parsed payload
SwapHelper.DecodedVaaParameters memory payload =
_getParsedPayload(
encodedVaa,
typeExactOut,
typeNativeSwap,
msg.sender // feeRecipient
);
// query token balance after redeeming the payload
(,bytes memory queriedBalanceAfter) = feeTokenAddress.staticcall(
abi.encodeWithSelector(IERC20.balanceOf.selector,
address(this)
));
uint256 balanceAfter = abi.decode(queriedBalanceAfter, (uint256));
// the balance change is the swap amount (less relayer fees)
uint256 maxAmountInLessFees = balanceAfter - balanceBefore;
// sanity check path
require(
payload.path[0]==feeTokenAddress,
"tokenIn must be UST"
);
require(
payload.path[1]==wrappedNative,
"tokenOut must be wrapped native asset"
);
// amountOut is the estimated swap amount for exact out methods
uint256 amountOut = payload.estimatedAmount;
// approve the router to spend tokens
TransferHelper.safeApprove(
payload.path[0],
address(swapRouter),
maxAmountInLessFees
);
// set swap options with user params
ISwapRouter.ExactOutputSingleParams memory params =
ISwapRouter.ExactOutputSingleParams({
tokenIn: payload.path[0],
tokenOut: payload.path[1],
fee: payload.poolFee,
recipient: address(this),
deadline: payload.deadline,
amountOut: amountOut,
amountInMaximum: maxAmountInLessFees,
sqrtPriceLimitX96: 0
});
// try to perform the swap
try swapRouter.exactOutputSingle(params) returns (uint256 amountInUsed) {
// refund recipient with any UST not used in the swap
if (amountInUsed < maxAmountInLessFees) {
TransferHelper.safeApprove(
feeTokenAddress,
address(swapRouter),
0
);
IERC20(feeTokenAddress).safeTransfer(
payload.recipientAddress,
maxAmountInLessFees - amountInUsed
);
}
// unwrap native and send to recipient
IWETH(wrappedNative).withdraw(amountOut);
payable(payload.recipientAddress).transfer(amountOut);
// used in UI to tell user they're getting
// their desired native asset
emit SwapResult(
payload.recipientAddress,
payload.path[1],
msg.sender,
amountOut,
1
);
return amountInUsed;
} catch {
// swap failed - return UST to recipient
IERC20(feeTokenAddress).safeTransfer(
payload.recipientAddress,
maxAmountInLessFees
);
// used in UI to tell user they're getting
// UST instead of their desired native asset
emit SwapResult(
payload.recipientAddress,
payload.path[0],
msg.sender,
maxAmountInLessFees,
0
);
}
}
/// @dev Executes exactOut token swap and pays the relayer
function recvAndSwapExactOut(
bytes calldata encodedVaa
) external returns (uint256 amountInUsed) {
// check token balance before redeeming the payload
(,bytes memory queriedBalanceBefore) = feeTokenAddress.staticcall(
abi.encodeWithSelector(IERC20.balanceOf.selector,
address(this)
));
uint256 balanceBefore = abi.decode(queriedBalanceBefore, (uint256));
// redeem and fetch parsed payload
SwapHelper.DecodedVaaParameters memory payload =
_getParsedPayload(
encodedVaa,
typeExactOut,
typeTokenSwap,
msg.sender // feeRecipient
);
// query token balance after redeeming the payload
(,bytes memory queriedBalanceAfter) = feeTokenAddress.staticcall(
abi.encodeWithSelector(IERC20.balanceOf.selector,
address(this)
));
uint256 balanceAfter = abi.decode(queriedBalanceAfter, (uint256));
// the balance change is the swap amount (less relayer fees)
uint256 maxAmountInLessFees = balanceAfter - balanceBefore;
// check path to see if first element is the feeToken
require(payload.path[0]==feeTokenAddress, "tokenIn must be UST");
// amountOut is the estimated swap amount for exact out methods
uint256 amountOut = payload.estimatedAmount;
// approve the router to spend tokens
TransferHelper.safeApprove(
payload.path[0],
address(swapRouter),
maxAmountInLessFees
);
// set swap options with user params
ISwapRouter.ExactOutputSingleParams memory params =
ISwapRouter.ExactOutputSingleParams({
tokenIn: payload.path[0],
tokenOut: payload.path[1],
fee: payload.poolFee,
recipient: payload.recipientAddress,
deadline: payload.deadline,
amountOut: amountOut,
amountInMaximum: maxAmountInLessFees,
sqrtPriceLimitX96: 0
});
// try to perform the swap
try swapRouter.exactOutputSingle(params) returns (uint256 amountInUsed) {
// refund recipient with any UST not used in the swap
if (amountInUsed < maxAmountInLessFees) {
TransferHelper.safeApprove(
feeTokenAddress,
address(swapRouter),
0
);
IERC20(feeTokenAddress).safeTransfer(
payload.recipientAddress,
maxAmountInLessFees - amountInUsed
);
}
// used in UI to tell user they're getting
// their desired token
emit SwapResult(
payload.recipientAddress,
payload.path[1],
msg.sender,
amountOut,
1
);
return amountInUsed;
} catch {
// swap failed - return UST to recipient
IERC20(feeTokenAddress).safeTransfer(
payload.recipientAddress,
maxAmountInLessFees
);
// used in UI to tell user they're getting
// UST instead of their desired token
emit SwapResult(
payload.recipientAddress,
payload.path[0],
msg.sender,
maxAmountInLessFees,
0
);
}
}
/// @dev Executes exactOut native asset and token swaps before
/// sending a custom payload to the TokenBridge
function _swapExactOutBeforeTransfer( function _swapExactOutBeforeTransfer(
uint256 amountOut, uint256 amountOut,
uint256 amountInMaximum, uint256 amountInMaximum,
address contractCaller, address contractCaller,
address[] calldata path, address[] calldata path,
uint256 deadline, uint256 deadline,
uint24 poolFee, uint24 poolFee
uint8 swapType ) internal {
) internal {
// create instance of erc20 token for token swaps
IERC20 token = IERC20(path[0]);
if (swapType == typeTokenSwap) {
// transfer tokens to this contract
token.safeTransferFrom(
contractCaller,
address(this),
amountInMaximum
);
// approve the router to spend tokens
TransferHelper.safeApprove(
path[0],
address(swapRouter),
amountInMaximum
);
}
// set swap options with user params // set swap options with user params
ISwapRouter.ExactOutputSingleParams memory params = ISwapRouter.ExactOutputSingleParams memory params =
ISwapRouter.ExactOutputSingleParams({ ISwapRouter.ExactOutputSingleParams({
@ -723,95 +370,21 @@ contract CrossChainSwapV3 {
sqrtPriceLimitX96: 0 sqrtPriceLimitX96: 0
}); });
if (swapType == typeTokenSwap) { // executes the swap returning the amountInUsed
// executes the swap returning the amountInUsed // ask for our money back -_- after the swap executes
uint256 amountInUsed = swapRouter.exactOutputSingle(params); uint256 amountInUsed = SWAP_ROUTER.exactOutputSingle{value: amountInMaximum}(params);
SWAP_ROUTER.refundETH();
// return any amountIn not used in the swap to contractCaller // return unused native asset to contractCaller
if (amountInUsed < amountInMaximum) { if (amountInUsed < amountInMaximum) {
TransferHelper.safeApprove(path[0], address(swapRouter), 0); // set SWAP_ROUTER allowance to zero
token.safeTransfer( TransferHelper.safeApprove(path[0], address(SWAP_ROUTER), 0);
contractCaller, payable(contractCaller).transfer(
amountInMaximum - amountInUsed amountInMaximum - amountInUsed
); );
}
} else { // native swap
// executes the swap returning the amountInUsed
// ask for our money back -_- after the swap executes
uint256 amountInUsed = swapRouter.exactOutputSingle{value: amountInMaximum}(params);
swapRouter.refundETH();
// return unused native asset to contractCaller
if (amountInUsed < amountInMaximum) {
TransferHelper.safeApprove(path[0], address(swapRouter), 0);
payable(contractCaller).transfer(
amountInMaximum - amountInUsed
);
}
} }
} }
/// @dev Calls _swapExactOutBeforeTransfer and encodes custom payload with
/// instructions for executing token swaps on the destination chain
function swapExactOutAndTransfer(
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],
swapParams.deadline,
swapParams.poolFee,
typeTokenSwap
);
// encode payload for second swap
bytes memory payload = abi.encodePacked(
swapParams.targetAmountOut,
swapParams.targetChainRecipient,
path[2],
path[3],
swapParams.deadline,
swapParams.poolFee,
typeExactOut,
typeTokenSwap
);
// 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
);
}
/// @dev Calls _swapExactOutBeforeTransfer and encodes custom payload with /// @dev Calls _swapExactOutBeforeTransfer and encodes custom payload with
/// instructions for executing native asset swaps on the destination chain /// instructions for executing native asset swaps on the destination chain
function swapExactNativeOutAndTransfer( function swapExactNativeOutAndTransfer(
@ -827,12 +400,12 @@ contract CrossChainSwapV3 {
"insufficient amountOut to pay relayer" "insufficient amountOut to pay relayer"
); );
require( require(
path[0]==wrappedNative, path[0]==WRAPPED_NATIVE,
"tokenIn must be wrapped native asset for first swap" "tokenIn must be wrapped native asset for first swap"
); );
require( require(
path[1]==feeTokenAddress, path[1]==FEE_TOKEN_ADDRESS,
"tokenOut must be UST for first swap" "tokenOut must be feeToken for first swap"
); );
require(msg.value > 0, "must pass non 0 native asset amount"); require(msg.value > 0, "must pass non 0 native asset amount");
@ -843,45 +416,34 @@ contract CrossChainSwapV3 {
msg.sender, msg.sender,
path[0:2], path[0:2],
swapParams.deadline, swapParams.deadline,
swapParams.poolFee, swapParams.poolFee
typeNativeSwap
); );
// create payload variable // create payload with target swap instructions
bytes memory payload; bytes memory payload = abi.encodePacked(
swapParams.targetAmountOut,
swapParams.targetChainRecipient,
path[2],
path[3],
swapParams.deadline,
swapParams.poolFee,
TypeExactOut,
relayerFee
);
// UST is native to Terra - no need for swap instructions // approve token bridge to spend feeTokens
if (targetChainId == terraChainId) {
payload = abi.encodePacked(
swapParams.targetChainRecipient
);
} else {
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( TransferHelper.safeApprove(
feeTokenAddress, FEE_TOKEN_ADDRESS,
tokenBridgeAddress, TOKEN_BRIDGE_ADDRESS,
swapParams.amountOut swapParams.amountOut
); );
// send transfer with payload to the TokenBridge // send transfer with payload to the TokenBridge
TokenBridge(tokenBridgeAddress).transferTokensWithPayload( TokenBridge(TOKEN_BRIDGE_ADDRESS).transferTokensWithPayload(
feeTokenAddress, FEE_TOKEN_ADDRESS,
swapParams.amountOut, swapParams.amountOut,
targetChainId, targetChainId,
targetContractAddress, targetContractAddress,
relayerFee,
nonce, nonce,
payload payload
); );

View File

@ -6,12 +6,11 @@ pragma abicoder v2;
import './IWormhole.sol'; import './IWormhole.sol';
import 'solidity-bytes-utils/contracts/BytesLib.sol'; import 'solidity-bytes-utils/contracts/BytesLib.sol';
/// @title Helper library for cross-chain swaps /// @title Helper library for cross-chain swaps
/// @notice Contains functions necessary for parsing encoded VAAs /// @notice Contains functions necessary for parsing encoded VAAs
/// and structs containing swap parameters /// and structs containing swap parameters
library SwapHelper { library SwapHelper {
using BytesLib for bytes; using BytesLib for bytes;
/// @dev Parameters needed for exactIn swap type /// @dev Parameters needed for exactIn swap type
struct ExactInParameters { struct ExactInParameters {
@ -40,14 +39,14 @@ library SwapHelper {
uint8 version; uint8 version;
uint256 swapAmount; uint256 swapAmount;
address contractAddress; address contractAddress;
uint256 relayerFee; bytes32 fromAddress;
uint256 estimatedAmount; uint256 estimatedAmount;
address recipientAddress; address recipientAddress;
address[2] path; address[2] path;
uint256 deadline; uint256 deadline;
uint24 poolFee; uint24 poolFee;
uint8 swapFunctionType; uint8 swapFunctionType;
uint8 swapCurrencyType; uint256 relayerFee;
} }
/// @dev Decodes parameters encoded in a VAA /// @dev Decodes parameters encoded in a VAA
@ -71,7 +70,7 @@ library SwapHelper {
// skip // skip
index += 2; index += 2;
decoded.relayerFee = vmPayload.toUint256(index); decoded.fromAddress = vmPayload.toBytes32(index);
index += 32; index += 32;
decoded.estimatedAmount = vmPayload.toUint256(index); decoded.estimatedAmount = vmPayload.toUint256(index);
@ -98,6 +97,9 @@ library SwapHelper {
decoded.swapFunctionType = vmPayload.toUint8(index); decoded.swapFunctionType = vmPayload.toUint8(index);
index += 1; index += 1;
decoded.swapCurrencyType = vmPayload.toUint8(index); decoded.relayerFee = vmPayload.toUint256(index);
index += 32;
require(vmPayload.length == index, "invalid payload length");
} }
} }

View File

@ -0,0 +1,18 @@
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.7.6;
pragma abicoder v2;
interface TokenBridge {
function transferTokensWithPayload(
address token,
uint256 amount,
uint16 recipientChain,
bytes32 recipient,
uint32 nonce,
bytes memory payload
) external payable returns (uint64);
function completeTransferWithPayload(
bytes memory encodedVm
) external returns (bytes memory);
}

View File

@ -0,0 +1,11 @@
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.7.6;
pragma abicoder v2;
import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
interface IWETH is IERC20 {
function deposit() external payable;
function withdraw(uint amount) external;
}

View File

@ -0,0 +1,12 @@
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.7.6;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract WormUSD is ERC20 {
constructor(address mintToAddress, uint8 decimals, uint256 supply) ERC20("wormUSD", "WUSD"){
_setupDecimals(decimals);
_mint(mintToAddress, supply*10**decimals);
}
}

4
contracts/deploy_token.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/bash
set -euo pipefail
npx truffle migrate --config cfg/truffle-config.tokens.js --network goerli --reset

View File

@ -5,29 +5,23 @@ const SwapHelper = artifacts.require("SwapHelper");
const scriptsAddressPath = "../react/src/addresses"; const scriptsAddressPath = "../react/src/addresses";
module.exports = async function(deployer, network) { module.exports = async function (deployer, network) {
const routerAddress = "0x7e3411b04766089cfaa52db688855356a12f05d1"; // hurricaneswap const routerAddress = "0x7e3411b04766089cfaa52db688855356a12f05d1"; // hurricaneswap router
const feeTokenAddress = "0xe09ed38e5cd1014444846f62376ac88c5232cde9"; // wUST const feeTokenAddress = "0x8F23C5BE43FBfE7a30c04e4eDEE3D18995Fe7E2d"; // wormUSD
const tokenBridgeAddress = "0x61E44E506Ca5659E6c0bba9b678586fA2d729756"; const tokenBridgeAddress = "0x61E44E506Ca5659E6c0bba9b678586fA2d729756";
const wrappedAvaxAddress = "0x1d308089a2d1ced3f1ce36b1fcaf815b07217be3"; const wrappedAvaxAddress = "0x1d308089a2d1ced3f1ce36b1fcaf815b07217be3";
await deployer.deploy(SwapHelper); await deployer.deploy(SwapHelper);
await deployer.link(SwapHelper, CrossChainSwapV2); await deployer.link(SwapHelper, CrossChainSwapV2);
await deployer.deploy( await deployer.deploy(CrossChainSwapV2, routerAddress, feeTokenAddress, tokenBridgeAddress, wrappedAvaxAddress);
CrossChainSwapV2,
routerAddress,
feeTokenAddress,
tokenBridgeAddress,
wrappedAvaxAddress
);
// save the contract address somewhere
await fsp.mkdir(scriptsAddressPath, { recursive: true });
await fsp.writeFile( // save the contract address somewhere
`${scriptsAddressPath}/${network}.ts`, await fsp.mkdir(scriptsAddressPath, { recursive: true });
`export const SWAP_CONTRACT_ADDRESS = '${CrossChainSwapV2.address}';`
); await fsp.writeFile(
`${scriptsAddressPath}/${network}.ts`,
`export const SWAP_CONTRACT_ADDRESS = '${CrossChainSwapV2.address}';`
);
//deployer.link(ConvertLib, MetaCoin); //deployer.link(ConvertLib, MetaCoin);
//deployer.deploy(MetaCoin); //deployer.deploy(MetaCoin);

View File

@ -5,29 +5,23 @@ const SwapHelper = artifacts.require("SwapHelper");
const scriptsAddressPath = "../react/src/addresses"; const scriptsAddressPath = "../react/src/addresses";
module.exports = async function(deployer, network) { module.exports = async function (deployer, network) {
const routerAddress = "0x9Ac64Cc6e4415144C455BD8E4837Fea55603e5c3"; // pancakeswap const routerAddress = "0x9Ac64Cc6e4415144C455BD8E4837Fea55603e5c3"; // pancakeswap
const feeTokenAddress = "0x7b8eae1e85c8b189ee653d3f78733f4f788bb2c1"; // wUST const feeTokenAddress = "0x2A29D46D7e0B997358E9726DA0210Af212f2dfd7"; // wormUSD
const tokenBridgeAddress = "0x9dcF9D205C9De35334D646BeE44b2D2859712A09"; const tokenBridgeAddress = "0x9dcF9D205C9De35334D646BeE44b2D2859712A09";
const wrappedBnbAddress = "0xae13d989dac2f0debff460ac112a837c89baa7cd"; const wrappedBnbAddress = "0xae13d989dac2f0debff460ac112a837c89baa7cd";
await deployer.deploy(SwapHelper); await deployer.deploy(SwapHelper);
await deployer.link(SwapHelper, CrossChainSwapV2); await deployer.link(SwapHelper, CrossChainSwapV2);
await deployer.deploy( await deployer.deploy(CrossChainSwapV2, routerAddress, feeTokenAddress, tokenBridgeAddress, wrappedBnbAddress);
CrossChainSwapV2,
routerAddress,
feeTokenAddress,
tokenBridgeAddress,
wrappedBnbAddress
);
// save the contract address somewhere
await fsp.mkdir(scriptsAddressPath, { recursive: true });
await fsp.writeFile( // save the contract address somewhere
`${scriptsAddressPath}/${network}.ts`, await fsp.mkdir(scriptsAddressPath, { recursive: true });
`export const SWAP_CONTRACT_ADDRESS = '${CrossChainSwapV2.address}';`
); await fsp.writeFile(
`${scriptsAddressPath}/${network}.ts`,
`export const SWAP_CONTRACT_ADDRESS = '${CrossChainSwapV2.address}';`
);
//deployer.link(ConvertLib, MetaCoin); //deployer.link(ConvertLib, MetaCoin);
//deployer.deploy(MetaCoin); //deployer.deploy(MetaCoin);

View File

@ -7,19 +7,13 @@ const scriptsAddressPath = "../react/src/addresses";
module.exports = async function (deployer, network) { module.exports = async function (deployer, network) {
const routerAddress = "0xE592427A0AEce92De3Edee1F18E0157C05861564"; const routerAddress = "0xE592427A0AEce92De3Edee1F18E0157C05861564";
const feeTokenAddress = "0x36Ed51Afc79619b299b238898E72ce482600568a"; // wUST const feeTokenAddress = "0x6336c2dA64408Fcc277e0E1104aC6c34c431464c"; // wormUSD
const tokenBridgeAddress = "0xF890982f9310df57d00f659cf4fd87e65adEd8d7"; const tokenBridgeAddress = "0xF890982f9310df57d00f659cf4fd87e65adEd8d7";
const wrappedEthAddress = "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6"; const wrappedEthAddress = "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6";
await deployer.deploy(SwapHelper); await deployer.deploy(SwapHelper);
await deployer.link(SwapHelper, CrossChainSwapV3); await deployer.link(SwapHelper, CrossChainSwapV3);
await deployer.deploy( await deployer.deploy(CrossChainSwapV3, routerAddress, feeTokenAddress, tokenBridgeAddress, wrappedEthAddress);
CrossChainSwapV3,
routerAddress,
feeTokenAddress,
tokenBridgeAddress,
wrappedEthAddress
);
// save the contract address somewhere // save the contract address somewhere
await fsp.mkdir(scriptsAddressPath, { recursive: true }); await fsp.mkdir(scriptsAddressPath, { recursive: true });

View File

@ -7,19 +7,13 @@ const scriptsAddressPath = "../react/src/addresses";
module.exports = async function (deployer, network) { module.exports = async function (deployer, network) {
const routerAddress = "0xa5E0829CaCEd8fFDD4De3c43696c57F7D7A678ff"; // quickwap const routerAddress = "0xa5E0829CaCEd8fFDD4De3c43696c57F7D7A678ff"; // quickwap
const feeTokenAddress = "0xe3a1c77e952b57b5883f6c906fc706fcc7d4392c"; // wUST const feeTokenAddress = "0xcf7BEE494B42cB5A902dF000158037Ad334eB4a7"; // wormUSD
const tokenBridgeAddress = "0x377D55a7928c046E18eEbb61977e714d2a76472a"; const tokenBridgeAddress = "0x377D55a7928c046E18eEbb61977e714d2a76472a";
const wrappedMaticAddress = "0x9c3c9283d3e44854697cd22d3faa240cfb032889"; const wrappedMaticAddress = "0x9c3c9283d3e44854697cd22d3faa240cfb032889";
await deployer.deploy(SwapHelper); await deployer.deploy(SwapHelper);
await deployer.link(SwapHelper, CrossChainSwapV2); await deployer.link(SwapHelper, CrossChainSwapV2);
await deployer.deploy( await deployer.deploy(CrossChainSwapV2, routerAddress, feeTokenAddress, tokenBridgeAddress, wrappedMaticAddress);
CrossChainSwapV2,
routerAddress,
feeTokenAddress,
tokenBridgeAddress,
wrappedMaticAddress
);
// save the contract address somewhere // save the contract address somewhere
await fsp.mkdir(scriptsAddressPath, { recursive: true }); await fsp.mkdir(scriptsAddressPath, { recursive: true });

View File

@ -0,0 +1,5 @@
const Migrations = artifacts.require("Migrations");
module.exports = function (deployer) {
deployer.deploy(Migrations);
};

View File

@ -0,0 +1,11 @@
require("dotenv").config({ path: ".env" });
const WormUSD = artifacts.require("WormUSD");
module.exports = async function (deployer, network) {
const mintAddress = process.env.mintToAddress;
const tokenDecimals = process.env.decimals;
const tokenSupply = process.env.supply;
await deployer.deploy(WormUSD, mintAddress, tokenDecimals, tokenSupply);
};

View File

@ -1,7 +1,4 @@
import { import { getIsTransferCompletedEth, hexToUint8Array } from "@certusone/wormhole-sdk";
getIsTransferCompletedEth,
hexToUint8Array,
} from "@certusone/wormhole-sdk";
import { ethers } from "ethers"; import { ethers } from "ethers";
@ -54,35 +51,24 @@ export function loadEvmConfig(): EvmEnvironment[] {
} }
let key_contract_address: string = evm + "_CONTRACT_ADDRESS"; let key_contract_address: string = evm + "_CONTRACT_ADDRESS";
let val_contract_address: string = eval( let val_contract_address: string = eval("process.env." + key_contract_address);
"process.env." + key_contract_address
);
if (!val_contract_address) { if (!val_contract_address) {
logger.error("Missing environment variable " + key_contract_address); logger.error("Missing environment variable " + key_contract_address);
return undefined; return undefined;
} }
let key_token_bridge_address: string = evm + "_TOKEN_BRIDGE_ADDRESS"; let key_token_bridge_address: string = evm + "_TOKEN_BRIDGE_ADDRESS";
let val_token_bridge_address: string = eval( let val_token_bridge_address: string = eval("process.env." + key_token_bridge_address);
"process.env." + key_token_bridge_address
);
if (!val_token_bridge_address) { if (!val_token_bridge_address) {
logger.error("Missing environment variable " + key_token_bridge_address); logger.error("Missing environment variable " + key_token_bridge_address);
return undefined; return undefined;
} }
let key_wallet_private_key: string = evm + "_WALLET_PRIVATE_KEY"; let key_wallet_private_key: string = evm + "_WALLET_PRIVATE_KEY";
let val_wallet_private_key: string = eval( let val_wallet_private_key: string = eval("process.env." + key_wallet_private_key);
"process.env." + key_wallet_private_key if (!val_wallet_private_key) val_wallet_private_key = process.env.WALLET_PRIVATE_KEY;
);
if (!val_wallet_private_key)
val_wallet_private_key = process.env.WALLET_PRIVATE_KEY;
if (!val_wallet_private_key) { if (!val_wallet_private_key) {
logger.error( logger.error("Missing environment variable " + key_wallet_private_key + " or WALLET_PRIVATE_KEY");
"Missing environment variable " +
key_wallet_private_key +
" or WALLET_PRIVATE_KEY"
);
return undefined; return undefined;
} }
@ -170,72 +156,15 @@ function makeContractDataForEvm(env: EvmEnvironment): EvmContractData {
}; };
} }
export function isEvmContract( export function isEvmContract(contractAddress: string, chain_id: number): boolean {
contractAddress: string,
chain_id: number
): boolean {
let ecd = evmContractData.get(chain_id); let ecd = evmContractData.get(chain_id);
return ecd && ecd.contractAddress === contractAddress; return ecd && ecd.contractAddress === contractAddress;
} }
/*
// GOERLI_PROVIDER = Ethereum
// MUMBAI_PROVIDER = Polygon
if (t3Payload.contractAddress === CROSSCHAINSWAP_CONTRACT_ADDRESS_ETHEREUM) {
// Use one of the V3 swap methods.
} else if (t3Payload.contractAddress === CROSSCHAINSWAP_CONTRACT_ADDRESS_POLYGON) {
// Use one of the V2 swap methods.
} else {
// Error
}
if (t3Payload.swapFunctionType === 1 && t3Payload.swapCurrencyType === 1) {
// swapExactInFromVaaNative
} else if (t3Payload.swapFunctionType === 1 && t3Payload.swapCurrencyType === 2) {
// swapExactInFromVaaToken
} else if (
t3Payload.swapFunctionType === 2 && t3Payload.swapCurrencyType === 1) {
// swapExactOutFromVaaNative
} else if (t3Payload.swapFunctionType === 2 && t3Payload.swapCurrencyType === 2) {
// swapExactOutFromVaaToken
} else {
// error
}
*/
/*
// GOERLI_PROVIDER = Ethereum
// MUMBAI_PROVIDER = Polygon
if (t3Payload.contractAddress === CROSSCHAINSWAP_CONTRACT_ADDRESS_ETHEREUM) {
// Use one of the V3 swap methods.
} else if (t3Payload.contractAddress === CROSSCHAINSWAP_CONTRACT_ADDRESS_POLYGON) {
// Use one of the V2 swap methods.
} else {
// Error
}
if (t3Payload.swapFunctionType === 1 && t3Payload.swapCurrencyType === 1) {
// swapExactInFromVaaNative
} else if (t3Payload.swapFunctionType === 1 && t3Payload.swapCurrencyType === 2) {
// swapExactInFromVaaToken
} else if (
t3Payload.swapFunctionType === 2 && t3Payload.swapCurrencyType === 1) {
// swapExactOutFromVaaNative
} else if (t3Payload.swapFunctionType === 2 && t3Payload.swapCurrencyType === 2) {
// swapExactOutFromVaaToken
} else {
// error
}
*/
export async function relayVaaToEvm(vaaBytes: string, t3Payload: Type3Payload) { export async function relayVaaToEvm(vaaBytes: string, t3Payload: Type3Payload) {
let ecd = evmContractData.get(t3Payload.targetChainId); let ecd = evmContractData.get(t3Payload.targetChainId);
if (!ecd) { if (!ecd) {
logger.error( logger.error("relayVaaToEvm: chain id " + t3Payload.targetChainId + " does not exist!");
"relayVaaToEvm: chain id " + t3Payload.targetChainId + " does not exist!"
);
} }
let exactIn: boolean = false; let exactIn: boolean = false;
@ -244,51 +173,23 @@ export async function relayVaaToEvm(vaaBytes: string, t3Payload: Type3Payload) {
exactIn = true; exactIn = true;
} else if (t3Payload.swapFunctionType !== 2) { } else if (t3Payload.swapFunctionType !== 2) {
error = true; error = true;
logger.error( logger.error("relayVaaTo" + ecd.name + ": unsupported swapFunctionType: [" + t3Payload.swapFunctionType + "]");
"relayVaaTo" +
ecd.name +
": unsupported swapFunctionType: [" +
t3Payload.swapFunctionType +
"]"
);
} }
let native: boolean = false;
if (t3Payload.swapCurrencyType === 1) {
native = true;
} else if (t3Payload.swapCurrencyType !== 2) {
error = true;
logger.error(
"relayVaaTo" +
ecd.name +
": unsupported swapCurrencyType: [" +
t3Payload.swapCurrencyType +
"]"
);
}
if (error) return; if (error) return;
logger.debug( logger.debug(
"relayVaaTo" + "relayVaaTo" + ecd.name + ": chain_id: " + ecd.chain_id + ", contractAddress: [" + t3Payload.contractAddress + "]"
ecd.name +
": chain_id: " +
ecd.chain_id +
", contractAddress: [" +
t3Payload.contractAddress +
"]"
); );
const signedVaaArray = hexToUint8Array(vaaBytes); const signedVaaArray = hexToUint8Array(vaaBytes);
await relayVaaToEvmChain(t3Payload, ecd, signedVaaArray, exactIn, native); await relayVaaToEvmChain(t3Payload, ecd, signedVaaArray, exactIn);
} }
async function relayVaaToEvmChain( async function relayVaaToEvmChain(
t3Payload: Type3Payload, t3Payload: Type3Payload,
tcd: EvmContractData, tcd: EvmContractData,
signedVaaArray: Uint8Array, signedVaaArray: Uint8Array,
exactIn: boolean, exactIn: boolean
native: boolean
) { ) {
logger.debug( logger.debug(
"relayVaaTo" + "relayVaaTo" +
@ -312,8 +213,6 @@ async function relayVaaToEvmChain(
t3Payload.contractAddress + t3Payload.contractAddress +
"], exactIn: " + "], exactIn: " +
exactIn + exactIn +
", native: " +
native +
": completed: already transferred" ": completed: already transferred"
); );
@ -331,41 +230,17 @@ async function relayVaaToEvmChain(
t3Payload.contractAddress + t3Payload.contractAddress +
"], exactIn: " + "], exactIn: " +
exactIn + exactIn +
", native: " +
native +
": submitting redeem request" ": submitting redeem request"
); );
try { try {
let receipt: any = null; let receipt: any = null;
if (exactIn) { if (exactIn) {
if (native) { logger.debug("relayVaaTo: calling evmSwapExactInFromVaaNative()");
logger.debug("relayVaaTo: calling evmSwapExactInFromVaaNative()"); receipt = await swap.evmSwapExactInFromVaaNative(tcd.contractWithSigner, signedVaaArray);
receipt = await swap.evmSwapExactInFromVaaNative(
tcd.contractWithSigner,
signedVaaArray
);
} else {
logger.debug("relayVaaTo: calling evmSwapExactInFromVaaToken()");
receipt = await swap.evmSwapExactInFromVaaToken(
tcd.contractWithSigner,
signedVaaArray
);
}
} else { } else {
if (native) { logger.debug("relayVaaTo: calling evmSwapExactOutFromVaaNative()");
logger.debug("relayVaaTo: calling evmSwapExactOutFromVaaNative()"); receipt = await swap.evmSwapExactOutFromVaaNative(tcd.contractWithSigner, signedVaaArray);
receipt = await swap.evmSwapExactOutFromVaaNative(
tcd.contractWithSigner,
signedVaaArray
);
} else {
logger.debug("relayVaaTo: calling evmSwapExactOutFromVaaToken()");
receipt = await swap.evmSwapExactOutFromVaaToken(
tcd.contractWithSigner,
signedVaaArray
);
}
} }
logger.info( logger.info(
@ -379,8 +254,6 @@ async function relayVaaToEvmChain(
t3Payload.contractAddress + t3Payload.contractAddress +
"], exactIn: " + "], exactIn: " +
exactIn + exactIn +
", native: " +
native +
": completed: success, txHash: " + ": completed: success, txHash: " +
receipt.transactionHash receipt.transactionHash
); );
@ -397,8 +270,6 @@ async function relayVaaToEvmChain(
t3Payload.contractAddress + t3Payload.contractAddress +
"], exactIn: " + "], exactIn: " +
exactIn + exactIn +
", native: " +
native +
": completed: relay failed because the vaa has already been redeemed" ": completed: relay failed because the vaa has already been redeemed"
); );
@ -412,8 +283,6 @@ async function relayVaaToEvmChain(
t3Payload.contractAddress + t3Payload.contractAddress +
"], exactIn: " + "], exactIn: " +
exactIn + exactIn +
", native: " +
native +
": transaction failed: %o", ": transaction failed: %o",
e e
); );
@ -431,8 +300,6 @@ async function relayVaaToEvmChain(
t3Payload.contractAddress + t3Payload.contractAddress +
"], exactIn: " + "], exactIn: " +
exactIn + exactIn +
", native: " +
native +
": redeem confirmed" ": redeem confirmed"
); );
} else { } else {
@ -447,29 +314,18 @@ async function relayVaaToEvmChain(
t3Payload.contractAddress + t3Payload.contractAddress +
"], exactIn: " + "], exactIn: " +
exactIn + exactIn +
", native: " +
native +
": completed: failed to confirm redeem!" ": completed: failed to confirm redeem!"
); );
} }
} }
async function isRedeemedOnEvm( async function isRedeemedOnEvm(tcd: EvmContractData, signedVaaArray: Uint8Array): Promise<boolean> {
tcd: EvmContractData,
signedVaaArray: Uint8Array
): Promise<boolean> {
let redeemed: boolean = false; let redeemed: boolean = false;
try { try {
redeemed = await getIsTransferCompletedEth( redeemed = await getIsTransferCompletedEth(tcd.tokenBridgeAddress, tcd.provider, signedVaaArray);
tcd.tokenBridgeAddress,
tcd.provider,
signedVaaArray
);
} catch (e) { } catch (e) {
logger.error( logger.error(
"relayVaaTo" + "relayVaaTo" + tcd.name + ": failed to check if transfer is already complete, will attempt the transfer, e: %o",
tcd.name +
": failed to check if transfer is already complete, will attempt the transfer, e: %o",
e e
); );
} }

View File

@ -12,33 +12,11 @@ import {
getEmitterAddressTerra, getEmitterAddressTerra,
} from "@certusone/wormhole-sdk"; } from "@certusone/wormhole-sdk";
import { import { importCoreWasm, setDefaultWasm } from "@certusone/wormhole-sdk/lib/cjs/solana/wasm";
importCoreWasm, import { createSpyRPCServiceClient, subscribeSignedVAA } from "@certusone/wormhole-spydk";
setDefaultWasm,
} from "@certusone/wormhole-sdk/lib/cjs/solana/wasm";
import {
createSpyRPCServiceClient,
subscribeSignedVAA,
} from "@certusone/wormhole-spydk";
import { ethers } from "ethers"; import { ethers } from "ethers";
import { EvmEnvironment, isEvmContract, loadEvmConfig, makeEvmContractData, relayVaaToEvm } from "./evm";
import { import { isTerraContract, loadTerraConfig, makeTerraContractData, relayVaaToTerra, TerraEnvironment } from "./terra";
EvmEnvironment,
isEvmContract,
loadEvmConfig,
makeEvmContractData,
relayVaaToEvm,
} from "./evm";
import {
isTerraContract,
loadTerraConfig,
makeTerraContractData,
relayVaaToTerra,
TerraEnvironment,
} from "./terra";
export let logger: any; export let logger: any;
@ -66,7 +44,6 @@ export type Type3Payload = {
contractAddress: string; contractAddress: string;
relayerFee: ethers.BigNumber; relayerFee: ethers.BigNumber;
swapFunctionType: number; swapFunctionType: number;
swapCurrencyType: number;
}; };
type PendingEvent = { type PendingEvent = {
@ -88,11 +65,7 @@ let condition = new CondVar();
let pendingQueue = new Array<PendingEvent>(); let pendingQueue = new Array<PendingEvent>();
if (success) { if (success) {
logger.info( logger.info("swap_relayer starting up, will listen for signed VAAs from [" + env.spy_host + "]");
"swap_relayer starting up, will listen for signed VAAs from [" +
env.spy_host +
"]"
);
try { try {
makeEvmContractData(env.evm_configs); makeEvmContractData(env.evm_configs);
@ -143,10 +116,7 @@ async function spy_listen() {
var myFilters = []; var myFilters = [];
for (var i = 0; i < parsedJsonFilters.length; i++) { for (var i = 0; i < parsedJsonFilters.length; i++) {
var myChainId = parseInt(parsedJsonFilters[i].chain_id) as ChainId; var myChainId = parseInt(parsedJsonFilters[i].chain_id) as ChainId;
var myEmitterAddress = await encodeEmitterAddress( var myEmitterAddress = await encodeEmitterAddress(myChainId, parsedJsonFilters[i].emitter_address);
myChainId,
parsedJsonFilters[i].emitter_address
);
var myEmitterFilter = { var myEmitterFilter = {
emitterFilter: { emitterFilter: {
chainId: myChainId, chainId: myChainId,
@ -182,10 +152,7 @@ async function spy_listen() {
})(); })();
} }
async function encodeEmitterAddress( async function encodeEmitterAddress(myChainId, emitterAddressStr): Promise<string> {
myChainId,
emitterAddressStr
): Promise<string> {
if (myChainId === CHAIN_ID_SOLANA) { if (myChainId === CHAIN_ID_SOLANA) {
return await getEmitterAddressSolana(emitterAddressStr); return await getEmitterAddressSolana(emitterAddressStr);
} }
@ -206,17 +173,11 @@ async function processVaa(vaaBytes: string) {
let emitter_address: string = uint8ArrayToHex(parsedVAA.emitter_address); let emitter_address: string = uint8ArrayToHex(parsedVAA.emitter_address);
let seqNumKey: string = let seqNumKey: string = parsedVAA.emitter_chain.toString() + ":" + emitter_address;
parsedVAA.emitter_chain.toString() + ":" + emitter_address;
let lastSeqNum = seqMap.get(seqNumKey); let lastSeqNum = seqMap.get(seqNumKey);
if (lastSeqNum) { if (lastSeqNum) {
if (lastSeqNum >= parsedVAA.sequence) { if (lastSeqNum >= parsedVAA.sequence) {
logger.debug( logger.debug("ignoring duplicate: emitter: [" + seqNumKey + "], seqNum: " + parsedVAA.sequence);
"ignoring duplicate: emitter: [" +
seqNumKey +
"], seqNum: " +
parsedVAA.sequence
);
return; return;
} }
} }
@ -248,8 +209,6 @@ async function processVaa(vaaBytes: string) {
t3Payload.relayerFee + t3Payload.relayerFee +
"], swapFunctionType: [" + "], swapFunctionType: [" +
t3Payload.swapFunctionType + t3Payload.swapFunctionType +
"], swapCurrencyType: [" +
t3Payload.swapCurrencyType +
"]" "]"
); );
@ -270,8 +229,6 @@ async function processVaa(vaaBytes: string) {
t3Payload.relayerFee + t3Payload.relayerFee +
"], swapFunctionType: [" + "], swapFunctionType: [" +
t3Payload.swapFunctionType + t3Payload.swapFunctionType +
"], swapCurrencyType: [" +
t3Payload.swapCurrencyType +
"]" "]"
); );
} }
@ -289,10 +246,7 @@ async function processVaa(vaaBytes: string) {
} }
} }
function decodeSignedVAAPayloadType3( function decodeSignedVAAPayloadType3(parsedVAA: any, sourceChainId: number): Type3Payload {
parsedVAA: any,
sourceChainId: number
): Type3Payload {
const payload = Buffer.from(new Uint8Array(parsedVAA.payload)); const payload = Buffer.from(new Uint8Array(parsedVAA.payload));
if (payload[0] !== 3) return undefined; if (payload[0] !== 3) return undefined;
@ -310,18 +264,13 @@ function decodeSignedVAAPayloadType3(
let contractAddress: string = ""; let contractAddress: string = "";
let swapFunctionType: number = 0; let swapFunctionType: number = 0;
let swapCurrencyType: number = 0;
if (targetChainId === 3) { if (targetChainId === 3) {
logger.info( logger.info("decodeSignedVAAPayloadType3: terraContractAddr: [" + payload.slice(67, 67 + 32).toString("hex") + "]");
"decodeSignedVAAPayloadType3: terraContractAddr: [" +
payload.slice(67, 67 + 32).toString("hex") +
"]"
);
contractAddress = payload.slice(67, 67 + 32).toString("hex"); contractAddress = payload.slice(67, 67 + 32).toString("hex");
} else { } else {
if (payload.length < 262) { if (payload.length < 272) {
logger.error( logger.error(
"decodeSignedVAAPayloadType3: dropping type 3 vaa because the payload is too short to extract the contract fields, length: " + "decodeSignedVAAPayloadType3: dropping type 3 vaa because the payload is too short to extract the contract fields, length: " +
payload.length + payload.length +
@ -330,34 +279,24 @@ function decodeSignedVAAPayloadType3(
); );
return undefined; return undefined;
} }
contractAddress = payload.slice(79, 79 + 20).toString("hex"); contractAddress = payload.slice(79, 79 + 20).toString("hex");
swapFunctionType = payload.readUInt8(272); swapFunctionType = payload.readUInt8(272);
swapCurrencyType = payload.readUInt8(273);
} }
return { return {
sourceChainId: sourceChainId, sourceChainId: sourceChainId,
targetChainId: targetChainId, targetChainId: targetChainId,
contractAddress: contractAddress, contractAddress: contractAddress,
relayerFee: ethers.BigNumber.from(payload.slice(101, 101 + 32)), relayerFee: ethers.BigNumber.from(payload.slice(273, 273 + 32)),
swapFunctionType: swapFunctionType, swapFunctionType: swapFunctionType,
swapCurrencyType: swapCurrencyType,
}; };
} }
function isOurContract(contractAddress: string, chainId: number): boolean { function isOurContract(contractAddress: string, chainId: number): boolean {
return ( return isEvmContract(contractAddress, chainId) || isTerraContract(contractAddress, chainId);
isEvmContract(contractAddress, chainId) ||
isTerraContract(contractAddress, chainId)
);
} }
async function postVaa( async function postVaa(vaaBytes: any, t3Payload: Type3Payload, receiveTime: Date) {
vaaBytes: any,
t3Payload: Type3Payload,
receiveTime: Date
) {
let event: PendingEvent = { let event: PendingEvent = {
vaaBytes: vaaBytes, vaaBytes: vaaBytes,
t3Payload: t3Payload, t3Payload: t3Payload,
@ -366,9 +305,7 @@ async function postVaa(
await mutex.runExclusive(() => { await mutex.runExclusive(() => {
pendingQueue.push(event); pendingQueue.push(event);
logger.debug( logger.debug("posting event, there are now " + pendingQueue.length + " enqueued events");
"posting event, there are now " + pendingQueue.length + " enqueued events"
);
if (condition) { if (condition) {
logger.debug("hitting condition variable."); logger.debug("hitting condition variable.");
condition.complete(true); condition.complete(true);
@ -419,16 +356,12 @@ async function callBack(err: any, result: any) {
await mutex.runExclusive(async () => { await mutex.runExclusive(async () => {
if (pendingQueue.length === 0) { if (pendingQueue.length === 0) {
logger.debug( logger.debug("in callback, no more pending events, rearming the condition.");
"in callback, no more pending events, rearming the condition."
);
done = true; done = true;
condition = new CondVar(); condition = new CondVar();
await condition.wait(COND_VAR_TIMEOUT, callBack); await condition.wait(COND_VAR_TIMEOUT, callBack);
} else { } else {
logger.debug( logger.debug("in callback, there are " + pendingQueue.length + " pending events.");
"in callback, there are " + pendingQueue.length + " pending events."
);
} }
}); });
} }
@ -455,8 +388,7 @@ function initLogger() {
let logFileName: string = ""; let logFileName: string = "";
if (process.env.LOG_DIR) { if (process.env.LOG_DIR) {
useConsole = false; useConsole = false;
logFileName = logFileName = process.env.LOG_DIR + "/swap_relay." + new Date().toISOString() + ".log";
process.env.LOG_DIR + "/swap_relay." + new Date().toISOString() + ".log";
} }
let logLevel = "info"; let logLevel = "info";
@ -472,11 +404,7 @@ function initLogger() {
level: logLevel, level: logLevel,
}); });
} else { } else {
console.log( console.log("swap_relay is logging to [%s] at level [%s]", logFileName, logLevel);
"swap_relay is logging to [%s] at level [%s]",
logFileName,
logLevel
);
transport = new winston.transports.File({ transport = new winston.transports.File({
filename: logFileName, filename: logFileName,
@ -492,9 +420,7 @@ function initLogger() {
winston.format.timestamp({ winston.format.timestamp({
format: "YYYY-MM-DD HH:mm:ss.SSS", format: "YYYY-MM-DD HH:mm:ss.SSS",
}), }),
winston.format.printf( winston.format.printf((info: any) => `${[info.timestamp]}|${info.level}|${info.message}`)
(info: any) => `${[info.timestamp]}|${info.level}|${info.message}`
)
), ),
}; };