Browse Source

p2w-terra-relay: iface.ts review nits, naive impl for Terra

commit-id:0ecbfdd6
Stan Drozd 3 năm trước cách đây
mục cha
commit
67c7e34809

+ 13 - 0
third_party/pyth/p2w-terra-relay/src/helpers.ts

@@ -301,3 +301,16 @@ export function sleep(ms: number) {
 export function computePrice(rawPrice: BigInt, expo: number): number {
   return Number(rawPrice) * 10 ** expo;
 }
+
+// Shorthand for optional/mandatory envs
+export function envOrErr(env: string, defaultValue?: string): string {
+    let val = process.env[env];
+    if (!val) {
+	if (!defaultValue) {
+	    throw `environment variable "${env}" must be set`;
+	} else {
+	    return defaultValue;
+	}
+    }
+    return String(process.env[env]);
+}

+ 8 - 47
third_party/pyth/p2w-terra-relay/src/relay/iface.ts

@@ -1,55 +1,16 @@
 // This module describes a common interface in front of chain-specific
 // relay logic.
 
-/// Represents a target chain client config.
-export interface RelayConfig {
-    /// Helps pick an appropriate relay() call frequency. usually in
-    /// the neighbourhood of the chain's block time
-    relayIntervalMs: number;
-    /// Helps establish target chain's expected relay() error allowance
-    retryCount: number;
-    /// How long to wait on a relay() call before it's finalized on target chain
-    confirmationTimeoutMs: number;
-}
+export type PriceId = string;
 
 /// Represents a target chain relay client generically.
-export interface Relay<RelayConfig> {
-
-    /// Relay a signed Wormhole payload to this chain
-    relay(payload: string): Promise<any>;
-
-    /// Query price data on this chain
-    query(priceId: string): Promise<any>;
-
-    /// Monitor the payer account balance
-    getPayerInfo(): Promise<{address: string, balance: string}>;
-}
-
-
-// Example implementation
-
-class ChACfg implements RelayConfig {
-    relayIntervalMs: number = Number(process.env.CHAIN_A_BLOCK_TIME_MS) || 15;
-    retryCount: number = Number(process.env.CHAIN_A_RETRY_COUNT) || 5;
-    confirmationTimeoutMs: number = 10000;
-}
-
-class ChainA implements Relay<ChACfg> {
-    readonly config: ChACfg;
-    async relay(payload: string) {
-	return (async () => {}) ();
-    }
-    async query(priceId: string) {
-    }
+export interface Relay {
+  /// Relay a signed Wormhole payload to this chain
+  relay(signedVAAs: Array<string>): Promise<any>;
 
-    async getPayerInfo() {
-	return {
-	    address: "AliceThePayer",
-	    balance: "BasicallyAFortune",
-	}
-    }
+  /// Query price data on this chain
+  query(priceId: PriceId): Promise<any>;
 
-    constructor(c: ChACfg) {
-	this.config = c;
-    }
+  /// Monitor the payer account balance
+  getPayerInfo(): Promise<{ address: string; balance: number }>;
 }

+ 162 - 181
third_party/pyth/p2w-terra-relay/src/relay/terra.ts

@@ -8,219 +8,200 @@ import {
 import { hexToUint8Array } from "@certusone/wormhole-sdk";
 import { redeemOnTerra } from "@certusone/wormhole-sdk";
 
-import { logger } from "../helpers";
-
-export type TerraConnectionData = {
-  nodeUrl: string;
-  terraChainId: string;
-  terraName: string;
-  walletPrivateKey: string;
-  coin: string;
-  contractAddress: string;
-  lcdConfig: LCDClientConfig;
-  walletSeqNum: number;
-  walletAccountNum: number;
-};
-
-export function connectToTerra(): TerraConnectionData {
-  if (!process.env.TERRA_NODE_URL) {
-    throw "Missing environment variable TERRA_NODE_URL";
+import { logger, envOrErr } from "../helpers";
+
+import { Relay, PriceId } from "./iface";
+
+export class TerraRelay implements Relay {
+  readonly nodeUrl: string = envOrErr("TERRA_NODE_URL");
+  readonly terraChainId: string = envOrErr("TERRA_CHAIN_ID");
+  readonly terraName: string = envOrErr("TERRA_NAME");
+  readonly walletPrivateKey: string = envOrErr("TERRA_PRIVATE_KEY");
+  readonly coin: string = envOrErr("TERRA_COIN");
+  readonly contractAddress: string = envOrErr("TERRA_PYTH_CONTRACT_ADDRESS");
+  readonly lcdConfig: LCDClientConfig;
+  walletSeqNum: number = 0;
+  walletAccountNum: number = 0;
+
+  constructor() {
+    this.lcdConfig = {
+      URL: this.nodeUrl,
+      chainID: this.terraChainId,
+      // name: process.env.TERRA_NAME,
+    };
+    logger.info(
+      "Terra connection parameters: url: [" +
+        this.nodeUrl +
+        "], terraChainId: [" +
+        this.terraChainId +
+        "], terraName: [" +
+        this.terraName +
+        "], coin: [" +
+        this.coin +
+        "], contractAddress: [" +
+        this.contractAddress +
+        "]"
+    );
   }
 
-  if (!process.env.TERRA_CHAIN_ID) {
-    throw "Missing environment variable TERRA_CHAIN_ID";
-  }
+  async relay(signedVAAs: Array<string>) {
+    logger.debug("relaying " + signedVAAs.length + " messages to terra");
 
-  if (!process.env.TERRA_NAME) {
-    throw "Missing environment variable TERRA_NAME";
-  }
+    logger.debug("TIME: connecting to terra");
+    const lcdClient = new LCDClient(this.lcdConfig);
 
-  if (!process.env.TERRA_PRIVATE_KEY) {
-    throw "Missing environment variable TERRA_PRIVATE_KEY";
-  }
+    const mk = new MnemonicKey({
+      mnemonic: this.walletPrivateKey,
+    });
 
-  if (!process.env.TERRA_COIN) {
-    throw "Missing environment variable TERRA_COIN";
-  }
+    const wallet = lcdClient.wallet(mk);
+
+    logger.debug("TIME: creating messages");
+    let msgs = new Array<MsgExecuteContract>();
+    for (let idx = 0; idx < signedVAAs.length; ++idx) {
+      const msg = new MsgExecuteContract(
+        wallet.key.accAddress,
+        this.contractAddress,
+        {
+          submit_vaa: {
+            data: Buffer.from(signedVAAs[idx], "hex").toString("base64"),
+          },
+        }
+      );
 
-  if (!process.env.TERRA_PYTH_CONTRACT_ADDRESS) {
-    throw "Missing environment variable TERRA_PYTH_CONTRACT_ADDRESS";
+      msgs.push(msg);
+    }
+
+    // logger.debug("TIME: looking up gas");
+    // //Alternate FCD methodology
+    // //let gasPrices = await axios.get("http://localhost:3060/v1/txs/gas_prices").then((result) => result.data);
+    // const gasPrices = lcdClient.config.gasPrices;
+
+    // logger.debug("TIME: estimating fees");
+    // //const walletSequence = await wallet.sequence();
+    // const feeEstimate = await lcdClient.tx.estimateFee(
+    //   wallet.key.accAddress,
+    //   msgs,
+    //   {
+    //     //TODO figure out type mismatch
+    //     feeDenoms: [this.coin],
+    //     gasPrices,
+    //   }
+    // );
+
+    logger.debug(
+      "TIME: creating transaction using seq number " +
+        this.walletSeqNum +
+        " and account number " +
+        this.walletAccountNum
+    );
+    const tx = await wallet.createAndSignTx({
+      sequence: this.walletSeqNum,
+      accountNumber: this.walletAccountNum,
+      msgs: msgs,
+      memo: "P2T",
+      feeDenoms: [this.coin],
+    });
+
+    this.walletSeqNum = this.walletSeqNum + 1;
+
+    logger.debug("TIME: sending msg");
+    const receipt = await lcdClient.tx.broadcastSync(tx);
+    logger.debug("TIME:submitted to terra: receipt: %o", receipt);
+    return receipt;
   }
 
-  logger.info(
-    "Terra connection parameters: url: [" +
-      process.env.TERRA_NODE_URL +
-      "], terraChainId: [" +
-      process.env.TERRA_CHAIN_ID +
-      "], terraName: [" +
-      process.env.TERRA_NAME +
-      "], coin: [" +
-      process.env.TERRA_COIN +
-      "], contractAddress: [" +
-      process.env.TERRA_PYTH_CONTRACT_ADDRESS +
-      "]"
-  );
-
-  const lcdConfig = {
-    URL: process.env.TERRA_NODE_URL,
-    chainID: process.env.TERRA_CHAIN_ID,
-    name: process.env.TERRA_NAME,
-  };
-
-  return {
-    nodeUrl: process.env.TERRA_NODE_URL,
-    terraChainId: process.env.TERRA_CHAIN_ID,
-    terraName: process.env.TERRA_NAME,
-    walletPrivateKey: process.env.TERRA_PRIVATE_KEY,
-    coin: process.env.TERRA_COIN,
-    contractAddress: process.env.TERRA_PYTH_CONTRACT_ADDRESS,
-    lcdConfig: lcdConfig,
-    walletSeqNum: 0,
-    walletAccountNum: 0,
-  };
-}
+  async query(priceId: PriceId) {
+    const encodedPriceId = fromUint8Array(hexToUint8Array(priceId));
 
-export async function relayTerra(
-  connectionData: TerraConnectionData,
-  signedVAAs: Array<string>
-) {
-  logger.debug("relaying " + signedVAAs.length + " messages to terra");
+    logger.info(
+      "Querying terra for price info for priceId [" +
+        priceId +
+        "], encoded as [" +
+        encodedPriceId +
+        "]"
+    );
 
-  logger.debug("TIME: connecting to terra");
-  const lcdClient = new LCDClient(connectionData.lcdConfig);
+    const lcdClient = new LCDClient(this.lcdConfig);
 
-  const mk = new MnemonicKey({
-    mnemonic: connectionData.walletPrivateKey,
-  });
+    const mk = new MnemonicKey({
+      mnemonic: this.walletPrivateKey,
+    });
 
-  const wallet = lcdClient.wallet(mk);
+    const wallet = lcdClient.wallet(mk);
 
-  logger.debug("TIME: creating messages");
-  let msgs = new Array<MsgExecuteContract>();
-  for (let idx = 0; idx < signedVAAs.length; ++idx) {
-    const msg = new MsgExecuteContract(
-      wallet.key.accAddress,
-      connectionData.contractAddress,
+    const query_result = await lcdClient.wasm.contractQuery(
+      this.contractAddress,
       {
-        submit_vaa: {
-          data: Buffer.from(signedVAAs[idx], "hex").toString("base64"),
+        price_info: {
+          price_id: encodedPriceId,
         },
       }
     );
-
-    msgs.push(msg);
   }
 
-  // logger.debug("TIME: looking up gas");
-  // //Alternate FCD methodology
-  // //let gasPrices = await axios.get("http://localhost:3060/v1/txs/gas_prices").then((result) => result.data);
-  // const gasPrices = lcdClient.config.gasPrices;
-
-  // logger.debug("TIME: estimating fees");
-  // //const walletSequence = await wallet.sequence();
-  // const feeEstimate = await lcdClient.tx.estimateFee(
-  //   wallet.key.accAddress,
-  //   msgs,
-  //   {
-  //     //TODO figure out type mismatch
-  //     feeDenoms: [connectionData.coin],
-  //     gasPrices,
-  //   }
-  // );
-
-  logger.debug(
-    "TIME: creating transaction using seq number " +
-      connectionData.walletSeqNum +
-      " and account number " +
-      connectionData.walletAccountNum
-  );
-  const tx = await wallet.createAndSignTx({
-    sequence: connectionData.walletSeqNum,
-    accountNumber: connectionData.walletAccountNum,
-    msgs: msgs,
-    memo: "P2T",
-    feeDenoms: [connectionData.coin],
-  });
+  async getPayerInfo(): Promise<{ address: string; balance: number }> {
+    const lcdClient = new LCDClient(this.lcdConfig);
+
+    const mk = new MnemonicKey({
+      mnemonic: this.walletPrivateKey,
+    });
+
+    const wallet = lcdClient.wallet(mk);
+
+    let balance: number = NaN;
+    try {
+      logger.debug("querying wallet balance");
+      let coins: any;
+      let pagnation: any;
+      [coins, pagnation] = await lcdClient.bank.balance(wallet.key.accAddress);
+      logger.debug("wallet query returned: %o", coins);
+      if (coins) {
+        let coin = coins.get(this.coin);
+        if (coin) {
+          balance = parseInt(coin.toData().amount);
+        } else {
+          logger.error(
+            "failed to query coin balance, coin [" +
+              this.coin +
+              "] is not in the wallet, coins: %o",
+            coins
+          );
+        }
+      } else {
+        logger.error("failed to query coin balance!");
+      }
+    } catch (e) {
+      logger.error("failed to query coin balance: %o", e);
+    }
+
+    return { address: wallet.key.accAddress, balance };
+  }
+}
 
-  connectionData.walletSeqNum = connectionData.walletSeqNum + 1;
+// TODO(2021-03-17): Propagate the use of the interface into worker/listener logic.
+export type TerraConnectionData = TerraRelay;
 
-  logger.debug("TIME: sending msg");
-  const receipt = await lcdClient.tx.broadcastSync(tx);
-  logger.debug("TIME:submitted to terra: receipt: %o", receipt);
-  return receipt;
+export function connectToTerra(): TerraConnectionData {
+  return new TerraRelay();
 }
 
+export async function relayTerra(
+  connectionData: TerraConnectionData,
+  signedVAAs: Array<string>
+) {}
+
 export async function queryTerra(
   connectionData: TerraConnectionData,
   priceIdStr: string
 ) {
-  const encodedPriceId = fromUint8Array(hexToUint8Array(priceIdStr));
-
-  logger.info(
-    "Querying terra for price info for priceId [" +
-      priceIdStr +
-      "], encoded as [" +
-      encodedPriceId +
-      "]"
-  );
-
-  const lcdClient = new LCDClient(connectionData.lcdConfig);
-
-  const mk = new MnemonicKey({
-    mnemonic: connectionData.walletPrivateKey,
-  });
-
-  const wallet = lcdClient.wallet(mk);
-
-  const query_result = await lcdClient.wasm.contractQuery(
-    connectionData.contractAddress,
-    {
-      price_info: {
-        price_id: encodedPriceId,
-      },
-    }
-  );
-
+  let query_result = await connectionData.query(priceIdStr);
   logger.debug("queryTerra: query returned: %o", query_result);
   return query_result;
 }
 
 export async function queryBalanceOnTerra(connectionData: TerraConnectionData) {
-  const lcdClient = new LCDClient(connectionData.lcdConfig);
-
-  const mk = new MnemonicKey({
-    mnemonic: connectionData.walletPrivateKey,
-  });
-
-  const wallet = lcdClient.wallet(mk);
-
-  let balance: number = NaN;
-  try {
-    logger.debug("querying wallet balance");
-    let coins: any;
-    let pagnation: any;
-    [coins, pagnation] = await lcdClient.bank.balance(wallet.key.accAddress);
-    logger.debug("wallet query returned: %o", coins);
-    if (coins) {
-      let coin = coins.get(connectionData.coin);
-      if (coin) {
-        balance = parseInt(coin.toData().amount);
-      } else {
-        logger.error(
-          "failed to query coin balance, coin [" +
-            connectionData.coin +
-            "] is not in the wallet, coins: %o",
-          coins
-        );
-      }
-    } else {
-      logger.error("failed to query coin balance!");
-    }
-  } catch (e) {
-    logger.error("failed to query coin balance: %o", e);
-  }
-
-  return balance;
+  return (await connectionData.getPayerInfo()).balance;
 }
 
 export async function setAccountNumOnTerra(