Compare commits

...

5 Commits

Author SHA1 Message Date
Karl Kempe 31cb892162 Merge branch 'terra-support' of github.com:certusone/wormhole-nativeswap-example into terra-support 2022-01-28 22:31:07 +00:00
Karl Kempe fafd2ace84 Add terra bridge addresses; add ust dst handling 2022-01-28 22:31:03 +00:00
Bruce Riley a295568e58 Enhance logging in the relayer 2022-01-28 20:00:56 +00:00
Bruce Riley ea822b5fb2 Relayer support for more EVMs 2022-01-28 17:59:48 +00:00
Karl Kempe f9870b03de Remove logging 2022-01-28 17:43:04 +00:00
9 changed files with 682 additions and 434 deletions

View File

@ -144,11 +144,18 @@ async function swapEverythingExactIn(
tokenInAddress: string, tokenInAddress: string,
tokenOutAddress: string, tokenOutAddress: string,
isNative: boolean, isNative: boolean,
amountIn: string amountIn: string,
recipientAddress: string
): Promise<void> { ): Promise<void> {
const isTerraSrc = tokenInAddress === UST_TOKEN_INFO.address;
if (isTerraSrc) {
throw Error("cannot use terra source yet");
}
// connect src wallet // connect src wallet
const srcWallet = determineWalletFromToken(tokenInAddress); const srcWallet = determineWalletFromToken(tokenInAddress);
console.info(`wallet pubkey: ${await srcWallet.getAddress()}`); console.info(`sender: ${await srcWallet.getAddress()}`);
console.info(`recipient: ${recipientAddress}`);
// tokens selected, let's initialize // tokens selected, let's initialize
await swapper.initialize(tokenInAddress, tokenOutAddress, isNative); await swapper.initialize(tokenInAddress, tokenOutAddress, isNative);
@ -193,9 +200,17 @@ async function swapEverythingExactIn(
logExactInParameters(swapper.quoter, exactInParameters); logExactInParameters(swapper.quoter, exactInParameters);
// do the src swap // do the src swap
console.info("approveAndSwap"); if (isTerraSrc) {
const srcSwapReceipt = await swapper.evmApproveAndSwap(srcWallet); // do terra method
console.info(`src transaction: ${srcSwapReceipt.transactionHash}`); throw Error("terra src not implemented yet");
} else {
console.info("approveAndSwap");
const srcSwapReceipt = await swapper.evmApproveAndSwap(
srcWallet,
recipientAddress
);
console.info(`src transaction: ${srcSwapReceipt.transactionHash}`);
}
// do the dst swap after fetching vaa // do the dst swap after fetching vaa
// connect dst wallet // connect dst wallet
@ -263,7 +278,8 @@ async function swapEverythingExactOut(
tokenInAddress: string, tokenInAddress: string,
tokenOutAddress: string, tokenOutAddress: string,
isNative: boolean, isNative: boolean,
amountOut: string amountOut: string,
recipientAddress: string
): Promise<void> { ): Promise<void> {
// connect src wallet // connect src wallet
const srcWallet = determineWalletFromToken(tokenInAddress); const srcWallet = determineWalletFromToken(tokenInAddress);
@ -313,7 +329,10 @@ async function swapEverythingExactOut(
// do the src swap // do the src swap
console.info("approveAndSwap"); console.info("approveAndSwap");
const srcSwapReceipt = await swapper.evmApproveAndSwap(srcWallet); const srcSwapReceipt = await swapper.evmApproveAndSwap(
srcWallet,
recipientAddress
);
console.info(`src transaction: ${srcSwapReceipt.transactionHash}`); console.info(`src transaction: ${srcSwapReceipt.transactionHash}`);
// do the dst swap after fetching vaa // do the dst swap after fetching vaa
@ -336,7 +355,11 @@ async function main() {
swapper.setTransport(NodeHttpTransport()); swapper.setTransport(NodeHttpTransport());
const tokenIn = ETH_TOKEN_INFO; const tokenIn = ETH_TOKEN_INFO;
const tokenOut = AVAX_TOKEN_INFO; //const tokenOut = MATIC_TOKEN_INFO;
const tokenOut = UST_TOKEN_INFO;
//const recipientAddress = "0x4e2dfAD7D7d0076b5A0A41223E4Bee390C33251C";
const recipientAddress = "terra1vewnsxcy5fqjslyyy409cw8js550esen38n8ey";
if (testExactIn) { if (testExactIn) {
console.info(`testing exact in. native=${isNative}`); console.info(`testing exact in. native=${isNative}`);
@ -347,17 +370,23 @@ async function main() {
tokenIn.address, tokenIn.address,
tokenOut.address, tokenOut.address,
isNative, isNative,
determineAmountFromToken(tokenIn.address) determineAmountFromToken(tokenIn.address),
recipientAddress
); );
console.info(`${tokenOut.name} -> ${tokenIn.name}`); if (tokenOut.address === UST_TOKEN_INFO.address) {
await swapEverythingExactIn( console.warn("not pinging back");
swapper, } else {
tokenOut.address, console.info(`${tokenOut.name} -> ${tokenIn.name}`);
tokenIn.address, await swapEverythingExactIn(
isNative, swapper,
determineAmountFromToken(tokenOut.address) tokenOut.address,
); tokenIn.address,
isNative,
determineAmountFromToken(tokenOut.address),
recipientAddress
);
}
} else { } else {
console.info(`testing exact out. native=${isNative}`); console.info(`testing exact out. native=${isNative}`);
@ -367,7 +396,8 @@ async function main() {
tokenIn.address, tokenIn.address,
tokenOut.address, tokenOut.address,
isNative, isNative,
determineAmountFromToken(tokenOut.address) determineAmountFromToken(tokenOut.address),
recipientAddress
); );
console.info(`${tokenOut.name} -> ${tokenIn.name}`); console.info(`${tokenOut.name} -> ${tokenIn.name}`);
@ -376,7 +406,8 @@ async function main() {
tokenOut.address, tokenOut.address,
tokenIn.address, tokenIn.address,
isNative, isNative,
determineAmountFromToken(tokenIn.address) determineAmountFromToken(tokenIn.address),
recipientAddress
); );
} }

View File

@ -29,21 +29,11 @@ export async function getEvmGasParametersForContract(
contract: ethers.Contract contract: ethers.Contract
): Promise<any> { ): Promise<any> {
const chainId = await getChainIdFromContract(contract); const chainId = await getChainIdFromContract(contract);
console.info(`getEvmGasParametersForContract... chainId: ${chainId}`);
if (EVM_EIP1559_CHAIN_IDS.indexOf(chainId) >= 0) { if (EVM_EIP1559_CHAIN_IDS.indexOf(chainId) >= 0) {
console.info(
`eip1559? chainId: ${chainId}, eip1559 chains... ${JSON.stringify(
EVM_EIP1559_CHAIN_IDS
)}`
);
return CROSSCHAINSWAP_GAS_PARAMETERS_EIP1559; return CROSSCHAINSWAP_GAS_PARAMETERS_EIP1559;
} }
console.info(
`not eip1559 chainId: ${chainId}, eip1559 chains... ${JSON.stringify(
EVM_EIP1559_CHAIN_IDS
)}`
);
return CROSSCHAINSWAP_GAS_PARAMETERS_EVM; return CROSSCHAINSWAP_GAS_PARAMETERS_EVM;
} }
@ -64,12 +54,6 @@ export async function evmSwapExactInFromVaaNative(
swapContractWithSigner swapContractWithSigner
); );
console.info(
`evmSwapExactInFromVaaNative... contract: ${
swapContractWithSigner.address
}, gasParams: ${JSON.stringify(gasParams)}`
);
const tx = await swapContractWithSigner.recvAndSwapExactNativeIn( const tx = await swapContractWithSigner.recvAndSwapExactNativeIn(
signedVaa, signedVaa,
gasParams gasParams

View File

@ -1,4 +1,4 @@
//@ts-nocheck //@ts-nocheckk
import { ethers } from "ethers"; import { ethers } from "ethers";
import { TransactionReceipt } from "@ethersproject/abstract-provider"; import { TransactionReceipt } from "@ethersproject/abstract-provider";
import { import {
@ -58,7 +58,16 @@ import { SWAP_CONTRACT_ADDRESS as CROSSCHAINSWAP_CONTRACT_ADDRESS_BSC } from "..
import { makeErc20Contract } from "../route/evm"; import { makeErc20Contract } from "../route/evm";
// placeholders // placeholders
const CROSSCHAINSWAP_CONTRACT_ADDRESS_TERRA = ""; const CROSSCHAINSWAP_CONTRACT_ADDRESS_TERRA =
"terra163shc8unyqrndgcldaj2q9kgnqs82v0kgkhynf";
function makeNullSwapPath(): any[] {
const zeroBuffer = Buffer.alloc(20);
const nullAddress = "0x" + zeroBuffer.toString("hex");
return [nullAddress, nullAddress];
}
const NULL_SWAP_PATH = makeNullSwapPath();
interface SwapContractParameters { interface SwapContractParameters {
address: string; address: string;
@ -223,6 +232,48 @@ function addressToBytes32(
return hexToUint8Array(hexString); return hexToUint8Array(hexString);
} }
function evmMakeExactInSwapParameters(
amountIn: ethers.BigNumber,
recipientAddress: string,
dstWormholeChainId: ChainId,
quoteParams: ExactInCrossParameters
): any[] {
const src = quoteParams.src;
const dst = quoteParams.dst;
if (dst === undefined) {
return [
amountIn,
src.minAmountOut,
0,
addressToBytes32(recipientAddress, dstWormholeChainId),
src.deadline,
src.poolFee || 0,
];
}
return [
amountIn,
src.minAmountOut,
dst.minAmountOut,
addressToBytes32(recipientAddress, dstWormholeChainId),
src.deadline,
dst.poolFee || src.poolFee || 0,
];
}
function makePathArray(
quoteParams: ExactInCrossParameters | ExactOutCrossParameters
): any[] {
if (quoteParams.src === undefined) {
return NULL_SWAP_PATH.concat(quoteParams.dst.path);
} else if (quoteParams.dst === undefined) {
return quoteParams.src.path.concat(NULL_SWAP_PATH);
} else {
return quoteParams.src.path.concat(quoteParams.dst.path);
}
}
async function evmApproveAndSwapExactIn( async function evmApproveAndSwapExactIn(
srcProvider: ethers.providers.Provider, srcProvider: ethers.providers.Provider,
srcWallet: ethers.Signer, srcWallet: ethers.Signer,
@ -230,7 +281,8 @@ async function evmApproveAndSwapExactIn(
quoteParams: ExactInCrossParameters, quoteParams: ExactInCrossParameters,
srcExecutionParams: ExecutionParameters, srcExecutionParams: ExecutionParameters,
dstExecutionParams: ExecutionParameters, dstExecutionParams: ExecutionParameters,
isNative: boolean isNative: boolean,
recipientAddress: string
): Promise<TransactionReceipt> { ): Promise<TransactionReceipt> {
const swapContractParams = srcExecutionParams.crossChainSwap; const swapContractParams = srcExecutionParams.crossChainSwap;
@ -244,21 +296,16 @@ async function evmApproveAndSwapExactIn(
// approve and swap this amount // approve and swap this amount
const amountIn = quoteParams.src.amountIn; const amountIn = quoteParams.src.amountIn;
const address = await srcWallet.getAddress();
const dstWormholeChainId = dstExecutionParams.wormhole.chainId; const dstWormholeChainId = dstExecutionParams.wormhole.chainId;
const swapParams = [ const swapParams = evmMakeExactInSwapParameters(
amountIn, amountIn,
quoteParams.src.minAmountOut, recipientAddress,
quoteParams.dst.minAmountOut, dstWormholeChainId,
addressToBytes32(address, dstWormholeChainId), quoteParams
quoteParams.src.deadline, );
quoteParams.dst.poolFee || quoteParams.src.poolFee || 0,
];
const pathArray = quoteParams.src.path.concat(quoteParams.dst.path); const pathArray = makePathArray(quoteParams);
const dstContractAddress = addressToBytes32( const dstContractAddress = addressToBytes32(
dstExecutionParams.crossChainSwap.address, dstExecutionParams.crossChainSwap.address,
@ -306,6 +353,7 @@ async function evmApproveAndSwapExactIn(
} }
} }
// TODO: fix to resemble ExactIn
async function evmApproveAndSwapExactOut( async function evmApproveAndSwapExactOut(
srcProvider: ethers.providers.Provider, srcProvider: ethers.providers.Provider,
srcWallet: ethers.Signer, srcWallet: ethers.Signer,
@ -313,11 +361,12 @@ async function evmApproveAndSwapExactOut(
quoteParams: ExactOutCrossParameters, quoteParams: ExactOutCrossParameters,
srcExecutionParams: ExecutionParameters, srcExecutionParams: ExecutionParameters,
dstExecutionParams: ExecutionParameters, dstExecutionParams: ExecutionParameters,
isNative: boolean isNative: boolean,
recipientAddress: string
): Promise<TransactionReceipt> { ): Promise<TransactionReceipt> {
const swapContractParams = srcExecutionParams.crossChainSwap; const swapContractParams = srcExecutionParams.crossChainSwap;
const protocol = quoteParams.src.protocol; const protocol = quoteParams.src?.protocol;
const swapContract = makeCrossChainSwapEvmContract( const swapContract = makeCrossChainSwapEvmContract(
srcProvider, srcProvider,
protocol, protocol,
@ -326,22 +375,19 @@ async function evmApproveAndSwapExactOut(
const contractWithSigner = swapContract.connect(srcWallet); const contractWithSigner = swapContract.connect(srcWallet);
// approve and swap this amount // approve and swap this amount
const amountOut = quoteParams.src.amountOut; const amountOut = quoteParams.src?.amountOut;
const maxAmountIn = quoteParams.src.maxAmountIn; const maxAmountIn = quoteParams.src?.maxAmountIn;
const address = await srcWallet.getAddress();
const dstWormholeChainId = dstExecutionParams.wormhole.chainId; const dstWormholeChainId = dstExecutionParams.wormhole.chainId;
const swapParams = [ const swapParams = [
amountOut, amountOut,
maxAmountIn, maxAmountIn,
quoteParams.dst.amountOut, quoteParams.dst.amountOut,
addressToBytes32(address, dstWormholeChainId), addressToBytes32(recipientAddress, dstWormholeChainId),
quoteParams.src.deadline, quoteParams.src.deadline,
quoteParams.dst.poolFee || quoteParams.src.poolFee || 0, quoteParams.dst.poolFee || quoteParams.src.poolFee || 0,
]; ];
const pathArray = quoteParams.src.path.concat(quoteParams.dst.path); const pathArray = makePathArray(quoteParams);
const dstContractAddress = addressToBytes32( const dstContractAddress = addressToBytes32(
dstExecutionParams.crossChainSwap.address, dstExecutionParams.crossChainSwap.address,
@ -613,30 +659,34 @@ export class UniswapToUniswapExecutor {
} }
async evmApproveAndSwapExactIn( async evmApproveAndSwapExactIn(
wallet: ethers.Signer srcWallet: ethers.Signer,
recipientAddress: string
): Promise<TransactionReceipt> { ): Promise<TransactionReceipt> {
return evmApproveAndSwapExactIn( return evmApproveAndSwapExactIn(
this.getSrcEvmProvider(), this.getSrcEvmProvider(),
wallet, srcWallet,
this.getTokenInAddress(), this.getTokenInAddress(),
this.cachedExactInParams, this.cachedExactInParams,
this.srcExecutionParams, this.srcExecutionParams,
this.dstExecutionParams, this.dstExecutionParams,
this.isNative this.isNative,
recipientAddress
); );
} }
async evmApproveAndSwapExactOut( async evmApproveAndSwapExactOut(
wallet: ethers.Signer srcWallet: ethers.Signer,
recipientAddress: string
): Promise<TransactionReceipt> { ): Promise<TransactionReceipt> {
return evmApproveAndSwapExactOut( return evmApproveAndSwapExactOut(
this.getSrcEvmProvider(), this.getSrcEvmProvider(),
wallet, srcWallet,
this.getTokenInAddress(), this.getTokenInAddress(),
this.cachedExactOutParams, this.cachedExactOutParams,
this.srcExecutionParams, this.srcExecutionParams,
this.dstExecutionParams, this.dstExecutionParams,
this.isNative this.isNative,
recipientAddress
); );
} }
@ -647,18 +697,27 @@ export class UniswapToUniswapExecutor {
); );
} }
async evmApproveAndSwap(wallet: ethers.Signer): Promise<TransactionReceipt> { async evmApproveAndSwap(
wallet: ethers.Signer,
recipientAddress: string
): Promise<TransactionReceipt> {
const quoteType = this.quoteType; const quoteType = this.quoteType;
if (quoteType === QuoteType.ExactIn) { if (quoteType === QuoteType.ExactIn) {
this.srcEvmReceipt = await this.evmApproveAndSwapExactIn(wallet); this.srcEvmReceipt = await this.evmApproveAndSwapExactIn(
wallet,
recipientAddress
);
} else if (quoteType === QuoteType.ExactOut) { } else if (quoteType === QuoteType.ExactOut) {
this.srcEvmReceipt = await this.evmApproveAndSwapExactOut(wallet); this.srcEvmReceipt = await this.evmApproveAndSwapExactOut(
wallet,
recipientAddress
);
} else { } else {
throw Error("no quote found"); throw Error("no quote found");
} }
this.fetchAndSetEmitterAndSequence(); this.fetchAndSetEvmEmitterAndSequence();
return this.srcEvmReceipt; return this.srcEvmReceipt;
} }

View File

@ -190,7 +190,8 @@ export const CORE_BRIDGE_ADDRESS_AVALANCHE =
export const CORE_BRIDGE_ADDRESS_BSC = export const CORE_BRIDGE_ADDRESS_BSC =
"0x68605AD7b15c732a30b1BbC62BE8F2A509D74b4D"; "0x68605AD7b15c732a30b1BbC62BE8F2A509D74b4D";
export const CORE_BRIDGE_ADDRESS_TERRA = undefined; export const CORE_BRIDGE_ADDRESS_TERRA =
"terra1pd65m0q9tl3v8znnz5f5ltsfegyzah7g42cx5v";
// token bridge // token bridge
export const TOKEN_BRIDGE_ADDRESS_ETHEREUM = export const TOKEN_BRIDGE_ADDRESS_ETHEREUM =
@ -205,7 +206,8 @@ export const TOKEN_BRIDGE_ADDRESS_BSC =
export const TOKEN_BRIDGE_ADDRESS_AVALANCHE = export const TOKEN_BRIDGE_ADDRESS_AVALANCHE =
"0x61E44E506Ca5659E6c0bba9b678586fA2d729756"; "0x61E44E506Ca5659E6c0bba9b678586fA2d729756";
export const TOKEN_BRIDGE_ADDRESS_TERRA = undefined; export const TOKEN_BRIDGE_ADDRESS_TERRA =
"terra1pseddrv0yfsn76u4zxrjmtf45kdlmalswdv39a";
// gas // gas
export const APPROVAL_GAS_LIMIT = "100000"; export const APPROVAL_GAS_LIMIT = "100000";

View File

@ -1,12 +1,29 @@
# TestNet # TestNet
SPY_SERVICE_HOST=localhost:7073 SPY_SERVICE_HOST=localhost:7073
SPY_SERVICE_FILTERS=[{"chain_id":2,"emitter_address":"0x000000000000000000000000f890982f9310df57d00f659cf4fd87e65aded8d7"},{"chain_id":3,"emitter_address":"terra1pseddrv0yfsn76u4zxrjmtf45kdlmalswdv39a"},{"chain_id":5,"emitter_address":"0x000000000000000000000000377d55a7928c046e18eebb61977e714d2a76472a"}] # You can omit the following to get signed VAAs from every emitter.
#SPY_SERVICE_FILTERS=[{"chain_id":2,"emitter_address":"0x000000000000000000000000f890982f9310df57d00f659cf4fd87e65aded8d7"},{"chain_id":3,"emitter_address":"terra1pseddrv0yfsn76u4zxrjmtf45kdlmalswdv39a"},{"chain_id":4,"emitter_address":"0x0000000000000000000000009dcF9D205C9De35334D646BeE44b2D2859712A09"},{"chain_id":5,"emitter_address":"0x000000000000000000000000377d55a7928c046e18eebb61977e714d2a76472a"},{"chain_id":6,"emitter_address":"0x0000000000000000000000007bbcE28e64B3F8b84d876Ab298393c38ad7aac4C"}]
EVM_CHAINS=ETH,BSC,POLYGON,AVAX
ETH_PROVIDER=https://goerli.infura.io/v3/your_project_id ETH_PROVIDER=https://goerli.infura.io/v3/your_project_id
ETH_TOKEN_BRIDGE_ADDRESS=0xF890982f9310df57d00f659cf4fd87e65adEd8d7 ETH_TOKEN_BRIDGE_ADDRESS=0xF890982f9310df57d00f659cf4fd87e65adEd8d7
ETH_CHAIN_ID=2
ETH_ABI=V3
BSC_PROVIDER=
BSC_TOKEN_BRIDGE_ADDRESS=0x9dcF9D205C9De35334D646BeE44b2D2859712A09
BSC_CHAIN_ID=4
BSC_ABI=V2
POLYGON_PROVIDER=https://polygon-mumbai.infura.io/v3/your_project_id POLYGON_PROVIDER=https://polygon-mumbai.infura.io/v3/your_project_id
POLYGON_TOKEN_BRIDGE_ADDRESS=0x377D55a7928c046E18eEbb61977e714d2a76472a POLYGON_TOKEN_BRIDGE_ADDRESS=0x377D55a7928c046E18eEbb61977e714d2a76472a
POLYGON_CHAIN_ID=5
POLYGON_ABI=V2
AVAX_PROVIDER=
AVAX_TOKEN_BRIDGE_ADDRESS=0x61E44E506Ca5659E6c0bba9b678586fA2d729756
AVAX_CHAIN_ID=6
AVAX_ABI=V2
WALLET_PRIVATE_KEY=your_key_here WALLET_PRIVATE_KEY=your_key_here
@ -20,6 +37,8 @@ TERRA_TOKEN_BRIDGE_ADDRESS=terra1pseddrv0yfsn76u4zxrjmtf45kdlmalswdv39a
#LOG_DIR=/var/logs #LOG_DIR=/var/logs
LOG_LEVEL=debug LOG_LEVEL=debug
ETH_CONTRACT_ADDRESS=0x61D26732B190bdc5771e2a2b3ADB295e1b5A88BF ETH_CONTRACT_ADDRESS=0x9e7Cae3a46ED297b0a05FCEeb41160fC5218E14f
POLYGON_CONTRACT_ADDRESS=0xc5Ba16A974a0c0E7935285d99F496Ee65eDFB8BA BSC_CONTRACT_ADDRESS=0x0DC183c2eFAA5e1749B85f13621F5cC6aCcDa786
TERRA_CONTRACT_ADDRESS=terra_contract_address POLYGON_CONTRACT_ADDRESS=0x72F2F646dC979a9fA8aA685B8a47b7afe2fE0516
TERRA_CONTRACT_ADDRESS=terra163shc8unyqrndgcldaj2q9kgnqs82v0kgkhynf
AVAX_CONTRACT_ADDRESS=0x52D8A50AF35b0760335F29a4D6aaF0604B7D7484

View File

@ -32,7 +32,7 @@
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^10.0.0", "dotenv": "^10.0.0",
"ethers": "^5.5.3", "ethers": "^5.5.3",
"@terra-money/terra.js": "^2.0.14", "@terra-money/terra.js": "^3.0.4",
"winston": "^3.3.3" "winston": "^3.3.3"
} }
} }

View File

@ -1,23 +1,29 @@
import { Mutex } from "async-mutex";
let CondVar = require("condition-variable");
import { getIsTransferCompletedEth } from "@certusone/wormhole-sdk";
import { import {
importCoreWasm, getIsTransferCompletedEth,
setDefaultWasm, hexToUint8Array,
} from "@certusone/wormhole-sdk/lib/cjs/solana/wasm"; } from "@certusone/wormhole-sdk";
import { ethers } from "ethers"; import { ethers } from "ethers";
import { abi as SWAP_CONTRACT_V2_ABI } from "../../react/src/abi/contracts/CrossChainSwapV2.json"; import { abi as SWAP_CONTRACT_V2_ABI } from "../../react/src/abi/contracts/CrossChainSwapV2.json";
import { abi as SWAP_CONTRACT_V3_ABI } from "../../react/src/abi/contracts/CrossChainSwapV3.json"; import { abi as SWAP_CONTRACT_V3_ABI } from "../../react/src/abi/contracts/CrossChainSwapV3.json";
import * as swap from "../../react/src/swapper/util"; import * as swap from "../../react/src/swapper/helpers";
import { logger, OurEnvironment, Type3Payload } from "./index"; import { logger, OurEnvironment, Type3Payload } from "./index";
export type EvmEnvironment = {
name: string;
chain_id: number;
provider_url: string;
contract_address: string;
token_bridge_address: string;
wallet_private_key: string;
abi_version: string;
};
type EvmContractData = { type EvmContractData = {
chain_id: number;
name: string; name: string;
contractAddress: string; contractAddress: string;
tokenBridgeAddress: string; tokenBridgeAddress: string;
@ -27,48 +33,136 @@ type EvmContractData = {
contractWithSigner: ethers.Contract; contractWithSigner: ethers.Contract;
}; };
let ethContractData: EvmContractData = null; let evmContractData = new Map<number, EvmContractData>();
let polygonContractData: EvmContractData = null;
export function makeEvmContractData(env: OurEnvironment) { export function loadEvmConfig(): EvmEnvironment[] {
ethContractData = makeEthContractData(env); let evm_configs: EvmEnvironment[] = [];
polygonContractData = makePolygonContractData(env); let evms = process.env.EVM_CHAINS.split(",");
for (const evm of evms) {
let key_chain_id: string = evm + "_CHAIN_ID";
let val_chain_id: string = eval("process.env." + key_chain_id);
if (!val_chain_id) {
logger.error("Missing environment variable " + key_chain_id);
return undefined;
}
let key_provider: string = evm + "_PROVIDER";
let val_provider: string = eval("process.env." + key_provider);
if (!val_provider) {
logger.error("Missing environment variable " + key_provider);
return undefined;
}
let key_contract_address: string = evm + "_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
);
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;
if (!val_wallet_private_key) {
logger.error(
"Missing environment variable " +
key_wallet_private_key +
" or WALLET_PRIVATE_KEY"
);
return undefined;
}
let key_abi_version: string = evm + "_ABI";
let val_abi_version: string = eval("process.env." + key_abi_version);
if (!val_abi_version) {
logger.error("Missing environment variable " + key_abi_version);
return undefined;
}
if (val_abi_version !== "V2" && val_abi_version !== "V3") {
logger.error(
"Invalid value of environment variable " +
key_abi_version +
", is [" +
val_abi_version +
"], must be either V2 or V3"
);
return undefined;
}
evm_configs.push({
name: evm,
chain_id: parseInt(val_chain_id),
provider_url: val_provider,
contract_address: val_contract_address,
token_bridge_address: val_token_bridge_address,
wallet_private_key: val_wallet_private_key,
abi_version: val_abi_version,
});
}
return evm_configs;
} }
// Ethereum (Goerli) set up export function makeEvmContractData(envs: EvmEnvironment[]) {
function makeEthContractData(env: OurEnvironment): EvmContractData { if (!envs) return;
let contractAddress: string = env.eth_contract_address.toLowerCase(); for (const evm of envs) {
if (contractAddress.search("0x") == 0) { evmContractData.set(evm.chain_id, makeContractDataForEvm(evm));
}
}
function makeContractDataForEvm(env: EvmEnvironment): EvmContractData {
let contractAddress: string = env.contract_address.toLowerCase();
if (contractAddress.search("0x") === 0) {
contractAddress = contractAddress.substring(2); contractAddress = contractAddress.substring(2);
} }
logger.info( logger.info(
"Connecting to Ethereum: contract address: [" + "Connecting to " +
env.name +
": chain_id: " +
env.chain_id +
", contract address: [" +
contractAddress + contractAddress +
"], node: [" + "], node: [" +
env.eth_provider_url + env.provider_url +
"], token bridge address: [" + "], token bridge address: [" +
env.eth_token_bridge_address + env.token_bridge_address +
"], abi version: [" +
env.abi_version +
"]" "]"
); );
const provider = new ethers.providers.StaticJsonRpcProvider( const provider = new ethers.providers.StaticJsonRpcProvider(env.provider_url);
env.eth_provider_url
);
const contract = new ethers.Contract( const contract = new ethers.Contract(
contractAddress, contractAddress,
SWAP_CONTRACT_V3_ABI, env.abi_version == "V2" ? SWAP_CONTRACT_V2_ABI : SWAP_CONTRACT_V3_ABI,
provider provider
); );
const wallet = new ethers.Wallet(env.evm_wallet_private_key, provider); const wallet = new ethers.Wallet(env.wallet_private_key, provider);
const contractWithSigner = contract.connect(wallet); const contractWithSigner = contract.connect(wallet);
return { return {
name: "Ethereum", chain_id: env.chain_id,
name: env.name,
contractAddress: contractAddress, contractAddress: contractAddress,
tokenBridgeAddress: env.eth_token_bridge_address, tokenBridgeAddress: env.token_bridge_address,
contract: contract, contract: contract,
provider: provider, provider: provider,
wallet: wallet, wallet: wallet,
@ -76,53 +170,12 @@ function makeEthContractData(env: OurEnvironment): EvmContractData {
}; };
} }
// Polygon (Mumbai) set up export function isEvmContract(
function makePolygonContractData(env: OurEnvironment): EvmContractData { contractAddress: string,
let contractAddress: string = env.polygon_contract_address.toLowerCase(); chain_id: number
if (contractAddress.search("0x") == 0) { ): boolean {
contractAddress = contractAddress.substring(2); let ecd = evmContractData.get(chain_id);
} return ecd && ecd.contractAddress === contractAddress;
logger.info(
"Connecting to Polygon: contract address: [" +
contractAddress +
"], node: [" +
env.polygon_provider_url +
"], token bridge address: [" +
env.polygon_token_bridge_address +
"]"
);
const provider = new ethers.providers.StaticJsonRpcProvider(
env.polygon_provider_url
);
const contract = new ethers.Contract(
contractAddress,
SWAP_CONTRACT_V2_ABI,
provider
);
const wallet = new ethers.Wallet(env.evm_wallet_private_key, provider);
const contractWithSigner = contract.connect(wallet);
return {
name: "Polygon",
contractAddress: contractAddress,
tokenBridgeAddress: env.polygon_token_bridge_address,
contract: contract,
provider: provider,
wallet: wallet,
contractWithSigner: contractWithSigner,
};
}
export function isEvmContract(contractAddress: string): boolean {
return (
(ethContractData && contractAddress === ethContractData.contractAddress) ||
(polygonContractData &&
contractAddress === polygonContractData.contractAddress)
);
} }
/* /*
@ -177,16 +230,24 @@ export function isEvmContract(contractAddress: string): boolean {
} }
*/ */
export async function relayVaaToEvm( export async function relayVaaToEvm(vaaBytes: string, t3Payload: Type3Payload) {
signedVaaArray: Uint8Array, let ecd = evmContractData.get(t3Payload.targetChainId);
t3Payload: Type3Payload if (!ecd) {
) { logger.error(
"relayVaaToEvm: chain id " + t3Payload.targetChainId + " does not exist!"
);
}
let exactIn: boolean = false; let exactIn: boolean = false;
let error: boolean = false;
if (t3Payload.swapFunctionType === 1) { if (t3Payload.swapFunctionType === 1) {
exactIn = true; exactIn = true;
} else if (t3Payload.swapFunctionType !== 2) { } else if (t3Payload.swapFunctionType !== 2) {
error = true;
logger.error( logger.error(
"relayVaa: unsupported swapFunctionType: [" + "relayVaaTo" +
ecd.name +
": unsupported swapFunctionType: [" +
t3Payload.swapFunctionType + t3Payload.swapFunctionType +
"]" "]"
); );
@ -196,48 +257,30 @@ export async function relayVaaToEvm(
if (t3Payload.swapCurrencyType === 1) { if (t3Payload.swapCurrencyType === 1) {
native = true; native = true;
} else if (t3Payload.swapCurrencyType !== 2) { } else if (t3Payload.swapCurrencyType !== 2) {
error = true;
logger.error( logger.error(
"relayVaa: unsupported swapCurrencyType: [" + "relayVaaTo" +
ecd.name +
": unsupported swapCurrencyType: [" +
t3Payload.swapCurrencyType + t3Payload.swapCurrencyType +
"]" "]"
); );
} }
if (error) return;
logger.debug( logger.debug(
"relayVaa: contractAddress: [" + "relayVaaTo" +
ecd.name +
": chain_id: " +
ecd.chain_id +
", contractAddress: [" +
t3Payload.contractAddress + t3Payload.contractAddress +
"], ethContract: [" +
ethContractData.contractAddress +
"], polygonContract[" +
polygonContractData.contractAddress +
"]" "]"
); );
if (t3Payload.contractAddress === ethContractData.contractAddress) { const signedVaaArray = hexToUint8Array(vaaBytes);
await relayVaaToEvmChain( await relayVaaToEvmChain(t3Payload, ecd, signedVaaArray, exactIn, native);
t3Payload,
ethContractData,
signedVaaArray,
exactIn,
native
);
} else if (
t3Payload.contractAddress === polygonContractData.contractAddress
) {
await relayVaaToEvmChain(
t3Payload,
polygonContractData,
signedVaaArray,
exactIn,
native
);
} else {
logger.error(
"relayVaa: unexpected contract: [" +
t3Payload.contractAddress +
"], this should not happen!"
);
}
} }
async function relayVaaToEvmChain( async function relayVaaToEvmChain(
@ -257,17 +300,21 @@ async function relayVaaToEvmChain(
"]" "]"
); );
if (await isRedeemedOnEvm(t3Payload, tcd, signedVaaArray)) { if (await isRedeemedOnEvm(tcd, signedVaaArray)) {
logger.info( logger.info(
"relayVaaTo" + "relayVaaTo" +
tcd.name + tcd.name +
": contract: [" + ": srcChain: " +
t3Payload.sourceChainId +
", targetChain: " +
t3Payload.targetChainId +
", contract: [" +
t3Payload.contractAddress + t3Payload.contractAddress +
"], exactIn: " + "], exactIn: " +
exactIn + exactIn +
", native: " + ", native: " +
native + native +
": already transferred" ": completed: already transferred"
); );
return; return;
@ -276,7 +323,11 @@ async function relayVaaToEvmChain(
logger.info( logger.info(
"relayVaaTo" + "relayVaaTo" +
tcd.name + tcd.name +
": contract: [" + ": srcChain: " +
t3Payload.sourceChainId +
", targetChain: " +
t3Payload.targetChainId +
", contract: [" +
t3Payload.contractAddress + t3Payload.contractAddress +
"], exactIn: " + "], exactIn: " +
exactIn + exactIn +
@ -289,24 +340,28 @@ async function relayVaaToEvmChain(
let receipt: any = null; let receipt: any = null;
if (exactIn) { if (exactIn) {
if (native) { if (native) {
receipt = await swap.swapExactInFromVaaNative( logger.debug("relayVaaTo: calling evmSwapExactInFromVaaNative()");
receipt = await swap.evmSwapExactInFromVaaNative(
tcd.contractWithSigner, tcd.contractWithSigner,
signedVaaArray signedVaaArray
); );
} else { } else {
receipt = await swap.swapExactInFromVaaToken( logger.debug("relayVaaTo: calling evmSwapExactInFromVaaToken()");
receipt = await swap.evmSwapExactInFromVaaToken(
tcd.contractWithSigner, tcd.contractWithSigner,
signedVaaArray signedVaaArray
); );
} }
} else { } else {
if (native) { if (native) {
receipt = await swap.swapExactOutFromVaaNative( logger.debug("relayVaaTo: calling evmSwapExactOutFromVaaNative()");
receipt = await swap.evmSwapExactOutFromVaaNative(
tcd.contractWithSigner, tcd.contractWithSigner,
signedVaaArray signedVaaArray
); );
} else { } else {
receipt = await swap.swapExactOutFromVaaToken( logger.debug("relayVaaTo: calling evmSwapExactOutFromVaaToken()");
receipt = await swap.evmSwapExactOutFromVaaToken(
tcd.contractWithSigner, tcd.contractWithSigner,
signedVaaArray signedVaaArray
); );
@ -316,27 +371,35 @@ async function relayVaaToEvmChain(
logger.info( logger.info(
"relayVaaTo" + "relayVaaTo" +
tcd.name + tcd.name +
": contract: [" + ": srcChain: " +
t3Payload.sourceChainId +
", targetChain: " +
t3Payload.targetChainId +
", contract: [" +
t3Payload.contractAddress + t3Payload.contractAddress +
"], exactIn: " + "], exactIn: " +
exactIn + exactIn +
", native: " + ", native: " +
native + native +
": success, txHash: " + ": completed: success, txHash: " +
receipt.transactionHash receipt.transactionHash
); );
} catch (e: any) { } catch (e: any) {
if (await isRedeemedOnEvm(t3Payload, tcd, signedVaaArray)) { if (await isRedeemedOnEvm(tcd, signedVaaArray)) {
logger.info( logger.info(
"relayVaaTo" + "relayVaaTo" +
tcd.name + tcd.name +
": contract: [" + ": srcChain: " +
t3Payload.sourceChainId +
", targetChain: " +
t3Payload.targetChainId +
", contract: [" +
t3Payload.contractAddress + t3Payload.contractAddress +
"], exactIn: " + "], exactIn: " +
exactIn + exactIn +
", native: " + ", native: " +
native + native +
": relay failed because the vaa has already been redeemed" ": completed: relay failed because the vaa has already been redeemed"
); );
return; return;
@ -356,35 +419,42 @@ async function relayVaaToEvmChain(
); );
} }
if (await isRedeemedOnEvm(t3Payload, tcd, signedVaaArray)) { if (await isRedeemedOnEvm(tcd, signedVaaArray)) {
logger.info( logger.info(
"relayVaaTo" + "relayVaaTo" +
tcd.name + tcd.name +
": contract: [" + ": srcChain: " +
t3Payload.sourceChainId +
", targetChain: " +
t3Payload.targetChainId +
", contract: [" +
t3Payload.contractAddress + t3Payload.contractAddress +
"], exactIn: " + "], exactIn: " +
exactIn + exactIn +
", native: " + ", native: " +
native + native +
": redeem succeeded" ": redeem confirmed"
); );
} else { } else {
logger.error( logger.error(
"relayVaaTo" + "relayVaaTo" +
tcd.name + tcd.name +
": contract: [" + ": srcChain: " +
t3Payload.sourceChainId +
", targetChain: " +
t3Payload.targetChainId +
", contract: [" +
t3Payload.contractAddress + t3Payload.contractAddress +
"], exactIn: " + "], exactIn: " +
exactIn + exactIn +
", native: " + ", native: " +
native + native +
": redeem failed!" ": completed: failed to confirm redeem!"
); );
} }
} }
async function isRedeemedOnEvm( async function isRedeemedOnEvm(
t3Payload: Type3Payload,
tcd: EvmContractData, tcd: EvmContractData,
signedVaaArray: Uint8Array signedVaaArray: Uint8Array
): Promise<boolean> { ): Promise<boolean> {

View File

@ -10,7 +10,6 @@ import {
getEmitterAddressEth, getEmitterAddressEth,
getEmitterAddressSolana, getEmitterAddressSolana,
getEmitterAddressTerra, getEmitterAddressTerra,
getIsTransferCompletedEth,
} from "@certusone/wormhole-sdk"; } from "@certusone/wormhole-sdk";
import { import {
@ -25,17 +24,20 @@ import {
import { ethers } from "ethers"; import { ethers } from "ethers";
import { abi as SWAP_CONTRACT_V2_ABI } from "../../react/src/abi/contracts/CrossChainSwapV2.json"; import {
import { abi as SWAP_CONTRACT_V3_ABI } from "../../react/src/abi/contracts/CrossChainSwapV3.json"; EvmEnvironment,
isEvmContract,
import * as swap from "../../react/src/swapper/util"; loadEvmConfig,
makeEvmContractData,
import { isEvmContract, makeEvmContractData, relayVaaToEvm } from "./evm"; relayVaaToEvm,
} from "./evm";
import { import {
isTerraContract, isTerraContract,
loadTerraConfig,
makeTerraContractData, makeTerraContractData,
relayVaaToTerra, relayVaaToTerra,
TerraEnvironment,
} from "./terra"; } from "./terra";
export let logger: any; export let logger: any;
@ -54,37 +56,12 @@ export type OurEnvironment = {
spy_host: string; spy_host: string;
spy_filters: string; spy_filters: string;
eth_provider_url: string; evm_configs: EvmEnvironment[];
eth_contract_address: string; terra_config: TerraEnvironment;
eth_token_bridge_address: string;
polygon_provider_url: string;
polygon_contract_address: string;
polygon_token_bridge_address: string;
evm_wallet_private_key: string;
terraEnabled: boolean;
terra_provider_url: string;
terra_chain_id: string;
terra_name: string;
terra_contract_address: string;
terra_token_bridge_address: string;
terra_wallet_private_key: string;
terra_gas_price_url: string;
};
type EvmContractData = {
name: string;
contractAddress: string;
tokenBridgeAddress: string;
contract: ethers.Contract;
provider: ethers.providers.StaticJsonRpcProvider;
wallet: ethers.Wallet;
contractWithSigner: ethers.Contract;
}; };
export type Type3Payload = { export type Type3Payload = {
sourceChainId: number;
targetChainId: number; targetChainId: number;
contractAddress: string; contractAddress: string;
relayerFee: ethers.BigNumber; relayerFee: ethers.BigNumber;
@ -112,16 +89,14 @@ let pendingQueue = new Array<PendingEvent>();
if (success) { if (success) {
logger.info( logger.info(
"swap_relay starting up, will listen for signed VAAs from [" + "swap_relayer starting up, will listen for signed VAAs from [" +
env.spy_host + env.spy_host +
"]" "]"
); );
try { try {
makeEvmContractData(env); makeEvmContractData(env.evm_configs);
if (env.terraEnabled) { makeTerraContractData(env.terra_config);
makeTerraContractData(env);
}
} catch (e: any) { } catch (e: any) {
logger.error("failed to connect to target contracts: %o", e); logger.error("failed to connect to target contracts: %o", e);
success = false; success = false;
@ -139,98 +114,22 @@ function loadConfig(): [boolean, OurEnvironment] {
return [false, undefined]; return [false, undefined];
} }
if (!process.env.ETH_PROVIDER) { let evm_configs: EvmEnvironment[] = null;
logger.error("Missing environment variable ETH_PROVIDER"); if (process.env.EVM_CHAINS) {
return [false, undefined]; evm_configs = loadEvmConfig();
} if (!evm_configs) return [false, undefined];
if (!process.env.ETH_CONTRACT_ADDRESS) {
logger.error("Missing environment variable ETH_CONTRACT_ADDRESS");
return [false, undefined];
}
if (!process.env.ETH_TOKEN_BRIDGE_ADDRESS) {
logger.error("Missing environment variable ETH_TOKEN_BRIDGE_ADDRESS");
return [false, undefined];
} }
if (!process.env.POLYGON_PROVIDER) { let terra_config = loadTerraConfig();
logger.error("Missing environment variable POLYGON_PROVIDER"); if (!terra_config) return [false, undefined];
return [false, undefined];
}
if (!process.env.POLYGON_CONTRACT_ADDRESS) {
logger.error("Missing environment variable POLYGON_CONTRACT_ADDRESS");
return [false, undefined];
}
if (!process.env.POLYGON_TOKEN_BRIDGE_ADDRESS) {
logger.error("Missing environment variable POLYGON_TOKEN_BRIDGE_ADDRESS");
return [false, undefined];
}
if (!process.env.WALLET_PRIVATE_KEY) {
logger.error("Missing environment variable WALLET_PRIVATE_KEY");
return [false, undefined];
}
let terraEnabled: boolean = false;
if (process.env.TERRA_PROVIDER) {
terraEnabled = true;
if (!process.env.TERRA_CHAIN_ID) {
logger.error("Missing environment variable WALLET_PRIVATE_KEY");
return [false, undefined];
throw "Missing environment variable TERRA_CHAIN_ID";
}
if (!process.env.TERRA_NAME) {
logger.error("Missing environment variable WALLET_PRIVATE_KEY");
return [false, undefined];
throw "Missing environment variable TERRA_NAME";
}
if (!process.env.TERRA_WALLET_PRIVATE_KEY) {
logger.error("Missing environment variable TERRA_WALLET_PRIVATE_KEY");
return [false, undefined];
}
if (!process.env.TERRA_GAS_PRICE_URL) {
logger.error("Missing environment variable TERRA_GAS_PRICE_URL");
return [false, undefined];
}
if (!process.env.TERRA_CONTRACT_ADDRESS) {
logger.error("Missing environment variable TERRA_CONTRACT_ADDRESS");
return [false, undefined];
}
if (!process.env.TERRA_TOKEN_BRIDGE_ADDRESS) {
logger.error("Missing environment variable TERRA_TOKEN_BRIDGE_ADDRESS");
return [false, undefined];
}
}
return [ return [
true, true,
{ {
spy_host: process.env.SPY_SERVICE_HOST, spy_host: process.env.SPY_SERVICE_HOST,
spy_filters: process.env.SPY_SERVICE_FILTERS, spy_filters: process.env.SPY_SERVICE_FILTERS,
evm_configs: evm_configs,
eth_provider_url: process.env.ETH_PROVIDER, terra_config: terra_config,
eth_contract_address: process.env.ETH_CONTRACT_ADDRESS,
eth_token_bridge_address: process.env.ETH_TOKEN_BRIDGE_ADDRESS,
polygon_provider_url: process.env.POLYGON_PROVIDER,
polygon_contract_address: process.env.POLYGON_CONTRACT_ADDRESS,
polygon_token_bridge_address: process.env.POLYGON_TOKEN_BRIDGE_ADDRESS,
evm_wallet_private_key: process.env.WALLET_PRIVATE_KEY,
terraEnabled: terraEnabled,
terra_provider_url: process.env.TERRA_PROVIDER,
terra_chain_id: process.env.TERRA_CHAIN_ID,
terra_name: process.env.TERRA_NAME,
terra_contract_address: process.env.TERRA_CONTRACT_ADDRESS,
terra_token_bridge_address: process.env.TERRA_TOKEN_BRIDGE_ADDRESS,
terra_wallet_private_key: process.env.TERRA_WALLET_PRIVATE_KEY,
terra_gas_price_url: process.env.TERRA_GAS_PRICE_URL,
}, },
]; ];
} }
@ -279,7 +178,7 @@ async function spy_listen() {
processVaa(vaaBytes); processVaa(vaaBytes);
}); });
logger.info("swap_relay waiting for transfer signed VAAs"); logger.info("swap_relayer waiting for transfer signed VAAs");
})(); })();
} }
@ -300,10 +199,10 @@ async function encodeEmitterAddress(
async function processVaa(vaaBytes: string) { async function processVaa(vaaBytes: string) {
let receiveTime = new Date(); let receiveTime = new Date();
logger.debug("processVaa"); // logger.debug("processVaa");
const { parse_vaa } = await importCoreWasm(); const { parse_vaa } = await importCoreWasm();
const parsedVAA = parse_vaa(hexToUint8Array(vaaBytes)); const parsedVAA = parse_vaa(hexToUint8Array(vaaBytes));
logger.debug("processVaa: parsedVAA: %o", parsedVAA); // logger.debug("processVaa: parsedVAA: %o", parsedVAA);
let emitter_address: string = uint8ArrayToHex(parsedVAA.emitter_address); let emitter_address: string = uint8ArrayToHex(parsedVAA.emitter_address);
@ -326,14 +225,14 @@ async function processVaa(vaaBytes: string) {
let t3Payload: Type3Payload = null; let t3Payload: Type3Payload = null;
try { try {
t3Payload = decodeSignedVAAPayloadType3(parsedVAA); t3Payload = decodeSignedVAAPayloadType3(parsedVAA, parsedVAA.emitter_chain);
} catch (e) { } catch (e) {
logger.error("failed to parse type 3 vaa: %o", e); logger.error("failed to parse type 3 vaa: %o", e);
return; return;
} }
if (t3Payload) { if (t3Payload) {
if (isOurContract(t3Payload.contractAddress)) { if (isOurContract(t3Payload.contractAddress, t3Payload.targetChainId)) {
logger.info( logger.info(
"enqueuing type 3 vaa: emitter: [" + "enqueuing type 3 vaa: emitter: [" +
parsedVAA.emitter_chain + parsedVAA.emitter_chain +
@ -341,7 +240,9 @@ async function processVaa(vaaBytes: string) {
emitter_address + emitter_address +
"], seqNum: " + "], seqNum: " +
parsedVAA.sequence + parsedVAA.sequence +
", contractAddress: [" + ", target: [" +
t3Payload.targetChainId +
":" +
t3Payload.contractAddress + t3Payload.contractAddress +
"], relayerFee: [" + "], relayerFee: [" +
t3Payload.relayerFee + t3Payload.relayerFee +
@ -361,7 +262,9 @@ async function processVaa(vaaBytes: string) {
emitter_address + emitter_address +
"], seqNum: " + "], seqNum: " +
parsedVAA.sequence + parsedVAA.sequence +
", contractAddress: [" + ", target: [" +
t3Payload.targetChainId +
":" +
t3Payload.contractAddress + t3Payload.contractAddress +
"], relayerFee: [" + "], relayerFee: [" +
t3Payload.relayerFee + t3Payload.relayerFee +
@ -372,21 +275,24 @@ async function processVaa(vaaBytes: string) {
"]" "]"
); );
} }
} else { // } else {
logger.debug( // logger.debug(
"dropping vaa: emitter: [" + // "dropping vaa: emitter: [" +
parsedVAA.emitter_chain + // parsedVAA.emitter_chain +
":" + // ":" +
emitter_address + // emitter_address +
"], seqNum: " + // "], seqNum: " +
parsedVAA.sequence + // parsedVAA.sequence +
" payloadType: " + // " payloadType: " +
parsedVAA.payload[0] // parsedVAA.payload[0]
); // );
} }
} }
function decodeSignedVAAPayloadType3(parsedVAA: any): Type3Payload { function decodeSignedVAAPayloadType3(
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;
@ -412,11 +318,6 @@ function decodeSignedVAAPayloadType3(parsedVAA: any): Type3Payload {
payload.slice(67, 67 + 32).toString("hex") + payload.slice(67, 67 + 32).toString("hex") +
"]" "]"
); );
logger.info(
"decodeSignedVAAPayloadType3: terraContractAddr: [" +
payload.slice(67, 67 + 32).toString() +
"]"
);
contractAddress = payload.slice(67, 67 + 32).toString("hex"); contractAddress = payload.slice(67, 67 + 32).toString("hex");
} else { } else {
@ -431,21 +332,25 @@ function decodeSignedVAAPayloadType3(parsedVAA: any): Type3Payload {
} }
contractAddress = payload.slice(79, 79 + 20).toString("hex"); contractAddress = payload.slice(79, 79 + 20).toString("hex");
swapFunctionType = payload.readUInt8(260); swapFunctionType = payload.readUInt8(272);
swapCurrencyType = payload.readUInt8(261); swapCurrencyType = payload.readUInt8(273);
} }
return { return {
sourceChainId: sourceChainId,
targetChainId: targetChainId, targetChainId: targetChainId,
contractAddress: payload.slice(79, 79 + 20).toString("hex"), contractAddress: contractAddress,
relayerFee: ethers.BigNumber.from(payload.slice(101, 101 + 32)), relayerFee: ethers.BigNumber.from(payload.slice(101, 101 + 32)),
swapFunctionType: swapFunctionType, swapFunctionType: swapFunctionType,
swapCurrencyType: swapCurrencyType, swapCurrencyType: swapCurrencyType,
}; };
} }
function isOurContract(contractAddress: string): boolean { function isOurContract(contractAddress: string, chainId: number): boolean {
return isEvmContract(contractAddress) || isTerraContract(contractAddress); return (
isEvmContract(contractAddress, chainId) ||
isTerraContract(contractAddress, chainId)
);
} }
async function postVaa( async function postVaa(
@ -533,14 +438,12 @@ async function callBack(err: any, result: any) {
} }
async function relayVaa(vaaBytes: string, t3Payload: Type3Payload) { async function relayVaa(vaaBytes: string, t3Payload: Type3Payload) {
const signedVaaArray = hexToUint8Array(vaaBytes);
if (t3Payload.targetChainId === 3) { if (t3Payload.targetChainId === 3) {
relayVaaToTerra(t3Payload, signedVaaArray); await relayVaaToTerra(t3Payload, vaaBytes);
return; return;
} }
relayVaaToEvm(signedVaaArray, t3Payload); await relayVaaToEvm(vaaBytes, t3Payload);
} }
///////////////////////////////// Start of logger stuff /////////////////////////////////////////// ///////////////////////////////// Start of logger stuff ///////////////////////////////////////////

View File

@ -1,20 +1,34 @@
import { import {
CHAIN_ID_TERRA,
getIsTransferCompletedTerra, getIsTransferCompletedTerra,
redeemOnTerra, redeemOnTerra,
uint8ArrayToHex,
} from "@certusone/wormhole-sdk"; } from "@certusone/wormhole-sdk";
import { import axios from "axios";
importCoreWasm, import { bech32 } from "bech32";
setDefaultWasm, import { zeroPad } from "ethers/lib/utils";
} from "@certusone/wormhole-sdk/lib/cjs/solana/wasm"; import { fromUint8Array } from "js-base64";
import * as Terra from "@terra-money/terra.js"; import * as Terra from "@terra-money/terra.js";
import { logger, OurEnvironment, Type3Payload } from "./index"; import { logger, OurEnvironment, Type3Payload } from "./index";
export type TerraEnvironment = {
terra_enabled: boolean;
terra_provider_url: string;
terra_chain_id: string;
terra_name: string;
terra_contract_address: string;
terra_token_bridge_address: string;
terra_wallet_private_key: string;
terra_gas_price_url: string;
};
type TerraContractData = { type TerraContractData = {
name: string; name: string;
contractAddress: string; contractAddress: string;
encodedContractAddress: string;
tokenBridgeAddress: string; tokenBridgeAddress: string;
lcdConfig: Terra.LCDClientConfig; lcdConfig: Terra.LCDClientConfig;
lcdClient: Terra.LCDClient; lcdClient: Terra.LCDClient;
@ -24,15 +38,70 @@ type TerraContractData = {
let terraContractData: TerraContractData = null; let terraContractData: TerraContractData = null;
export function makeTerraContractData(env: OurEnvironment) { export function loadTerraConfig(): TerraEnvironment {
let terra_enabled: boolean = false;
if (process.env.TERRA_PROVIDER) {
terra_enabled = true;
if (!process.env.TERRA_CHAIN_ID) {
logger.error("Missing environment variable WALLET_PRIVATE_KEY");
return undefined;
}
if (!process.env.TERRA_NAME) {
logger.error("Missing environment variable WALLET_PRIVATE_KEY");
return undefined;
}
if (!process.env.TERRA_WALLET_PRIVATE_KEY) {
logger.error("Missing environment variable TERRA_WALLET_PRIVATE_KEY");
return undefined;
}
if (!process.env.TERRA_GAS_PRICE_URL) {
logger.error("Missing environment variable TERRA_GAS_PRICE_URL");
return undefined;
}
if (!process.env.TERRA_CONTRACT_ADDRESS) {
logger.error("Missing environment variable TERRA_CONTRACT_ADDRESS");
return undefined;
}
if (!process.env.TERRA_TOKEN_BRIDGE_ADDRESS) {
logger.error("Missing environment variable TERRA_TOKEN_BRIDGE_ADDRESS");
return undefined;
}
}
return {
terra_enabled: terra_enabled,
terra_provider_url: process.env.TERRA_PROVIDER,
terra_chain_id: process.env.TERRA_CHAIN_ID,
terra_name: process.env.TERRA_NAME,
terra_contract_address: process.env.TERRA_CONTRACT_ADDRESS,
terra_token_bridge_address: process.env.TERRA_TOKEN_BRIDGE_ADDRESS,
terra_wallet_private_key: process.env.TERRA_WALLET_PRIVATE_KEY,
terra_gas_price_url: process.env.TERRA_GAS_PRICE_URL,
};
}
export function makeTerraContractData(env: TerraEnvironment) {
if (!env.terra_enabled) return;
let contractAddress: string = env.terra_contract_address.toLowerCase(); let contractAddress: string = env.terra_contract_address.toLowerCase();
if (contractAddress.search("0x") == 0) { if (contractAddress.search("0x") == 0) {
contractAddress = contractAddress.substring(2); contractAddress = contractAddress.substring(2);
} }
let encodedContractAddress: string = Buffer.from(
zeroPad(bech32.fromWords(bech32.decode(contractAddress).words), 32)
).toString("hex");
logger.info( logger.info(
"Connecting to Terra: contract address: [" + "Connecting to Terra: contract address: [" +
contractAddress + contractAddress +
"], encoded contract address: [" +
encodedContractAddress +
"], node: [" + "], node: [" +
env.terra_provider_url + env.terra_provider_url +
"], token bridge address: [" + "], token bridge address: [" +
@ -61,6 +130,7 @@ export function makeTerraContractData(env: OurEnvironment) {
terraContractData = { terraContractData = {
name: "Terra", name: "Terra",
contractAddress: contractAddress, contractAddress: contractAddress,
encodedContractAddress: encodedContractAddress,
tokenBridgeAddress: env.terra_token_bridge_address, tokenBridgeAddress: env.terra_token_bridge_address,
lcdConfig: lcdConfig, lcdConfig: lcdConfig,
lcdClient: lcdClient, lcdClient: lcdClient,
@ -69,111 +139,221 @@ export function makeTerraContractData(env: OurEnvironment) {
}; };
} }
export function isTerraContract(contractAddress: string): boolean { export function isTerraContract(
return ( contractAddress: string,
terraContractData && contractAddress === terraContractData.contractAddress chain_id: number
): boolean {
if (chain_id !== CHAIN_ID_TERRA) return false;
if (!terraContractData) return false;
let retVal: boolean =
terraContractData &&
contractAddress === terraContractData.encodedContractAddress;
logger.debug(
"isTerraContract: comparing [" +
contractAddress +
"] to [" +
terraContractData.encodedContractAddress +
"]: " +
retVal
); );
return retVal;
} }
export async function relayVaaToTerra( export async function relayVaaToTerra(
t3Payload: Type3Payload, t3Payload: Type3Payload,
signedVaaArray: Uint8Array vaaBytes: string
) { ) {
if (!terraContractData) return; if (!terraContractData) return;
logger.debug( // logger.debug(
"relayVaaToTerra: checking if already redeemed using tokenBridgeAddress [" + // "relayVaaToTerra: checking if already redeemed using tokenBridgeAddress [" +
terraContractData.tokenBridgeAddress + // terraContractData.tokenBridgeAddress +
"]" // "]"
); // );
if (await isRedeemedOnTerra(t3Payload, terraContractData, signedVaaArray)) { // if (await isRedeemedOnTerra(terraContractData, vaaBytes)) {
logger.info( // logger.info(
"relayVaaToTerra: contract: [" + // "relayVaaToTerra: srcChain: " +
t3Payload.contractAddress + // t3Payload.sourceChainId +
"]: already transferred" // ", targetChain: " +
); // t3Payload.targetChainId +
// ", contract: [" +
// t3Payload.contractAddress +
// "]: completed: already redeemed"
// );
return; // return;
} // }
logger.info(
"relayVaaToTerra: contract: [" +
t3Payload.contractAddress +
"]: submitting redeem request"
);
try { try {
const msg = await redeemOnTerra( logger.debug(
terraContractData.contractAddress, "relayVaaToTerra: creating message using contract address [" +
terraContractData.wallet.key.accAddress, terraContractData.contractAddress +
signedVaaArray "]"
); );
let receipt: any = null; logger.debug("relayVaaToTerra: creating a message");
logger.debug(
"relayVaaToTerra: vaa as hex: [" +
Buffer.from(vaaBytes, "hex").toString("hex") +
"]"
);
logger.debug(
"relayVaaToTerra: vaa as base64: [" +
Buffer.from(vaaBytes, "hex").toString("base64") +
"]"
);
const msg = new Terra.MsgExecuteContract(
terraContractData.wallet.key.accAddress,
terraContractData.contractAddress,
{
redeem_payload: {
data: Buffer.from(vaaBytes, "hex").toString("base64"),
},
}
);
// logger.debug("relayVaaToTerra: getting gas prices");
// const gasPrices = terraContractData.lcdClient.config.gasPrices;
// logger.debug("relayVaaToTerra: estimating fees");
// const feeEstimate = await terraContractData.lcdClient.tx.estimateFee(terraContractData.wallet.key.accAddress, [msg], {
// feeDenoms: ["uluna"],
// gasPrices,
// });
logger.debug("relayVaaToTerra: creating transaction");
const tx = await terraContractData.wallet.createAndSignTx({
msgs: [msg],
memo: "swap relayer",
feeDenoms: ["uluna"],
// gasPrices,
// fee: feeEstimate,
});
logger.info( logger.info(
"relayVaaToTerra: contract: [" + "relayVaaToTerra: contract: [" +
t3Payload.contractAddress + t3Payload.contractAddress +
"]: success, txHash: " + "]: submitting redeem request"
receipt.transactionHash
); );
} catch (e: any) {
if (await isRedeemedOnTerra(t3Payload, terraContractData, signedVaaArray)) {
logger.info(
"relayVaaToTerra: contract: [" +
t3Payload.contractAddress +
"]: relay failed because the vaa has already been redeemed"
);
return; const receipt = await terraContractData.lcdClient.tx.broadcast(tx);
}
logger.info(
"relayVaaToTerra: srcChain: " +
t3Payload.sourceChainId +
", targetChain: " +
t3Payload.targetChainId +
", contract: [" +
t3Payload.contractAddress +
"]: completed: success: %o",
receipt
);
// logger.info(
// "relayVaaToTerra: contract: [" +
// t3Payload.contractAddress +
// "]: success, txHash: " +
// receipt.transactionHash
// );
} catch (e: any) {
// if (await isRedeemedOnTerra(terraContractData, vaaBytes)) {
// logger.info(
// "relayVaaToTerra: srcChain: " +
// t3Payload.sourceChainId +
// ", targetChain: " +
// t3Payload.targetChainId +
// ", contract: [" +
// t3Payload.contractAddress +
// "]: completed: relay failed because the vaa has already been redeemed"
// );
// return;
// }
logger.error( logger.error(
"relayVaaToTerra: contract: [" + "relayVaaToTerra: srcChain: " +
t3Payload.sourceChainId +
", targetChain: " +
t3Payload.targetChainId +
", contract: [" +
t3Payload.contractAddress + t3Payload.contractAddress +
"]: transaction failed: %o", "]: completed: transaction failed: %o",
e e
); );
} }
if (await isRedeemedOnTerra(t3Payload, terraContractData, signedVaaArray)) { // if (await isRedeemedOnTerra(terraContractData, vaaBytes)) {
logger.info( // logger.info(
"relayVaaToTerra: contract: [" + // "relayVaaToTerra: srcChain: " +
t3Payload.contractAddress + // t3Payload.sourceChainId +
"]: redeem succeeded" // ", targetChain: " +
); // t3Payload.targetChainId +
} else { // ", contract: [" +
logger.error( // t3Payload.contractAddress +
"relayVaaToTerra: contract: [" + // "]: redeem confirmed"
t3Payload.contractAddress + // );
"]: redeem failed!" // } else {
); // logger.error(
} // "relayVaaToTerra: srcChain: " +
// t3Payload.sourceChainId +
// ", targetChain: " +
// t3Payload.targetChainId +
// ", contract: [" +
// t3Payload.contractAddress +
// "]: completed: failed to confirm redeem!"
// );
// }
} }
async function isRedeemedOnTerra( async function isRedeemedOnTerra(
t3Payload: Type3Payload,
terraContractData: TerraContractData, terraContractData: TerraContractData,
signedVaaArray: Uint8Array vaaBytes: string
): Promise<boolean> { ): Promise<boolean> {
let redeemed: boolean = false; let msg: Terra.MsgExecuteContract = null;
let sequenceNumber: number = 0;
try { try {
redeemed = await await getIsTransferCompletedTerra( msg = new Terra.MsgExecuteContract(
terraContractData.tokenBridgeAddress,
signedVaaArray,
terraContractData.wallet.key.accAddress, terraContractData.wallet.key.accAddress,
terraContractData.lcdClient, terraContractData.tokenBridgeAddress,
terraContractData.gasPriceUrl {
submit_vaa: {
data: Buffer.from(vaaBytes, "hex").toString("base64"),
},
}
); );
sequenceNumber = await terraContractData.wallet.sequence();
} catch (e) { } catch (e) {
logger.error( logger.error(
"relayVaaTo" + "isRedeemedOnTerra: failed to create message or look up sequence number, e: %o",
terraContractData.name + e
": failed to check if transfer is already complete, will attempt the transfer, e: %o", );
return false;
}
try {
await terraContractData.lcdClient.tx.estimateFee(
[{ sequenceNumber: sequenceNumber }],
{
msgs: [msg],
}
);
} catch (e) {
if (e.response.data.error.includes("VaaAlreadyExecuted")) {
return true;
}
logger.error(
"isRedeemedOnTerra: failed to check if transfer is already complete, e: %o",
e e
); );
} }
return redeemed; return false;
} }