Compare commits
No commits in common. "37649e5eaa61ca31c54feec719251c511c176875" and "2c062ef7f3197be89f49a0ad053164ec4d609f18" have entirely different histories.
37649e5eaa
...
2c062ef7f3
|
@ -1,6 +1,7 @@
|
|||
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
|
||||
|
@ -28,9 +29,9 @@ require("dotenv").config({ path: ".env" });
|
|||
// const mnemonic = fs.readFileSync('.secret').toString().trim();
|
||||
|
||||
module.exports = {
|
||||
contracts_directory: "./contracts",
|
||||
contracts_build_directory: "./build/contracts",
|
||||
migrations_directory: "./migrations/polygon",
|
||||
contracts_directory: './contracts',
|
||||
contracts_build_directory: './build/contracts',
|
||||
migrations_directory: './migrations/polygon',
|
||||
/**
|
||||
* 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
|
||||
|
@ -41,18 +42,18 @@ module.exports = {
|
|||
* $ truffle test --network <network-name>
|
||||
*/
|
||||
|
||||
networks: {
|
||||
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)
|
||||
},
|
||||
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
|
||||
|
@ -64,15 +65,14 @@ module.exports = {
|
|||
// },
|
||||
// Useful for deploying to a public network.
|
||||
// NB: It's important to wrap the provider as a function.
|
||||
mumbai: {
|
||||
provider: () => new HDWalletProvider(process.env.ETH_PRIVATE_KEY, process.env.MUMBAI_PROVIDER),
|
||||
network_id: 80001,
|
||||
gasPrice: 80000000000,
|
||||
gas: 7000000,
|
||||
//confirmations: 2, // # of confs to wait between deployments. (default: 0)
|
||||
timeoutBlocks: 50, // # of blocks before a deployment times out (minimum/default: 50)
|
||||
skipDryRun: true, // Skip dry run before migrations? (default: false for public nets )
|
||||
},
|
||||
mumbai: {
|
||||
provider: () => new HDWalletProvider(process.env.ETH_PRIVATE_KEY, process.env.MUMBAI_PROVIDER),
|
||||
network_id: 80001,
|
||||
//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`),
|
||||
|
@ -89,16 +89,16 @@ module.exports = {
|
|||
// Configure your compilers
|
||||
compilers: {
|
||||
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)
|
||||
// settings: { // See the solidity docs for advice about optimization and evmVersion
|
||||
optimizer: {
|
||||
enabled: false,
|
||||
runs: 200,
|
||||
enabled: false,
|
||||
runs: 200
|
||||
},
|
||||
// evmVersion: 'byzantium'
|
||||
// }
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
// Truffle DB is currently disabled by default; to enable it, change enabled:
|
||||
|
@ -108,17 +108,17 @@ module.exports = {
|
|||
// NOTE: It is not possible to migrate your contracts to truffle DB and you should
|
||||
// make a backup of your artifacts to a safe location before enabling this feature.
|
||||
//
|
||||
// After you backed up your artifacts you can utilize db by running migrate as follows:
|
||||
// After you backed up your artifacts you can utilize db by running migrate as follows:
|
||||
// $ truffle migrate --reset --compile-all
|
||||
//
|
||||
// db: {
|
||||
// enabled: false,
|
||||
// host: '127.0.0.1',
|
||||
// adapter: {
|
||||
// name: 'sqlite',
|
||||
// settings: {
|
||||
// directory: '.db'
|
||||
// }
|
||||
// }
|
||||
// enabled: false,
|
||||
// host: '127.0.0.1',
|
||||
// adapter: {
|
||||
// name: 'sqlite',
|
||||
// settings: {
|
||||
// directory: '.db'
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
};
|
||||
|
|
|
@ -1,123 +0,0 @@
|
|||
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,10 +3,8 @@
|
|||
pragma solidity ^0.7.6;
|
||||
pragma abicoder v2;
|
||||
|
||||
import './shared/IWormhole.sol';
|
||||
import './shared/SwapHelper.sol';
|
||||
import './shared/TokenBridge.sol';
|
||||
import './shared/WETH.sol';
|
||||
import './IWormhole.sol';
|
||||
import './SwapHelper.sol';
|
||||
import 'solidity-bytes-utils/contracts/BytesLib.sol';
|
||||
import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
|
||||
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
|
||||
|
@ -14,18 +12,45 @@ import '@uniswap/v3-periphery/contracts/libraries/TransferHelper.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
|
||||
/// @notice Swaps against UniswapV2 pools and uses Wormhole TokenBridge
|
||||
/// for cross-chain transfers
|
||||
contract CrossChainSwapV2 {
|
||||
using SafeERC20 for IERC20;
|
||||
using BytesLib for bytes;
|
||||
uint8 public immutable TypeExactIn = 1;
|
||||
uint8 public immutable TypeExactOut = 2;
|
||||
IUniswapV2Router02 public immutable SWAP_ROUTER;
|
||||
address public immutable FEE_TOKEN_ADDRESS;
|
||||
address public immutable TOKEN_BRIDGE_ADDRESS;
|
||||
address public immutable WRAPPED_NATIVE;
|
||||
uint8 public immutable typeExactIn = 1;
|
||||
uint8 public immutable typeExactOut = 2;
|
||||
uint8 public immutable typeNativeSwap = 1;
|
||||
uint8 public immutable typeTokenSwap = 2;
|
||||
uint16 public immutable expectedVaaLength = 274;
|
||||
uint8 public immutable terraChainId = 3;
|
||||
IUniswapV2Router02 public immutable swapRouter;
|
||||
address public immutable feeTokenAddress;
|
||||
address public immutable tokenBridgeAddress;
|
||||
address public immutable wrappedNative;
|
||||
|
||||
constructor(
|
||||
address _swapRouterAddress,
|
||||
|
@ -33,10 +58,10 @@ contract CrossChainSwapV2 {
|
|||
address _tokenBridgeAddress,
|
||||
address _wrappedNativeAddress
|
||||
) {
|
||||
SWAP_ROUTER = IUniswapV2Router02(_swapRouterAddress);
|
||||
FEE_TOKEN_ADDRESS = _feeTokenAddress;
|
||||
TOKEN_BRIDGE_ADDRESS = _tokenBridgeAddress;
|
||||
WRAPPED_NATIVE = _wrappedNativeAddress;
|
||||
swapRouter = IUniswapV2Router02(_swapRouterAddress);
|
||||
feeTokenAddress = _feeTokenAddress;
|
||||
tokenBridgeAddress = _tokenBridgeAddress;
|
||||
wrappedNative = _wrappedNativeAddress;
|
||||
}
|
||||
|
||||
/// @dev Used to communicate information about executed swaps to UI/user
|
||||
|
@ -52,12 +77,20 @@ contract CrossChainSwapV2 {
|
|||
/// instructions after redeeming the VAA from the TokenBridge
|
||||
function _getParsedPayload(
|
||||
bytes calldata encodedVaa,
|
||||
uint8 swapFunctionType
|
||||
uint8 swapFunctionType,
|
||||
uint8 swapCurrencyType,
|
||||
address feeRecipient
|
||||
) private returns (SwapHelper.DecodedVaaParameters memory payload) {
|
||||
// complete the transfer on the token bridge
|
||||
bytes memory vmPayload = TokenBridge(
|
||||
TOKEN_BRIDGE_ADDRESS
|
||||
).completeTransferWithPayload(encodedVaa);
|
||||
tokenBridgeAddress
|
||||
).completeTransferWithPayload(encodedVaa, feeRecipient);
|
||||
|
||||
// make sure payload is the right size
|
||||
require(
|
||||
vmPayload.length==expectedVaaLength,
|
||||
"VAA has the wrong number of bytes"
|
||||
);
|
||||
|
||||
// parse the payload
|
||||
payload = SwapHelper.decodeVaaPayload(vmPayload);
|
||||
|
@ -67,19 +100,42 @@ contract CrossChainSwapV2 {
|
|||
payload.swapFunctionType==swapFunctionType,
|
||||
"incorrect swapFunctionType in payload"
|
||||
);
|
||||
require(
|
||||
payload.swapCurrencyType==swapCurrencyType,
|
||||
"incorrect swapCurrencyType in payload"
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Executes exactIn native asset swap and pays the relayer
|
||||
function recvAndSwapExactNativeIn(
|
||||
bytes calldata encodedVaa
|
||||
) 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
|
||||
SwapHelper.DecodedVaaParameters memory payload =
|
||||
_getParsedPayload(
|
||||
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
|
||||
// uniswap won't take fixed size array
|
||||
address[] memory uniPath = new address[](2);
|
||||
|
@ -88,39 +144,32 @@ contract CrossChainSwapV2 {
|
|||
|
||||
// sanity check path
|
||||
require(
|
||||
uniPath[0]==FEE_TOKEN_ADDRESS,
|
||||
"tokenIn must be feeToken"
|
||||
uniPath[0]==feeTokenAddress,
|
||||
"tokenIn must be UST"
|
||||
);
|
||||
require(
|
||||
uniPath[1]==WRAPPED_NATIVE,
|
||||
uniPath[1]==wrappedNative,
|
||||
"tokenOut must be wrapped native asset"
|
||||
);
|
||||
|
||||
// approve the router to spend tokens
|
||||
TransferHelper.safeApprove(
|
||||
uniPath[0],
|
||||
address(SWAP_ROUTER),
|
||||
payload.swapAmount
|
||||
address(swapRouter),
|
||||
swapAmountLessFees
|
||||
);
|
||||
|
||||
// try to execute the swap
|
||||
try SWAP_ROUTER.swapExactTokensForTokens(
|
||||
payload.swapAmount,
|
||||
try swapRouter.swapExactTokensForTokens(
|
||||
swapAmountLessFees,
|
||||
payload.estimatedAmount,
|
||||
uniPath,
|
||||
address(this),
|
||||
payload.deadline
|
||||
) returns (uint256[] memory amounts) {
|
||||
// calculate how much to pay the relayer in the native token
|
||||
uint256 nativeRelayerFee = amounts[1] * payload.relayerFee / payload.swapAmount;
|
||||
uint256 nativeAmountOut = amounts[1] - nativeRelayerFee;
|
||||
|
||||
// unwrap native and send to recipient
|
||||
IWETH(WRAPPED_NATIVE).withdraw(amounts[1]);
|
||||
payable(payload.recipientAddress).transfer(nativeAmountOut);
|
||||
|
||||
/// pay the relayer in the native token
|
||||
payable(msg.sender).transfer(nativeRelayerFee);
|
||||
IWETH(wrappedNative).withdraw(amounts[1]);
|
||||
payable(payload.recipientAddress).transfer(amounts[1]);
|
||||
|
||||
// used in UI to tell user they're getting
|
||||
// their desired token
|
||||
|
@ -128,104 +177,84 @@ contract CrossChainSwapV2 {
|
|||
payload.recipientAddress,
|
||||
uniPath[1],
|
||||
msg.sender,
|
||||
nativeAmountOut,
|
||||
amounts[1],
|
||||
1
|
||||
);
|
||||
return amounts;
|
||||
} catch {
|
||||
// pay relayer in the feeToken since the swap failed
|
||||
IERC20 feeToken = IERC20(FEE_TOKEN_ADDRESS);
|
||||
feeToken.safeTransfer(msg.sender, payload.relayerFee);
|
||||
|
||||
// swap failed - return feeToken to recipient
|
||||
feeToken.safeTransfer(
|
||||
// swap failed - return UST to recipient
|
||||
IERC20(feeTokenAddress).safeTransfer(
|
||||
payload.recipientAddress,
|
||||
payload.swapAmount - payload.relayerFee
|
||||
swapAmountLessFees
|
||||
);
|
||||
|
||||
// used in UI to tell user they're getting
|
||||
// feeToken instead of their desired native asset
|
||||
// UST instead of their desired native asset
|
||||
emit SwapResult(
|
||||
payload.recipientAddress,
|
||||
uniPath[0],
|
||||
msg.sender,
|
||||
payload.swapAmount - payload.relayerFee,
|
||||
swapAmountLessFees,
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Executes exactOut native asset swap and pays the relayer
|
||||
function recvAndSwapExactNativeOut(
|
||||
/// @dev Executes exactIn token swap and pays the relayer
|
||||
function recvAndSwapExactIn(
|
||||
bytes calldata encodedVaa
|
||||
) external returns (uint256 amountInUsed) {
|
||||
// redeem and fetch parsed payload
|
||||
) external 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 the parsed payload
|
||||
SwapHelper.DecodedVaaParameters memory payload =
|
||||
_getParsedPayload(
|
||||
encodedVaa,
|
||||
TypeExactOut
|
||||
typeExactIn,
|
||||
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
|
||||
address[] memory uniPath = new address[](2);
|
||||
uniPath[0] = payload.path[0];
|
||||
uniPath[1] = payload.path[1];
|
||||
|
||||
// sanity check path
|
||||
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;
|
||||
// make sure first element in path is UST
|
||||
require(uniPath[0]==feeTokenAddress, "tokenIn must be UST");
|
||||
|
||||
// approve the router to spend tokens
|
||||
TransferHelper.safeApprove(
|
||||
uniPath[0],
|
||||
address(SWAP_ROUTER),
|
||||
maxAmountInLessFees
|
||||
);
|
||||
address(swapRouter),
|
||||
swapAmountLessFees
|
||||
);
|
||||
|
||||
// try to perform the swap
|
||||
try SWAP_ROUTER.swapTokensForExactTokens(
|
||||
amountOut,
|
||||
maxAmountInLessFees,
|
||||
// try to perform the swap
|
||||
try swapRouter.swapExactTokensForTokens(
|
||||
swapAmountLessFees,
|
||||
payload.estimatedAmount,
|
||||
uniPath,
|
||||
address(this),
|
||||
payload.recipientAddress,
|
||||
payload.deadline
|
||||
) 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
|
||||
// their desired native asset
|
||||
// their desired token
|
||||
emit SwapResult(
|
||||
payload.recipientAddress,
|
||||
uniPath[1],
|
||||
|
@ -233,28 +262,28 @@ contract CrossChainSwapV2 {
|
|||
amounts[1],
|
||||
1
|
||||
);
|
||||
return amountInUsed;
|
||||
return amounts;
|
||||
} catch {
|
||||
// swap failed - return feeToken to recipient
|
||||
IERC20(FEE_TOKEN_ADDRESS).safeTransfer(
|
||||
// swap failed - return UST to recipient
|
||||
IERC20(feeTokenAddress).safeTransfer(
|
||||
payload.recipientAddress,
|
||||
maxAmountInLessFees
|
||||
swapAmountLessFees
|
||||
);
|
||||
|
||||
// used in UI to tell user they're getting
|
||||
// feeToken instead of their desired native asset
|
||||
// UST instead of their desired token
|
||||
emit SwapResult(
|
||||
payload.recipientAddress,
|
||||
uniPath[0],
|
||||
msg.sender,
|
||||
maxAmountInLessFees,
|
||||
swapAmountLessFees,
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// @dev Executes exactIn native asset swap
|
||||
/// @dev Executes exactIn native asset and token swaps before
|
||||
/// sending a custom payload to the TokenBridge
|
||||
function _swapExactInBeforeTransfer(
|
||||
uint256 amountIn,
|
||||
uint256 amountOutMinimum,
|
||||
|
@ -265,12 +294,12 @@ contract CrossChainSwapV2 {
|
|||
// approve the router to spend tokens
|
||||
TransferHelper.safeApprove(
|
||||
path[0],
|
||||
address(SWAP_ROUTER),
|
||||
address(swapRouter),
|
||||
amountIn
|
||||
);
|
||||
|
||||
// perform the swap
|
||||
uint256[] memory amounts = SWAP_ROUTER.swapExactTokensForTokens(
|
||||
uint256[] memory amounts = swapRouter.swapExactTokensForTokens(
|
||||
amountIn,
|
||||
amountOutMinimum,
|
||||
path,
|
||||
|
@ -280,6 +309,69 @@ contract CrossChainSwapV2 {
|
|||
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
|
||||
/// instructions for executing native asset swaps on the destination chain
|
||||
function swapExactNativeInAndTransfer(
|
||||
|
@ -295,17 +387,17 @@ contract CrossChainSwapV2 {
|
|||
"insufficient amountOutMinimum to pay relayer"
|
||||
);
|
||||
require(
|
||||
path[0]==WRAPPED_NATIVE,
|
||||
path[0]==wrappedNative,
|
||||
"tokenIn must be wrapped native asset for first swap"
|
||||
);
|
||||
require(
|
||||
path[1]==FEE_TOKEN_ADDRESS,
|
||||
"tokenOut must be feeToken for first swap"
|
||||
path[1]==feeTokenAddress,
|
||||
"tokenOut must be UST for first swap"
|
||||
);
|
||||
require(msg.value > 0, "must pass non 0 native asset amount");
|
||||
|
||||
// wrap native asset
|
||||
IWETH(WRAPPED_NATIVE).deposit{
|
||||
IWETH(wrappedNative).deposit{
|
||||
value : msg.value
|
||||
}();
|
||||
|
||||
|
@ -318,53 +410,276 @@ contract CrossChainSwapV2 {
|
|||
swapParams.deadline
|
||||
);
|
||||
|
||||
// create payload with target swap instructions
|
||||
bytes memory payload = abi.encodePacked(
|
||||
swapParams.targetAmountOutMinimum,
|
||||
swapParams.targetChainRecipient,
|
||||
path[2],
|
||||
path[3],
|
||||
swapParams.deadline,
|
||||
swapParams.poolFee,
|
||||
TypeExactIn,
|
||||
relayerFee
|
||||
);
|
||||
|
||||
// approve token bridge to spend feeTokens
|
||||
// create payload variable
|
||||
bytes memory payload;
|
||||
|
||||
// UST is native to Terra - no need for swap instructions
|
||||
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(
|
||||
FEE_TOKEN_ADDRESS,
|
||||
TOKEN_BRIDGE_ADDRESS,
|
||||
feeTokenAddress,
|
||||
tokenBridgeAddress,
|
||||
amountOut
|
||||
);
|
||||
|
||||
// send transfer with payload to the TokenBridge
|
||||
TokenBridge(TOKEN_BRIDGE_ADDRESS).transferTokensWithPayload(
|
||||
FEE_TOKEN_ADDRESS,
|
||||
TokenBridge(tokenBridgeAddress).transferTokensWithPayload(
|
||||
feeTokenAddress,
|
||||
amountOut,
|
||||
targetChainId,
|
||||
targetContractAddress,
|
||||
targetContractAddress,
|
||||
relayerFee,
|
||||
nonce,
|
||||
payload
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Executes exactOut native asset swaps
|
||||
|
||||
/// @dev Executes exactOut native asset swap and pays the relayer
|
||||
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(
|
||||
uint256 amountOut,
|
||||
uint256 amountInMaximum,
|
||||
address contractCaller,
|
||||
address[] calldata path,
|
||||
uint256 deadline
|
||||
uint256 deadline,
|
||||
uint8 swapType
|
||||
) internal {
|
||||
// approve the router to spend tokens
|
||||
TransferHelper.safeApprove(
|
||||
path[0],
|
||||
address(SWAP_ROUTER),
|
||||
address(swapRouter),
|
||||
amountInMaximum
|
||||
);
|
||||
|
||||
// perform the swap
|
||||
uint256[] memory amounts = SWAP_ROUTER.swapTokensForExactTokens(
|
||||
uint256[] memory amounts = swapRouter.swapTokensForExactTokens(
|
||||
amountOut,
|
||||
amountInMaximum,
|
||||
path,
|
||||
|
@ -377,17 +692,94 @@ contract CrossChainSwapV2 {
|
|||
|
||||
// refund contractCaller with any amountIn that wasn't spent
|
||||
if (amountInUsed < amountInMaximum) {
|
||||
// unwrap remaining native asset and send to contractCaller
|
||||
TransferHelper.safeApprove(path[0], address(SWAP_ROUTER), 0);
|
||||
IWETH(WRAPPED_NATIVE).withdraw(
|
||||
amountInMaximum - amountInUsed
|
||||
);
|
||||
payable(contractCaller).transfer(
|
||||
amountInMaximum - amountInUsed
|
||||
);
|
||||
TransferHelper.safeApprove(path[0], address(swapRouter), 0);
|
||||
if (swapType == typeTokenSwap) {
|
||||
// send remaining tokens to contractCaller
|
||||
IERC20 token = IERC20(path[0]);
|
||||
token.safeTransfer(
|
||||
contractCaller,
|
||||
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
|
||||
/// instructions for executing native asset swaps on the destination chain
|
||||
function swapExactNativeOutAndTransfer(
|
||||
|
@ -403,17 +795,17 @@ contract CrossChainSwapV2 {
|
|||
"insufficient amountOut to pay relayer"
|
||||
);
|
||||
require(
|
||||
path[0]==WRAPPED_NATIVE,
|
||||
path[0]==wrappedNative,
|
||||
"tokenIn must be wrapped native asset for first swap"
|
||||
);
|
||||
require(
|
||||
path[1]==FEE_TOKEN_ADDRESS,
|
||||
"tokenOut must be feeToken for first swap"
|
||||
path[1]==feeTokenAddress,
|
||||
"tokenOut must be UST for first swap"
|
||||
);
|
||||
require(msg.value > 0, "must pass non 0 native asset amount");
|
||||
|
||||
// wrap native asset
|
||||
IWETH(WRAPPED_NATIVE).deposit{
|
||||
IWETH(wrappedNative).deposit{
|
||||
value : msg.value
|
||||
}();
|
||||
|
||||
|
@ -423,34 +815,45 @@ contract CrossChainSwapV2 {
|
|||
msg.value,
|
||||
msg.sender,
|
||||
path[0:2],
|
||||
swapParams.deadline
|
||||
swapParams.deadline,
|
||||
typeNativeSwap
|
||||
);
|
||||
|
||||
// create payload with target swap instructions
|
||||
bytes memory payload = abi.encodePacked(
|
||||
swapParams.targetAmountOut,
|
||||
swapParams.targetChainRecipient,
|
||||
path[2],
|
||||
path[3],
|
||||
swapParams.deadline,
|
||||
swapParams.poolFee,
|
||||
TypeExactOut,
|
||||
relayerFee
|
||||
);
|
||||
// create payload variable
|
||||
bytes memory payload;
|
||||
|
||||
// approve token bridge to spend feeTokens
|
||||
// UST is native to Terra - no need for swap instructions
|
||||
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(
|
||||
FEE_TOKEN_ADDRESS,
|
||||
TOKEN_BRIDGE_ADDRESS,
|
||||
feeTokenAddress,
|
||||
tokenBridgeAddress,
|
||||
swapParams.amountOut
|
||||
);
|
||||
|
||||
// send transfer with payload to the TokenBridge
|
||||
TokenBridge(TOKEN_BRIDGE_ADDRESS).transferTokensWithPayload(
|
||||
FEE_TOKEN_ADDRESS,
|
||||
TokenBridge(tokenBridgeAddress).transferTokensWithPayload(
|
||||
feeTokenAddress,
|
||||
swapParams.amountOut,
|
||||
targetChainId,
|
||||
targetContractAddress,
|
||||
relayerFee,
|
||||
nonce,
|
||||
payload
|
||||
);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -6,11 +6,12 @@ pragma abicoder v2;
|
|||
import './IWormhole.sol';
|
||||
import 'solidity-bytes-utils/contracts/BytesLib.sol';
|
||||
|
||||
|
||||
/// @title Helper library for cross-chain swaps
|
||||
/// @notice Contains functions necessary for parsing encoded VAAs
|
||||
/// and structs containing swap parameters
|
||||
library SwapHelper {
|
||||
using BytesLib for bytes;
|
||||
using BytesLib for bytes;
|
||||
|
||||
/// @dev Parameters needed for exactIn swap type
|
||||
struct ExactInParameters {
|
||||
|
@ -39,14 +40,14 @@ library SwapHelper {
|
|||
uint8 version;
|
||||
uint256 swapAmount;
|
||||
address contractAddress;
|
||||
bytes32 fromAddress;
|
||||
uint256 relayerFee;
|
||||
uint256 estimatedAmount;
|
||||
address recipientAddress;
|
||||
address[2] path;
|
||||
uint256 deadline;
|
||||
uint24 poolFee;
|
||||
uint8 swapFunctionType;
|
||||
uint256 relayerFee;
|
||||
uint8 swapCurrencyType;
|
||||
}
|
||||
|
||||
/// @dev Decodes parameters encoded in a VAA
|
||||
|
@ -70,7 +71,7 @@ library SwapHelper {
|
|||
// skip
|
||||
index += 2;
|
||||
|
||||
decoded.fromAddress = vmPayload.toBytes32(index);
|
||||
decoded.relayerFee = vmPayload.toUint256(index);
|
||||
index += 32;
|
||||
|
||||
decoded.estimatedAmount = vmPayload.toUint256(index);
|
||||
|
@ -97,9 +98,6 @@ library SwapHelper {
|
|||
decoded.swapFunctionType = vmPayload.toUint8(index);
|
||||
index += 1;
|
||||
|
||||
decoded.relayerFee = vmPayload.toUint256(index);
|
||||
index += 32;
|
||||
|
||||
require(vmPayload.length == index, "invalid payload length");
|
||||
decoded.swapCurrencyType = vmPayload.toUint8(index);
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
// 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);
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
// 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;
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
// 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);
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
npx truffle migrate --config cfg/truffle-config.tokens.js --network goerli --reset
|
|
@ -5,23 +5,29 @@ const SwapHelper = artifacts.require("SwapHelper");
|
|||
|
||||
const scriptsAddressPath = "../react/src/addresses";
|
||||
|
||||
module.exports = async function (deployer, network) {
|
||||
const routerAddress = "0x7e3411b04766089cfaa52db688855356a12f05d1"; // hurricaneswap router
|
||||
const feeTokenAddress = "0x8F23C5BE43FBfE7a30c04e4eDEE3D18995Fe7E2d"; // wormUSD
|
||||
const tokenBridgeAddress = "0x61E44E506Ca5659E6c0bba9b678586fA2d729756";
|
||||
const wrappedAvaxAddress = "0x1d308089a2d1ced3f1ce36b1fcaf815b07217be3";
|
||||
module.exports = async function(deployer, network) {
|
||||
const routerAddress = "0x7e3411b04766089cfaa52db688855356a12f05d1"; // hurricaneswap
|
||||
const feeTokenAddress = "0xe09ed38e5cd1014444846f62376ac88c5232cde9"; // wUST
|
||||
const tokenBridgeAddress = "0x61E44E506Ca5659E6c0bba9b678586fA2d729756";
|
||||
const wrappedAvaxAddress = "0x1d308089a2d1ced3f1ce36b1fcaf815b07217be3";
|
||||
|
||||
await deployer.deploy(SwapHelper);
|
||||
await deployer.link(SwapHelper, CrossChainSwapV2);
|
||||
await deployer.deploy(CrossChainSwapV2, routerAddress, feeTokenAddress, tokenBridgeAddress, wrappedAvaxAddress);
|
||||
await deployer.deploy(SwapHelper);
|
||||
await deployer.link(SwapHelper, CrossChainSwapV2);
|
||||
await deployer.deploy(
|
||||
CrossChainSwapV2,
|
||||
routerAddress,
|
||||
feeTokenAddress,
|
||||
tokenBridgeAddress,
|
||||
wrappedAvaxAddress
|
||||
);
|
||||
|
||||
// save the contract address somewhere
|
||||
await fsp.mkdir(scriptsAddressPath, { recursive: true });
|
||||
|
||||
// save the contract address somewhere
|
||||
await fsp.mkdir(scriptsAddressPath, { recursive: true });
|
||||
|
||||
await fsp.writeFile(
|
||||
`${scriptsAddressPath}/${network}.ts`,
|
||||
`export const SWAP_CONTRACT_ADDRESS = '${CrossChainSwapV2.address}';`
|
||||
);
|
||||
await fsp.writeFile(
|
||||
`${scriptsAddressPath}/${network}.ts`,
|
||||
`export const SWAP_CONTRACT_ADDRESS = '${CrossChainSwapV2.address}';`
|
||||
);
|
||||
|
||||
//deployer.link(ConvertLib, MetaCoin);
|
||||
//deployer.deploy(MetaCoin);
|
||||
|
|
|
@ -5,23 +5,29 @@ const SwapHelper = artifacts.require("SwapHelper");
|
|||
|
||||
const scriptsAddressPath = "../react/src/addresses";
|
||||
|
||||
module.exports = async function (deployer, network) {
|
||||
const routerAddress = "0x9Ac64Cc6e4415144C455BD8E4837Fea55603e5c3"; // pancakeswap
|
||||
const feeTokenAddress = "0x2A29D46D7e0B997358E9726DA0210Af212f2dfd7"; // wormUSD
|
||||
const tokenBridgeAddress = "0x9dcF9D205C9De35334D646BeE44b2D2859712A09";
|
||||
const wrappedBnbAddress = "0xae13d989dac2f0debff460ac112a837c89baa7cd";
|
||||
module.exports = async function(deployer, network) {
|
||||
const routerAddress = "0x9Ac64Cc6e4415144C455BD8E4837Fea55603e5c3"; // pancakeswap
|
||||
const feeTokenAddress = "0x7b8eae1e85c8b189ee653d3f78733f4f788bb2c1"; // wUST
|
||||
const tokenBridgeAddress = "0x9dcF9D205C9De35334D646BeE44b2D2859712A09";
|
||||
const wrappedBnbAddress = "0xae13d989dac2f0debff460ac112a837c89baa7cd";
|
||||
|
||||
await deployer.deploy(SwapHelper);
|
||||
await deployer.link(SwapHelper, CrossChainSwapV2);
|
||||
await deployer.deploy(CrossChainSwapV2, routerAddress, feeTokenAddress, tokenBridgeAddress, wrappedBnbAddress);
|
||||
await deployer.deploy(SwapHelper);
|
||||
await deployer.link(SwapHelper, CrossChainSwapV2);
|
||||
await deployer.deploy(
|
||||
CrossChainSwapV2,
|
||||
routerAddress,
|
||||
feeTokenAddress,
|
||||
tokenBridgeAddress,
|
||||
wrappedBnbAddress
|
||||
);
|
||||
|
||||
// save the contract address somewhere
|
||||
await fsp.mkdir(scriptsAddressPath, { recursive: true });
|
||||
|
||||
// save the contract address somewhere
|
||||
await fsp.mkdir(scriptsAddressPath, { recursive: true });
|
||||
|
||||
await fsp.writeFile(
|
||||
`${scriptsAddressPath}/${network}.ts`,
|
||||
`export const SWAP_CONTRACT_ADDRESS = '${CrossChainSwapV2.address}';`
|
||||
);
|
||||
await fsp.writeFile(
|
||||
`${scriptsAddressPath}/${network}.ts`,
|
||||
`export const SWAP_CONTRACT_ADDRESS = '${CrossChainSwapV2.address}';`
|
||||
);
|
||||
|
||||
//deployer.link(ConvertLib, MetaCoin);
|
||||
//deployer.deploy(MetaCoin);
|
||||
|
|
|
@ -7,13 +7,19 @@ const scriptsAddressPath = "../react/src/addresses";
|
|||
|
||||
module.exports = async function (deployer, network) {
|
||||
const routerAddress = "0xE592427A0AEce92De3Edee1F18E0157C05861564";
|
||||
const feeTokenAddress = "0x6336c2dA64408Fcc277e0E1104aC6c34c431464c"; // wormUSD
|
||||
const feeTokenAddress = "0x36Ed51Afc79619b299b238898E72ce482600568a"; // wUST
|
||||
const tokenBridgeAddress = "0xF890982f9310df57d00f659cf4fd87e65adEd8d7";
|
||||
const wrappedEthAddress = "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6";
|
||||
|
||||
await deployer.deploy(SwapHelper);
|
||||
await deployer.link(SwapHelper, CrossChainSwapV3);
|
||||
await deployer.deploy(CrossChainSwapV3, routerAddress, feeTokenAddress, tokenBridgeAddress, wrappedEthAddress);
|
||||
await deployer.deploy(
|
||||
CrossChainSwapV3,
|
||||
routerAddress,
|
||||
feeTokenAddress,
|
||||
tokenBridgeAddress,
|
||||
wrappedEthAddress
|
||||
);
|
||||
|
||||
// save the contract address somewhere
|
||||
await fsp.mkdir(scriptsAddressPath, { recursive: true });
|
||||
|
|
|
@ -7,13 +7,19 @@ const scriptsAddressPath = "../react/src/addresses";
|
|||
|
||||
module.exports = async function (deployer, network) {
|
||||
const routerAddress = "0xa5E0829CaCEd8fFDD4De3c43696c57F7D7A678ff"; // quickwap
|
||||
const feeTokenAddress = "0xcf7BEE494B42cB5A902dF000158037Ad334eB4a7"; // wormUSD
|
||||
const feeTokenAddress = "0xe3a1c77e952b57b5883f6c906fc706fcc7d4392c"; // wUST
|
||||
const tokenBridgeAddress = "0x377D55a7928c046E18eEbb61977e714d2a76472a";
|
||||
const wrappedMaticAddress = "0x9c3c9283d3e44854697cd22d3faa240cfb032889";
|
||||
|
||||
await deployer.deploy(SwapHelper);
|
||||
await deployer.link(SwapHelper, CrossChainSwapV2);
|
||||
await deployer.deploy(CrossChainSwapV2, routerAddress, feeTokenAddress, tokenBridgeAddress, wrappedMaticAddress);
|
||||
await deployer.deploy(
|
||||
CrossChainSwapV2,
|
||||
routerAddress,
|
||||
feeTokenAddress,
|
||||
tokenBridgeAddress,
|
||||
wrappedMaticAddress
|
||||
);
|
||||
|
||||
// save the contract address somewhere
|
||||
await fsp.mkdir(scriptsAddressPath, { recursive: true });
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
const Migrations = artifacts.require("Migrations");
|
||||
|
||||
module.exports = function (deployer) {
|
||||
deployer.deploy(Migrations);
|
||||
};
|
|
@ -1,11 +0,0 @@
|
|||
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,4 +1,7 @@
|
|||
import { getIsTransferCompletedEth, hexToUint8Array } from "@certusone/wormhole-sdk";
|
||||
import {
|
||||
getIsTransferCompletedEth,
|
||||
hexToUint8Array,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
|
||||
import { ethers } from "ethers";
|
||||
|
||||
|
@ -51,24 +54,35 @@ export function loadEvmConfig(): EvmEnvironment[] {
|
|||
}
|
||||
|
||||
let key_contract_address: string = evm + "_CONTRACT_ADDRESS";
|
||||
let val_contract_address: string = eval("process.env." + key_contract_address);
|
||||
let val_contract_address: string = eval(
|
||||
"process.env." + key_contract_address
|
||||
);
|
||||
if (!val_contract_address) {
|
||||
logger.error("Missing environment variable " + key_contract_address);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let key_token_bridge_address: string = evm + "_TOKEN_BRIDGE_ADDRESS";
|
||||
let val_token_bridge_address: string = eval("process.env." + key_token_bridge_address);
|
||||
let val_token_bridge_address: string = eval(
|
||||
"process.env." + key_token_bridge_address
|
||||
);
|
||||
if (!val_token_bridge_address) {
|
||||
logger.error("Missing environment variable " + key_token_bridge_address);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let key_wallet_private_key: string = evm + "_WALLET_PRIVATE_KEY";
|
||||
let val_wallet_private_key: string = eval("process.env." + key_wallet_private_key);
|
||||
if (!val_wallet_private_key) val_wallet_private_key = process.env.WALLET_PRIVATE_KEY;
|
||||
let val_wallet_private_key: string = eval(
|
||||
"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) {
|
||||
logger.error("Missing environment variable " + key_wallet_private_key + " or WALLET_PRIVATE_KEY");
|
||||
logger.error(
|
||||
"Missing environment variable " +
|
||||
key_wallet_private_key +
|
||||
" or WALLET_PRIVATE_KEY"
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
@ -156,15 +170,72 @@ function makeContractDataForEvm(env: EvmEnvironment): EvmContractData {
|
|||
};
|
||||
}
|
||||
|
||||
export function isEvmContract(contractAddress: string, chain_id: number): boolean {
|
||||
export function isEvmContract(
|
||||
contractAddress: string,
|
||||
chain_id: number
|
||||
): boolean {
|
||||
let ecd = evmContractData.get(chain_id);
|
||||
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) {
|
||||
let ecd = evmContractData.get(t3Payload.targetChainId);
|
||||
if (!ecd) {
|
||||
logger.error("relayVaaToEvm: chain id " + t3Payload.targetChainId + " does not exist!");
|
||||
logger.error(
|
||||
"relayVaaToEvm: chain id " + t3Payload.targetChainId + " does not exist!"
|
||||
);
|
||||
}
|
||||
|
||||
let exactIn: boolean = false;
|
||||
|
@ -173,23 +244,51 @@ export async function relayVaaToEvm(vaaBytes: string, t3Payload: Type3Payload) {
|
|||
exactIn = true;
|
||||
} else if (t3Payload.swapFunctionType !== 2) {
|
||||
error = true;
|
||||
logger.error("relayVaaTo" + ecd.name + ": unsupported swapFunctionType: [" + t3Payload.swapFunctionType + "]");
|
||||
logger.error(
|
||||
"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;
|
||||
|
||||
logger.debug(
|
||||
"relayVaaTo" + ecd.name + ": chain_id: " + ecd.chain_id + ", contractAddress: [" + t3Payload.contractAddress + "]"
|
||||
"relayVaaTo" +
|
||||
ecd.name +
|
||||
": chain_id: " +
|
||||
ecd.chain_id +
|
||||
", contractAddress: [" +
|
||||
t3Payload.contractAddress +
|
||||
"]"
|
||||
);
|
||||
|
||||
const signedVaaArray = hexToUint8Array(vaaBytes);
|
||||
await relayVaaToEvmChain(t3Payload, ecd, signedVaaArray, exactIn);
|
||||
await relayVaaToEvmChain(t3Payload, ecd, signedVaaArray, exactIn, native);
|
||||
}
|
||||
|
||||
async function relayVaaToEvmChain(
|
||||
t3Payload: Type3Payload,
|
||||
tcd: EvmContractData,
|
||||
signedVaaArray: Uint8Array,
|
||||
exactIn: boolean
|
||||
exactIn: boolean,
|
||||
native: boolean
|
||||
) {
|
||||
logger.debug(
|
||||
"relayVaaTo" +
|
||||
|
@ -213,6 +312,8 @@ async function relayVaaToEvmChain(
|
|||
t3Payload.contractAddress +
|
||||
"], exactIn: " +
|
||||
exactIn +
|
||||
", native: " +
|
||||
native +
|
||||
": completed: already transferred"
|
||||
);
|
||||
|
||||
|
@ -230,17 +331,41 @@ async function relayVaaToEvmChain(
|
|||
t3Payload.contractAddress +
|
||||
"], exactIn: " +
|
||||
exactIn +
|
||||
", native: " +
|
||||
native +
|
||||
": submitting redeem request"
|
||||
);
|
||||
|
||||
try {
|
||||
let receipt: any = null;
|
||||
if (exactIn) {
|
||||
logger.debug("relayVaaTo: calling evmSwapExactInFromVaaNative()");
|
||||
receipt = await swap.evmSwapExactInFromVaaNative(tcd.contractWithSigner, signedVaaArray);
|
||||
if (native) {
|
||||
logger.debug("relayVaaTo: calling evmSwapExactInFromVaaNative()");
|
||||
receipt = await swap.evmSwapExactInFromVaaNative(
|
||||
tcd.contractWithSigner,
|
||||
signedVaaArray
|
||||
);
|
||||
} else {
|
||||
logger.debug("relayVaaTo: calling evmSwapExactInFromVaaToken()");
|
||||
receipt = await swap.evmSwapExactInFromVaaToken(
|
||||
tcd.contractWithSigner,
|
||||
signedVaaArray
|
||||
);
|
||||
}
|
||||
} else {
|
||||
logger.debug("relayVaaTo: calling evmSwapExactOutFromVaaNative()");
|
||||
receipt = await swap.evmSwapExactOutFromVaaNative(tcd.contractWithSigner, signedVaaArray);
|
||||
if (native) {
|
||||
logger.debug("relayVaaTo: calling evmSwapExactOutFromVaaNative()");
|
||||
receipt = await swap.evmSwapExactOutFromVaaNative(
|
||||
tcd.contractWithSigner,
|
||||
signedVaaArray
|
||||
);
|
||||
} else {
|
||||
logger.debug("relayVaaTo: calling evmSwapExactOutFromVaaToken()");
|
||||
receipt = await swap.evmSwapExactOutFromVaaToken(
|
||||
tcd.contractWithSigner,
|
||||
signedVaaArray
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(
|
||||
|
@ -254,6 +379,8 @@ async function relayVaaToEvmChain(
|
|||
t3Payload.contractAddress +
|
||||
"], exactIn: " +
|
||||
exactIn +
|
||||
", native: " +
|
||||
native +
|
||||
": completed: success, txHash: " +
|
||||
receipt.transactionHash
|
||||
);
|
||||
|
@ -270,6 +397,8 @@ async function relayVaaToEvmChain(
|
|||
t3Payload.contractAddress +
|
||||
"], exactIn: " +
|
||||
exactIn +
|
||||
", native: " +
|
||||
native +
|
||||
": completed: relay failed because the vaa has already been redeemed"
|
||||
);
|
||||
|
||||
|
@ -283,6 +412,8 @@ async function relayVaaToEvmChain(
|
|||
t3Payload.contractAddress +
|
||||
"], exactIn: " +
|
||||
exactIn +
|
||||
", native: " +
|
||||
native +
|
||||
": transaction failed: %o",
|
||||
e
|
||||
);
|
||||
|
@ -300,6 +431,8 @@ async function relayVaaToEvmChain(
|
|||
t3Payload.contractAddress +
|
||||
"], exactIn: " +
|
||||
exactIn +
|
||||
", native: " +
|
||||
native +
|
||||
": redeem confirmed"
|
||||
);
|
||||
} else {
|
||||
|
@ -314,18 +447,29 @@ async function relayVaaToEvmChain(
|
|||
t3Payload.contractAddress +
|
||||
"], exactIn: " +
|
||||
exactIn +
|
||||
", native: " +
|
||||
native +
|
||||
": completed: failed to confirm redeem!"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function isRedeemedOnEvm(tcd: EvmContractData, signedVaaArray: Uint8Array): Promise<boolean> {
|
||||
async function isRedeemedOnEvm(
|
||||
tcd: EvmContractData,
|
||||
signedVaaArray: Uint8Array
|
||||
): Promise<boolean> {
|
||||
let redeemed: boolean = false;
|
||||
try {
|
||||
redeemed = await getIsTransferCompletedEth(tcd.tokenBridgeAddress, tcd.provider, signedVaaArray);
|
||||
redeemed = await getIsTransferCompletedEth(
|
||||
tcd.tokenBridgeAddress,
|
||||
tcd.provider,
|
||||
signedVaaArray
|
||||
);
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
"relayVaaTo" + tcd.name + ": failed to check if transfer is already complete, will attempt the transfer, e: %o",
|
||||
"relayVaaTo" +
|
||||
tcd.name +
|
||||
": failed to check if transfer is already complete, will attempt the transfer, e: %o",
|
||||
e
|
||||
);
|
||||
}
|
||||
|
|
|
@ -12,11 +12,33 @@ import {
|
|||
getEmitterAddressTerra,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
|
||||
import { importCoreWasm, setDefaultWasm } from "@certusone/wormhole-sdk/lib/cjs/solana/wasm";
|
||||
import { createSpyRPCServiceClient, subscribeSignedVAA } from "@certusone/wormhole-spydk";
|
||||
import {
|
||||
importCoreWasm,
|
||||
setDefaultWasm,
|
||||
} from "@certusone/wormhole-sdk/lib/cjs/solana/wasm";
|
||||
|
||||
import {
|
||||
createSpyRPCServiceClient,
|
||||
subscribeSignedVAA,
|
||||
} from "@certusone/wormhole-spydk";
|
||||
|
||||
import { ethers } from "ethers";
|
||||
import { EvmEnvironment, isEvmContract, loadEvmConfig, makeEvmContractData, relayVaaToEvm } from "./evm";
|
||||
import { isTerraContract, loadTerraConfig, makeTerraContractData, relayVaaToTerra, TerraEnvironment } from "./terra";
|
||||
|
||||
import {
|
||||
EvmEnvironment,
|
||||
isEvmContract,
|
||||
loadEvmConfig,
|
||||
makeEvmContractData,
|
||||
relayVaaToEvm,
|
||||
} from "./evm";
|
||||
|
||||
import {
|
||||
isTerraContract,
|
||||
loadTerraConfig,
|
||||
makeTerraContractData,
|
||||
relayVaaToTerra,
|
||||
TerraEnvironment,
|
||||
} from "./terra";
|
||||
|
||||
export let logger: any;
|
||||
|
||||
|
@ -44,6 +66,7 @@ export type Type3Payload = {
|
|||
contractAddress: string;
|
||||
relayerFee: ethers.BigNumber;
|
||||
swapFunctionType: number;
|
||||
swapCurrencyType: number;
|
||||
};
|
||||
|
||||
type PendingEvent = {
|
||||
|
@ -65,7 +88,11 @@ let condition = new CondVar();
|
|||
let pendingQueue = new Array<PendingEvent>();
|
||||
|
||||
if (success) {
|
||||
logger.info("swap_relayer starting up, will listen for signed VAAs from [" + env.spy_host + "]");
|
||||
logger.info(
|
||||
"swap_relayer starting up, will listen for signed VAAs from [" +
|
||||
env.spy_host +
|
||||
"]"
|
||||
);
|
||||
|
||||
try {
|
||||
makeEvmContractData(env.evm_configs);
|
||||
|
@ -116,7 +143,10 @@ async function spy_listen() {
|
|||
var myFilters = [];
|
||||
for (var i = 0; i < parsedJsonFilters.length; i++) {
|
||||
var myChainId = parseInt(parsedJsonFilters[i].chain_id) as ChainId;
|
||||
var myEmitterAddress = await encodeEmitterAddress(myChainId, parsedJsonFilters[i].emitter_address);
|
||||
var myEmitterAddress = await encodeEmitterAddress(
|
||||
myChainId,
|
||||
parsedJsonFilters[i].emitter_address
|
||||
);
|
||||
var myEmitterFilter = {
|
||||
emitterFilter: {
|
||||
chainId: myChainId,
|
||||
|
@ -152,7 +182,10 @@ async function spy_listen() {
|
|||
})();
|
||||
}
|
||||
|
||||
async function encodeEmitterAddress(myChainId, emitterAddressStr): Promise<string> {
|
||||
async function encodeEmitterAddress(
|
||||
myChainId,
|
||||
emitterAddressStr
|
||||
): Promise<string> {
|
||||
if (myChainId === CHAIN_ID_SOLANA) {
|
||||
return await getEmitterAddressSolana(emitterAddressStr);
|
||||
}
|
||||
|
@ -173,11 +206,17 @@ async function processVaa(vaaBytes: string) {
|
|||
|
||||
let emitter_address: string = uint8ArrayToHex(parsedVAA.emitter_address);
|
||||
|
||||
let seqNumKey: string = parsedVAA.emitter_chain.toString() + ":" + emitter_address;
|
||||
let seqNumKey: string =
|
||||
parsedVAA.emitter_chain.toString() + ":" + emitter_address;
|
||||
let lastSeqNum = seqMap.get(seqNumKey);
|
||||
if (lastSeqNum) {
|
||||
if (lastSeqNum >= parsedVAA.sequence) {
|
||||
logger.debug("ignoring duplicate: emitter: [" + seqNumKey + "], seqNum: " + parsedVAA.sequence);
|
||||
logger.debug(
|
||||
"ignoring duplicate: emitter: [" +
|
||||
seqNumKey +
|
||||
"], seqNum: " +
|
||||
parsedVAA.sequence
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -209,6 +248,8 @@ async function processVaa(vaaBytes: string) {
|
|||
t3Payload.relayerFee +
|
||||
"], swapFunctionType: [" +
|
||||
t3Payload.swapFunctionType +
|
||||
"], swapCurrencyType: [" +
|
||||
t3Payload.swapCurrencyType +
|
||||
"]"
|
||||
);
|
||||
|
||||
|
@ -229,6 +270,8 @@ async function processVaa(vaaBytes: string) {
|
|||
t3Payload.relayerFee +
|
||||
"], swapFunctionType: [" +
|
||||
t3Payload.swapFunctionType +
|
||||
"], swapCurrencyType: [" +
|
||||
t3Payload.swapCurrencyType +
|
||||
"]"
|
||||
);
|
||||
}
|
||||
|
@ -246,7 +289,10 @@ async function processVaa(vaaBytes: string) {
|
|||
}
|
||||
}
|
||||
|
||||
function decodeSignedVAAPayloadType3(parsedVAA: any, sourceChainId: number): Type3Payload {
|
||||
function decodeSignedVAAPayloadType3(
|
||||
parsedVAA: any,
|
||||
sourceChainId: number
|
||||
): Type3Payload {
|
||||
const payload = Buffer.from(new Uint8Array(parsedVAA.payload));
|
||||
if (payload[0] !== 3) return undefined;
|
||||
|
||||
|
@ -264,13 +310,18 @@ function decodeSignedVAAPayloadType3(parsedVAA: any, sourceChainId: number): Typ
|
|||
|
||||
let contractAddress: string = "";
|
||||
let swapFunctionType: number = 0;
|
||||
let swapCurrencyType: number = 0;
|
||||
|
||||
if (targetChainId === 3) {
|
||||
logger.info("decodeSignedVAAPayloadType3: terraContractAddr: [" + payload.slice(67, 67 + 32).toString("hex") + "]");
|
||||
logger.info(
|
||||
"decodeSignedVAAPayloadType3: terraContractAddr: [" +
|
||||
payload.slice(67, 67 + 32).toString("hex") +
|
||||
"]"
|
||||
);
|
||||
|
||||
contractAddress = payload.slice(67, 67 + 32).toString("hex");
|
||||
} else {
|
||||
if (payload.length < 272) {
|
||||
if (payload.length < 262) {
|
||||
logger.error(
|
||||
"decodeSignedVAAPayloadType3: dropping type 3 vaa because the payload is too short to extract the contract fields, length: " +
|
||||
payload.length +
|
||||
|
@ -279,24 +330,34 @@ function decodeSignedVAAPayloadType3(parsedVAA: any, sourceChainId: number): Typ
|
|||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
contractAddress = payload.slice(79, 79 + 20).toString("hex");
|
||||
swapFunctionType = payload.readUInt8(272);
|
||||
swapCurrencyType = payload.readUInt8(273);
|
||||
}
|
||||
|
||||
return {
|
||||
sourceChainId: sourceChainId,
|
||||
targetChainId: targetChainId,
|
||||
contractAddress: contractAddress,
|
||||
relayerFee: ethers.BigNumber.from(payload.slice(273, 273 + 32)),
|
||||
relayerFee: ethers.BigNumber.from(payload.slice(101, 101 + 32)),
|
||||
swapFunctionType: swapFunctionType,
|
||||
swapCurrencyType: swapCurrencyType,
|
||||
};
|
||||
}
|
||||
|
||||
function isOurContract(contractAddress: string, chainId: number): boolean {
|
||||
return isEvmContract(contractAddress, chainId) || isTerraContract(contractAddress, chainId);
|
||||
return (
|
||||
isEvmContract(contractAddress, chainId) ||
|
||||
isTerraContract(contractAddress, chainId)
|
||||
);
|
||||
}
|
||||
|
||||
async function postVaa(vaaBytes: any, t3Payload: Type3Payload, receiveTime: Date) {
|
||||
async function postVaa(
|
||||
vaaBytes: any,
|
||||
t3Payload: Type3Payload,
|
||||
receiveTime: Date
|
||||
) {
|
||||
let event: PendingEvent = {
|
||||
vaaBytes: vaaBytes,
|
||||
t3Payload: t3Payload,
|
||||
|
@ -305,7 +366,9 @@ async function postVaa(vaaBytes: any, t3Payload: Type3Payload, receiveTime: Date
|
|||
|
||||
await mutex.runExclusive(() => {
|
||||
pendingQueue.push(event);
|
||||
logger.debug("posting event, there are now " + pendingQueue.length + " enqueued events");
|
||||
logger.debug(
|
||||
"posting event, there are now " + pendingQueue.length + " enqueued events"
|
||||
);
|
||||
if (condition) {
|
||||
logger.debug("hitting condition variable.");
|
||||
condition.complete(true);
|
||||
|
@ -356,12 +419,16 @@ async function callBack(err: any, result: any) {
|
|||
|
||||
await mutex.runExclusive(async () => {
|
||||
if (pendingQueue.length === 0) {
|
||||
logger.debug("in callback, no more pending events, rearming the condition.");
|
||||
logger.debug(
|
||||
"in callback, no more pending events, rearming the condition."
|
||||
);
|
||||
done = true;
|
||||
condition = new CondVar();
|
||||
await condition.wait(COND_VAR_TIMEOUT, callBack);
|
||||
} else {
|
||||
logger.debug("in callback, there are " + pendingQueue.length + " pending events.");
|
||||
logger.debug(
|
||||
"in callback, there are " + pendingQueue.length + " pending events."
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -388,7 +455,8 @@ function initLogger() {
|
|||
let logFileName: string = "";
|
||||
if (process.env.LOG_DIR) {
|
||||
useConsole = false;
|
||||
logFileName = process.env.LOG_DIR + "/swap_relay." + new Date().toISOString() + ".log";
|
||||
logFileName =
|
||||
process.env.LOG_DIR + "/swap_relay." + new Date().toISOString() + ".log";
|
||||
}
|
||||
|
||||
let logLevel = "info";
|
||||
|
@ -404,7 +472,11 @@ function initLogger() {
|
|||
level: logLevel,
|
||||
});
|
||||
} else {
|
||||
console.log("swap_relay is logging to [%s] at level [%s]", logFileName, logLevel);
|
||||
console.log(
|
||||
"swap_relay is logging to [%s] at level [%s]",
|
||||
logFileName,
|
||||
logLevel
|
||||
);
|
||||
|
||||
transport = new winston.transports.File({
|
||||
filename: logFileName,
|
||||
|
@ -420,7 +492,9 @@ function initLogger() {
|
|||
winston.format.timestamp({
|
||||
format: "YYYY-MM-DD HH:mm:ss.SSS",
|
||||
}),
|
||||
winston.format.printf((info: any) => `${[info.timestamp]}|${info.level}|${info.message}`)
|
||||
winston.format.printf(
|
||||
(info: any) => `${[info.timestamp]}|${info.level}|${info.message}`
|
||||
)
|
||||
),
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue