Merge pull request #3 from certusone/evm-payload3-update
Evm payload3 update
This commit is contained in:
commit
37649e5eaa
|
@ -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:
|
||||||
|
@ -112,13 +112,13 @@ module.exports = {
|
||||||
// $ 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'
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
};
|
};
|
||||||
|
|
|
@ -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'
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
};
|
|
@ -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,
|
||||||
|
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;
|
|
||||||
|
|
||||||
// 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
|
||||||
);
|
);
|
||||||
|
|
|
@ -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,72 +281,7 @@ 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
|
||||||
|
@ -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
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,7 +6,6 @@ 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
|
||||||
|
@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
npx truffle migrate --config cfg/truffle-config.tokens.js --network goerli --reset
|
|
@ -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
|
// save the contract address somewhere
|
||||||
await fsp.mkdir(scriptsAddressPath, { recursive: true });
|
await fsp.mkdir(scriptsAddressPath, { recursive: true });
|
||||||
|
|
||||||
await fsp.writeFile(
|
await fsp.writeFile(
|
||||||
`${scriptsAddressPath}/${network}.ts`,
|
`${scriptsAddressPath}/${network}.ts`,
|
||||||
`export const SWAP_CONTRACT_ADDRESS = '${CrossChainSwapV2.address}';`
|
`export const SWAP_CONTRACT_ADDRESS = '${CrossChainSwapV2.address}';`
|
||||||
);
|
);
|
||||||
|
|
||||||
//deployer.link(ConvertLib, MetaCoin);
|
//deployer.link(ConvertLib, MetaCoin);
|
||||||
//deployer.deploy(MetaCoin);
|
//deployer.deploy(MetaCoin);
|
||||||
|
|
|
@ -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
|
// save the contract address somewhere
|
||||||
await fsp.mkdir(scriptsAddressPath, { recursive: true });
|
await fsp.mkdir(scriptsAddressPath, { recursive: true });
|
||||||
|
|
||||||
await fsp.writeFile(
|
await fsp.writeFile(
|
||||||
`${scriptsAddressPath}/${network}.ts`,
|
`${scriptsAddressPath}/${network}.ts`,
|
||||||
`export const SWAP_CONTRACT_ADDRESS = '${CrossChainSwapV2.address}';`
|
`export const SWAP_CONTRACT_ADDRESS = '${CrossChainSwapV2.address}';`
|
||||||
);
|
);
|
||||||
|
|
||||||
//deployer.link(ConvertLib, MetaCoin);
|
//deployer.link(ConvertLib, MetaCoin);
|
||||||
//deployer.deploy(MetaCoin);
|
//deployer.deploy(MetaCoin);
|
||||||
|
|
|
@ -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 });
|
||||||
|
|
|
@ -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 });
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
const Migrations = artifacts.require("Migrations");
|
||||||
|
|
||||||
|
module.exports = function (deployer) {
|
||||||
|
deployer.deploy(Migrations);
|
||||||
|
};
|
|
@ -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);
|
||||||
|
};
|
|
@ -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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}`
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue