瀏覽代碼

near/audit2: more audit cleanup

Josh Siegel 3 年之前
父節點
當前提交
0fc3e8badc

+ 14 - 0
near/Dockerfile.build

@@ -0,0 +1,14 @@
+FROM debian@sha256:2ce44bbc00a79113c296d9d25524e15d423b23303fdbbe20190d2f96e0aeb251 as near-contracts-build
+
+RUN apt-get update && apt-get install apt-utils && apt-get install -y python3 npm curl --no-install-recommends
+RUN apt-get install -y build-essential git
+
+ADD . .
+RUN ./setup-rust.sh
+RUN ./build-contracts.sh
+
+FROM scratch AS near-contracts-export
+
+COPY --from=near-contracts-build /contracts/token-bridge/target/wasm32-unknown-unknown/release/near_token_bridge.wasm near_token_bridge.wasm
+COPY --from=near-contracts-build /contracts/wormhole/target/wasm32-unknown-unknown/release/near_wormhole.wasm near_wormhole.wasm
+

+ 8 - 0
near/Makefile

@@ -4,6 +4,14 @@ test: build node_modules
 
 
 all: node_modules build nearcore
 all: node_modules build nearcore
 
 
+.PHONY: artifacts
+artifacts:
+	mkdir -p $@
+	@echo "Building artifacts for near"
+	DOCKER_BUILDKIT=1 docker build -f Dockerfile.build -t near-builder -o type=local,dest=$@ .
+	cd $@ && ls | xargs sha256sum > checksums.txt
+
+
 build:    contracts/ft/target/wasm32-unknown-unknown/release/near_ft.wasm \
 build:    contracts/ft/target/wasm32-unknown-unknown/release/near_ft.wasm \
           contracts/mock-bridge-integration/target/wasm32-unknown-unknown/release/near_mock_bridge_integration.wasm \
           contracts/mock-bridge-integration/target/wasm32-unknown-unknown/release/near_mock_bridge_integration.wasm \
           contracts/mock-bridge-token/target/wasm32-unknown-unknown/release/near_mock_bridge_token.wasm \
           contracts/mock-bridge-token/target/wasm32-unknown-unknown/release/near_mock_bridge_token.wasm \

+ 0 - 155
near/NOTES.md

@@ -1,155 +0,0 @@
-01000000000100bc708675768ba853af2e01c6217317fbb68500a508a39fece15dac27d36c7c804cf94ad9e4b2134209bbdad7c9479d9929c60bda4612a74c61c06efebacc4da401000003cb5d690100000200000000000000000000000026b4afb60d6c903165150c6f0aa14f8016be4aec00000000000000010f01000000000000000000000000d1a269d9b0dfb66cfdaf89cf0c6e6f8df0615ad00002415045f09f9092000000000000000000000000000000000000000000000000004e6f7420616e2041504520f09f90920000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a5268747470733a2f2f636c6f7564666c6172652d697066732e636f6d2f697066732f516d65536a53696e4870506e6d586d73704d6a776958794e367a533445397a63636172694752336a7863615774712f31301212121212121212121212121212121212121212121212121212121212121212000f
-
---
-
-1) vaa is received for a user for a token where the user has not paid the storage deposit in that token
-2) vaa gets submitted without using the SDK which detects this and correctly registers/pays-storage-deposit
-
-choice 1:
-
-   VAA gets consumed but the tokens don't get transfered
-
-choice 2:
-
-   The token bridge proactively throws money at the token, which it hopes will get refunded, 
-   to pay for storage if it is not already there
-
-   VAA gets consumed but the money might not get refunded... due to token implementor being an asshat
-
-
-
-
-// demonstrates how to query the state without setting
-// up an account. (View methods only)
-const { providers } = require("near-api-js");
-//network config (replace testnet with mainnet or betanet)
-const provider = new providers.JsonRpcProvider("https://rpc.testnet.near.org");
-
-getState();
-
-async function getState() {
-  const rawResult = await provider.query({
-    request_type: "call_function",
-    account_id: "guest-book.testnet",
-    method_name: "getMessages",
-    args_base64: "e30=",
-    finality: "optimistic",
-  });
-
-  // format result
-  const res = JSON.parse(Buffer.from(rawResult.result).toString());
-  console.log(res);
-}
-
-
-import { Account as nearAccount } from "near-api-js";
-
-My impression is:
-
-   https://docs.near.org/docs/tutorials/near-indexer
-
-   https://thewiki.near.page/events-api   
-
-==
-kubectl exec -it near-0 -c near-node -- /bin/bash
-
-My NEAR notes so far...
-
-If needed, install `Rust`:
-
-  curl https://sh.rustup.rs -sSf | sh
-
-You need at least version 1.56 or later
-
-  rustup default 1.56
-  rustup update
-  rustup target add wasm32-unknown-unknown
-
-If needed, install `near-cli`:
-
-   npm install near-cli -g
-
-To install the npm dependencies of this test program
-
-   npm install
-
-for the near sdk, we are dependent on 4.0.0 or later  (where the ecrecover API is)
-
-  https://docs.rs/near-sdk/4.0.0/near_sdk/index.html
-  near-sdk = { version = "4.0.0", features = ["unstable"] }
-
-  This has been stuck into Cargo.toml
-
-to bring up the sandbox, start a tmux window and run
-
-  rm -rf _sandbox
-  mkdir -p _sandbox
-  near-sandbox --home _sandbox init
-  near-sandbox --home _sandbox run
-
-https://docs.near.org/docs/develop/contracts/sandbox
-
-First thing, lets put this in a docker in Tilt..
-
-vaa_verify?
-
-near-sdk-rs/near-sdk/src/environment/env.rs: (still unstable)
-
-    /// Recovers an ECDSA signer address from a 32-byte message `hash` and a corresponding `signature`
-    /// along with `v` recovery byte.
-    ///
-    /// Takes in an additional flag to check for malleability of the signature
-    /// which is generally only ideal for transactions.
-    ///
-    /// Returns 64 bytes representing the public key if the recovery was successful.
-    #[cfg(feature = "unstable")]
-    pub fn ecrecover(
-        hash: &[u8],
-        signature: &[u8],
-        v: u8,
-        malleability_flag: bool,
-    ) -> Option<[u8; 64]> {
-        unsafe {
-            let return_code = sys::ecrecover(
-                hash.len() as _,
-                hash.as_ptr() as _,
-                signature.len() as _,
-                signature.as_ptr() as _,
-                v as u64,
-                malleability_flag as u64,
-                ATOMIC_OP_REGISTER,
-            );
-            if return_code == 0 {
-                None
-            } else {
-                Some(read_register_fixed_64(ATOMIC_OP_REGISTER))
-            }
-        }
-    }
-
-you can look for test_ecrecover()    in the same file...
-
-When building the sandbox, it is on port 3030 and we will need access to the validator_key.json...
-
-curl http://localhost:3031/validator_key.json
-
-function getConfig(env) {
-  switch (env) {
-    case "sandbox":
-    case "local":
-      return {
-        networkId: "sandbox",
-        nodeUrl: "http://localhost:3030",
-        masterAccount: "test.near",
-        contractAccount: "wormhole.test.near",
-        keyPath: "./_sandbox/validator_key.json",
-      };
-  }
-}
-
-   .function_call(
-                b"new".to_vec(),
-                ft,
-                data.to_vec(),
-                vaa.sequence,
-            )

+ 0 - 6
near/README.md

@@ -1,8 +1,2 @@
--- bridge for nep141/nep148 tokens --
-
-https://nomicon.io/Standards/Tokens/FungibleToken/Core#reference-level-explanation
-
-
 welcome to wormhole on near
 welcome to wormhole on near
 
 
-all near tokens and $NEAR itself will go to the token account...

+ 5 - 0
near/build-contracts.sh

@@ -0,0 +1,5 @@
+#!/bin/bash
+
+source $HOME/.cargo/env
+make build
+

+ 28 - 28
near/contracts/token-bridge/src/lib.rs

@@ -587,7 +587,9 @@ impl TokenBridge {
                 file!(),
                 file!(),
                 line!()
                 line!()
             ));
             ));
-            ext_ft_contract::ext(asset_token_account.clone()).update_ft(
+            ext_ft_contract::ext(asset_token_account.clone())
+                .with_static_gas(Gas(10_000_000_000_000))
+                .update_ft(
                 ft,
                 ft,
                 data.to_vec(),
                 data.to_vec(),
                 vaa.sequence,
                 vaa.sequence,
@@ -802,7 +804,7 @@ impl TokenBridge {
             Promise::new(refund_to).transfer(env::attached_deposit());
             Promise::new(refund_to).transfer(env::attached_deposit());
             return ret;
             return ret;
         }
         }
-        let a = AccountId::new_unchecked(account);
+        let a = AccountId::try_from(account).unwrap();
         self.hash_map.insert(&account_hash, &a);
         self.hash_map.insert(&account_hash, &a);
 
 
         if env::storage_usage() < storage_used {
         if env::storage_usage() < storage_used {
@@ -1652,32 +1654,30 @@ impl TokenBridge {
         }
         }
     }
     }
 
 
-    #[init(ignore_state)]
-    #[payable]
-    pub fn migrate() -> Self {
-        if env::attached_deposit() != 1 {
-            env::panic_str("Need money");
-        }
-        let old_state: OldPortal = env::state_read().expect("failed");
-        if env::signer_account_pk() != old_state.owner_pk {
-            env::panic_str("CannotCallMigrate");
-        }
-        env::log_str(&format!("token-bridge/{}#{}: migrate", file!(), line!(),));
-        Self {
-            booted:               old_state.booted,
-            core:                 old_state.core,
-            gov_idx:              0,
-            dups:                 LookupMap::new(b"d".to_vec()),
-            owner_pk:             old_state.owner_pk,
-            emitter_registration: old_state.emitter_registration,
-            last_asset:           old_state.last_asset,
-            upgrade_hash:         old_state.upgrade_hash,
-            tokens:               old_state.tokens,
-            key_map:              old_state.key_map,
-            hash_map:             old_state.hash_map,
-            bank:                 old_state.bank,
-        }
-    }
+//    #[init(ignore_state)]
+//    #[payable]
+//    #[private]
+//    pub fn migrate() -> Self {
+//        if env::attached_deposit() != 1 {
+//            env::panic_str("Need money");
+//        }
+//        let old_state: OldPortal = env::state_read().expect("failed");
+//        env::log_str(&format!("token-bridge/{}#{}: migrate", file!(), line!(),));
+//        Self {
+//            booted:               old_state.booted,
+//            core:                 old_state.core,
+//            gov_idx:              0,
+//            dups:                 LookupMap::new(b"d".to_vec()),
+//            owner_pk:             old_state.owner_pk,
+//            emitter_registration: old_state.emitter_registration,
+//            last_asset:           old_state.last_asset,
+//            upgrade_hash:         old_state.upgrade_hash,
+//            tokens:               old_state.tokens,
+//            key_map:              old_state.key_map,
+//            hash_map:             old_state.hash_map,
+//            bank:                 old_state.bank,
+//        }
+//    }
 }
 }
 
 
 //  let result = await userAccount.functionCall({
 //  let result = await userAccount.functionCall({

+ 22 - 24
near/contracts/wormhole/src/lib.rs

@@ -601,30 +601,28 @@ impl Wormhole {
         true
         true
     }
     }
 
 
-    #[init(ignore_state)]
-    #[payable]
-    pub fn migrate() -> Self {
-        // call migrate on self
-        if env::attached_deposit() != 1 {
-            env::panic_str("Need money");
-        }
-        let old_state: OldWormhole = env::state_read().expect("failed");
-        if env::signer_account_pk() != old_state.owner_pk {
-            env::panic_str("CannotCallMigrate");
-        }
-        env::log_str(&format!("wormhole/{}#{}: migrate", file!(), line!(),));
-        Self {
-            guardians:             old_state.guardians,
-            dups:                  old_state.dups,
-            emitters:              old_state.emitters,
-            guardian_set_expirity: old_state.guardian_set_expirity,
-            guardian_set_index:    old_state.guardian_set_index,
-            owner_pk:              old_state.owner_pk,
-            upgrade_hash:          old_state.upgrade_hash,
-            message_fee:           old_state.message_fee,
-            bank:                  old_state.bank,
-        }
-    }
+//    #[init(ignore_state)]
+//    #[payable]
+//    #[private]
+//    pub fn migrate() -> Self {
+//        // call migrate on self
+//        if env::attached_deposit() != 1 {
+//            env::panic_str("Need money");
+//        }
+//        let old_state: OldWormhole = env::state_read().expect("failed");
+//        env::log_str(&format!("wormhole/{}#{}: migrate", file!(), line!(),));
+//        Self {
+//            guardians:             old_state.guardians,
+//            dups:                  old_state.dups,
+//            emitters:              old_state.emitters,
+//            guardian_set_expirity: old_state.guardian_set_expirity,
+//            guardian_set_index:    old_state.guardian_set_index,
+//            owner_pk:              old_state.owner_pk,
+//            upgrade_hash:          old_state.upgrade_hash,
+//            message_fee:           old_state.message_fee,
+//            bank:                  old_state.bank,
+//        }
+//    }
 }
 }
 
 
 //  let result = await userAccount.functionCall({
 //  let result = await userAccount.functionCall({

+ 144 - 0
near/mainnet_deploy.ts

@@ -0,0 +1,144 @@
+// npx pretty-quick
+
+const nearAPI = require("near-api-js");
+const BN = require("bn.js");
+const fs = require("fs");
+const fetch = require("node-fetch");
+import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
+
+const { parseSeedPhrase, generateSeedPhrase } = require("near-seed-phrase");
+
+function getConfig(env: any) {
+  switch (env) {
+    case "mainnet":
+      return {
+        networkId: "mainnet",
+        nodeUrl: "https://rpc.mainnet.near.org",
+        wormholeMasterAccount: "wormhole_crypto.near",
+        wormholeAccount: "contract.wormhole_crypto.near",
+        portalMasterAccount: "portalbridge.near",
+        portalAccount: "contract.portalbridge.near",
+      };
+  }
+  return {};
+}
+
+async function initNear() {
+  let config = getConfig("mainnet");
+
+  let wormholeKeys = parseSeedPhrase(process.env.WORMHOLE_KEYS);
+  let portalKeys = parseSeedPhrase(process.env.PORTAL_KEYS);
+
+  let wormholeMasterKey = nearAPI.utils.KeyPair.fromString(
+    wormholeKeys["secretKey"]
+  );
+  let portalMasterKey = nearAPI.utils.KeyPair.fromString(
+    portalKeys["secretKey"]
+  );
+
+  let keyStore = new nearAPI.keyStores.InMemoryKeyStore();
+  keyStore.setKey(
+    config.networkId,
+    config.wormholeMasterAccount,
+    wormholeMasterKey
+  );
+  keyStore.setKey(config.networkId, config.wormholeAccount, wormholeMasterKey);
+  keyStore.setKey(
+    config.networkId,
+    config.portalMasterAccount,
+    portalMasterKey
+  );
+  keyStore.setKey(config.networkId, config.portalAccount, portalMasterKey);
+
+  let near = await nearAPI.connect({
+    deps: {
+      keyStore,
+    },
+    networkId: config.networkId,
+    nodeUrl: config.nodeUrl,
+  });
+
+  let wormholeMasterAccount = new nearAPI.Account(
+    near.connection,
+    config.wormholeMasterAccount
+  );
+
+  let portalMasterAccount = new nearAPI.Account(
+    near.connection,
+    config.portalMasterAccount
+  );
+
+  console.log(
+    "wormhole account: " +
+      JSON.stringify(await wormholeMasterAccount.getAccountBalance())
+  );
+  console.log(
+    "portal account: " +
+      JSON.stringify(await portalMasterAccount.getAccountBalance())
+  );
+
+  const wormholeContract = await fs.readFileSync(
+    "contracts/wormhole/target/wasm32-unknown-unknown/release/near_wormhole.wasm"
+  );
+  const portalContract = await fs.readFileSync(
+    "contracts/token-bridge/target/wasm32-unknown-unknown/release/near_token_bridge.wasm"
+  );
+
+  console.log("setting key for new wormhole contract");
+
+  keyStore.setKey(config.networkId, config.wormholeAccount, wormholeMasterKey);
+  keyStore.setKey(config.networkId, config.portalAccount, portalMasterKey);
+
+  console.log("Deploying core/wormhole contract: " + config.wormholeAccount);
+
+  let wormholeAccount = await wormholeMasterAccount.createAndDeployContract(
+    config.wormholeAccount,
+    wormholeMasterKey.getPublicKey(),
+    wormholeContract,
+    new BN("5000000000000000000000000")
+  );
+
+  console.log("Deploying core/portal contract: " + config.portalAccount);
+
+  let portalAccount = await portalMasterAccount.createAndDeployContract(
+    config.portalAccount,
+    portalMasterKey.getPublicKey(),
+    portalContract,
+    new BN("12000000000000000000000000")
+  );
+
+  let result = await wormholeMasterAccount.functionCall({
+    contractId: config.wormholeAccount,
+    methodName: "boot_wormhole",
+    args: {
+      gset: 0,
+      addresses: ["58CC3AE5C097b213cE3c81979e1B9f9570746AA5"],
+    },
+    gas: 100000000000000,
+  });
+
+  result = await portalMasterAccount.functionCall({
+    contractId: config.portalAccount,
+    methodName: "boot_portal",
+    args: {
+      core: config.wormholeAccount,
+    },
+    gas: 100000000000000,
+  });
+
+  await wormholeMasterAccount.functionCall({
+    contractId: config.wormholeAccount,
+    methodName: "register_emitter",
+    args: { emitter: config.portalAccount },
+    attachedDeposit: new BN("30000000000000000000000"),
+    gas: new BN("100000000000000"),
+  });
+
+  console.log("deleting the master key from the token contract");
+  await portalAccount.deleteKey(portalMasterKey.getPublicKey());
+
+  console.log("deleting the master key from the wormhole contract");
+  await wormholeAccount.deleteKey(wormholeMasterKey.getPublicKey());
+}
+
+initNear();

+ 1 - 1
near/rust-toolchain.toml

@@ -1,4 +1,4 @@
 [toolchain]
 [toolchain]
-channel = "nightly"
+channel = "1.63"
 targets = [ "wasm32-unknown-unknown" ]
 targets = [ "wasm32-unknown-unknown" ]
 profile = "default"
 profile = "default"

+ 76 - 16
near/test/devnet_upgrade.ts

@@ -4,6 +4,14 @@ const bs58 = require("bs58");
 const fs = require("fs");
 const fs = require("fs");
 const fetch = require("node-fetch");
 const fetch = require("node-fetch");
 import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
 import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
+const { createHash } = require('crypto');
+import { TestLib } from "./testlib";
+
+
+import {
+  ChainId,
+  CHAIN_ID_NEAR,
+} from "@certusone/wormhole-sdk/lib/cjs/utils";
 
 
 function getConfig(env: any) {
 function getConfig(env: any) {
   switch (env) {
   switch (env) {
@@ -22,6 +30,10 @@ function getConfig(env: any) {
   return {};
   return {};
 }
 }
 
 
+function hash(string: any) {
+  return createHash('sha256').update(string).digest('hex');
+}
+
 async function testDeploy() {
 async function testDeploy() {
   let config = getConfig(process.env.NEAR_ENV || "sandbox");
   let config = getConfig(process.env.NEAR_ENV || "sandbox");
 
 
@@ -56,7 +68,6 @@ async function testDeploy() {
   );
   );
 
 
   let userKey = nearAPI.utils.KeyPair.fromRandom("ed25519");
   let userKey = nearAPI.utils.KeyPair.fromRandom("ed25519");
-  keyStore.setKey(config.networkId, config.userAccount, userKey);
 
 
   console.log(
   console.log(
     "creating a user account: " +
     "creating a user account: " +
@@ -70,43 +81,92 @@ async function testDeploy() {
     userKey.getPublicKey(),
     userKey.getPublicKey(),
     new BN(10).pow(new BN(27))
     new BN(10).pow(new BN(27))
   );
   );
+
+  // A whole new world...
+  keyStore = new nearAPI.keyStores.InMemoryKeyStore();
+  keyStore.setKey(config.networkId, config.userAccount, userKey);
+
+  near = await nearAPI.connect({
+    deps: {
+      keyStore,
+    },
+    networkId: config.networkId,
+    nodeUrl: config.nodeUrl,
+  });
+
   const userAccount = new nearAPI.Account(near.connection, config.userAccount);
   const userAccount = new nearAPI.Account(near.connection, config.userAccount);
 
 
   const wormholeContract = await fs.readFileSync(
   const wormholeContract = await fs.readFileSync(
     "../contracts/wormhole/target/wasm32-unknown-unknown/release/near_wormhole.wasm"
     "../contracts/wormhole/target/wasm32-unknown-unknown/release/near_wormhole.wasm"
   );
   );
 
 
-  //console.log("sending money to cover the cost of deploying this contract.. so that we fail for the right reasons");
-  //await userAccount.sendMoney(config.wormholeAccount, new BN("10000000000000000000000000"));
+  let h = hash(wormholeContract);
 
 
-  keyStore.setKey(config.networkId, config.wormholeAccount, masterKey);
+  let ts = new TestLib();
+  let seq = 1;
+  let vaa = ts.genCoreUpdate(ts.singleGuardianPrivKey, 0, 0, seq, CHAIN_ID_NEAR, h);
 
 
   let wormholeAccount = new nearAPI.Account(
   let wormholeAccount = new nearAPI.Account(
     near.connection,
     near.connection,
     config.wormholeAccount
     config.wormholeAccount
   );
   );
 
 
-  try {
-    console.log("redeploying wormhole contract using standard deployment API");
-    let resp = await wormholeAccount.deployContract(wormholeContract);
-    console.log(resp);
-    console.log("This should have thrown a exception..");
-    process.exit(1);
-  } catch {
-    console.log("Exception thrown.. nice.. we dont suck");
-  }
+  console.log("submitting vaa");
+  let result = await userAccount.functionCall({
+    contractId: config.wormholeAccount,
+    methodName: "submit_vaa",
+    args: { vaa: vaa },
+    attachedDeposit: "12500000000000000000000",
+    gas: new BN("150000000000000"),
+  });
 
 
   console.log("calling upgradeContract");
   console.log("calling upgradeContract");
-  let result = await userAccount.functionCall({
+
+  result = await userAccount.functionCall({
     contractId: config.wormholeAccount,
     contractId: config.wormholeAccount,
     methodName: "update_contract",
     methodName: "update_contract",
     args: wormholeContract,
     args: wormholeContract,
-    attachedDeposit: "12500000000000000000000",
+    attachedDeposit: "5279790000000000000000000",
     gas: 300000000000000,
     gas: 300000000000000,
   });
   });
   console.log("done");
   console.log("done");
 
 
-  console.log(result);
+  const tokenContract = await fs.readFileSync(
+    "../contracts/token-bridge/target/wasm32-unknown-unknown/release/near_token_bridge.wasm"
+  );
+
+  h = hash(tokenContract);
+  seq = seq + 1
+  vaa = ts.genTokenUpdate(ts.singleGuardianPrivKey, 0, 0, seq, CHAIN_ID_NEAR, h);
+
+  console.log("submitting vaa");
+  result = await userAccount.functionCall({
+    contractId: config.tokenAccount,
+    methodName: "submit_vaa",
+    args: { vaa: vaa },
+    attachedDeposit: "12500000000000000000000",
+    gas: new BN("150000000000000"),
+  });
+
+  console.log("submitting vaa again");
+  result = await userAccount.functionCall({
+    contractId: config.tokenAccount,
+    methodName: "submit_vaa",
+    args: { vaa: vaa },
+    attachedDeposit: "12500000000000000000000",
+    gas: new BN("150000000000000"),
+  });
+
+  console.log("calling upgradeContract on the token bridge");
+
+  result = await userAccount.functionCall({
+    contractId: config.tokenAccount,
+    methodName: "update_contract",
+    args: tokenContract,
+    attachedDeposit: "22797900000000000000000000",
+    gas: 300000000000000,
+  });
+
 }
 }
 
 
 testDeploy();
 testDeploy();

+ 81 - 0
near/test/testlib.ts

@@ -247,6 +247,87 @@ export class TestLib {
     );
     );
   }
   }
 
 
+  genCoreUpdate(
+    signers: any,
+    guardianSet: number,
+    nonce: number,
+    seq: number,
+    tchain: number,
+    hash: string
+  ) {
+    const b = [
+      "0x",
+      this.zeroBytes.slice(0, 28 * 2),
+      this.encoder("uint8", this.ord("C")),
+      this.encoder("uint8", this.ord("o")),
+      this.encoder("uint8", this.ord("r")),
+      this.encoder("uint8", this.ord("e")),
+      this.encoder("uint8", 1),
+      this.encoder("uint16", tchain),
+      hash
+    ];
+
+    let emitter = "0x" + this.zeroBytes.slice(0, 31 * 2) + "04";
+
+    var seconds = Math.floor(new Date().getTime() / 1000.0);
+
+    return this.createSignedVAA(
+      guardianSet,
+      signers,
+      seconds,
+      nonce,
+      1,
+      emitter,
+      seq,
+      32,
+      b.join("")
+    );
+  }
+
+  genTokenUpdate(
+    signers: any,
+    guardianSet: number,
+    nonce: number,
+    seq: number,
+    tchain: number,
+    hash: string
+  ) {
+    const b = [
+      "0x",
+      this.zeroBytes.slice(0, (32 - 11) * 2),
+      this.encoder("uint8", this.ord("T")),
+      this.encoder("uint8", this.ord("o")),
+      this.encoder("uint8", this.ord("k")),
+      this.encoder("uint8", this.ord("e")),
+      this.encoder("uint8", this.ord("n")),
+      this.encoder("uint8", this.ord("B")),
+      this.encoder("uint8", this.ord("r")),
+      this.encoder("uint8", this.ord("i")),
+      this.encoder("uint8", this.ord("d")),
+      this.encoder("uint8", this.ord("g")),
+      this.encoder("uint8", this.ord("e")),
+      this.encoder("uint8", 2),
+      this.encoder("uint16", tchain),
+      hash
+    ];
+
+    let emitter = "0x" + this.zeroBytes.slice(0, 31 * 2) + "04";
+
+    var seconds = Math.floor(new Date().getTime() / 1000.0);
+
+    return this.createSignedVAA(
+      guardianSet,
+      signers,
+      seconds,
+      nonce,
+      1,
+      emitter,
+      seq,
+      32,
+      b.join("")
+    );
+  }
+
   getTokenEmitter(chain: number): string {
   getTokenEmitter(chain: number): string {
     if (chain === CHAIN_ID_SOLANA) {
     if (chain === CHAIN_ID_SOLANA) {
       return "c69a1b1a65dd336bf1df6a77afb501fc25db7fc0938cb08595a9ef473265cb4f";
       return "c69a1b1a65dd336bf1df6a77afb501fc25db7fc0938cb08595a9ef473265cb4f";