Quellcode durchsuchen

near/audit: audit feedback

Josh Siegel vor 3 Jahren
Ursprung
Commit
a752e1309c

+ 6 - 0
near/Docker.md

@@ -0,0 +1,6 @@
+# first build the image
+DOCKER_BUILDKIT=1 docker build -f Dockerfile.base -t near .
+# tag the image with the appropriate version
+docker tag near:latest ghcr.io/certusone/near:0.1
+# push to ghcr
+docker push ghcr.io/certusone/near:0.1

+ 1 - 23
near/Dockerfile

@@ -1,23 +1 @@
-FROM rust:1.60@sha256:48d3b5baf199dc7c378e775c47b0c40aaf7d8b23eaf67e15b095bbdaaecd1f10 as near-node
-
-# Support additional root CAs
-COPY README.md cert.pem* /certs/
-# Debian
-RUN if [ -e /certs/cert.pem ]; then cp /certs/cert.pem /etc/ssl/certs/ca-certificates.crt; fi
-# git
-RUN if [ -e /certs/cert.pem ]; then git config --global http.sslCAInfo /certs/cert.pem; fi
-
-RUN rustup update
-RUN apt-get install python3 --no-install-recommends
-
-COPY node_builder.sh /tmp
-
-WORKDIR /tmp
-
-RUN ./node_builder.sh
-
-COPY start_node.sh /tmp
-
-RUN rm -rf /tmp/_sandbox
-RUN mkdir -p /tmp/sandbox
-RUN nearcore/target/release/near-sandbox --home /tmp/_sandbox init
+FROM ghcr.io/certusone/near:0.1@sha256:b04512f6b2cab77615cd6d5177bb4c671fd704eb92b961e8d81a341599a5d47c as near-node

+ 16 - 0
near/Dockerfile.base

@@ -0,0 +1,16 @@
+FROM rust:1.60@sha256:48d3b5baf199dc7c378e775c47b0c40aaf7d8b23eaf67e15b095bbdaaecd1f10 as near-node
+
+RUN rustup update
+RUN apt-get install python3 --no-install-recommends
+
+COPY node_builder.sh /tmp
+
+WORKDIR /tmp
+
+RUN ./node_builder.sh
+
+COPY start_node.sh /tmp
+
+RUN rm -rf /tmp/_sandbox
+RUN mkdir -p /tmp/sandbox
+RUN nearcore/target/release/near-sandbox --home /tmp/_sandbox init

+ 8 - 0
near/contracts/ft/src/lib.rs

@@ -14,6 +14,7 @@ use {
         },
         },
         collections::LazyOption,
         collections::LazyOption,
         env,
         env,
+        utils::assert_one_yocto,
         json_types::U128,
         json_types::U128,
         near_bindgen,
         near_bindgen,
         AccountId,
         AccountId,
@@ -104,6 +105,7 @@ impl FTContract {
         self.meta.replace(&meta);
         self.meta.replace(&meta);
     }
     }
 
 
+    #[payable]
     pub fn vaa_withdraw(
     pub fn vaa_withdraw(
         &mut self,
         &mut self,
         from: AccountId,
         from: AccountId,
@@ -113,6 +115,8 @@ impl FTContract {
         fee: u128,
         fee: u128,
         payload: String,
         payload: String,
     ) -> String {
     ) -> String {
+        assert_one_yocto();
+
         if env::predecessor_account_id() != self.controller {
         if env::predecessor_account_id() != self.controller {
             env::panic_str("CrossContractInvalidCaller");
             env::panic_str("CrossContractInvalidCaller");
         }
         }
@@ -190,6 +194,10 @@ impl FTContract {
 
 
         let mut deposit: Balance = env::attached_deposit();
         let mut deposit: Balance = env::attached_deposit();
 
 
+        if deposit == 0 {
+            env::panic_str("ZeroDepositNotAllowed");
+        }
+
         if !self.token.accounts.contains_key(&account_id) {
         if !self.token.accounts.contains_key(&account_id) {
             let min_balance = self.storage_balance_bounds().min.0;
             let min_balance = self.storage_balance_bounds().min.0;
             if deposit < min_balance {
             if deposit < min_balance {

+ 7 - 15
near/contracts/nft-bridge/src/lib.rs

@@ -806,10 +806,6 @@ impl NFTBridge {
 
 
     #[private]
     #[private]
     fn update_contract_work(&mut self, v: Vec<u8>) -> Promise {
     fn update_contract_work(&mut self, v: Vec<u8>) -> Promise {
-        if env::attached_deposit() == 0 {
-            env::panic_str("attach some cash");
-        }
-
         let s = env::sha256(&v);
         let s = env::sha256(&v);
 
 
         env::log_str(&format!(
         env::log_str(&format!(
@@ -820,20 +816,16 @@ impl NFTBridge {
         ));
         ));
 
 
         if s.to_vec() != self.upgrade_hash {
         if s.to_vec() != self.upgrade_hash {
-            if env::attached_deposit() > 0 {
-                env::log_str(&format!(
-                    "nft-bridge/{}#{}: refunding {} to {}",
-                    file!(),
-                    line!(),
-                    env::attached_deposit(),
-                    env::predecessor_account_id()
-                ));
-
-                Promise::new(env::predecessor_account_id()).transfer(env::attached_deposit());
-            }
             env::panic_str("invalidUpgradeContract");
             env::panic_str("invalidUpgradeContract");
         }
         }
 
 
+        let storage_cost = ((v.len() + 32) as Balance) * env::storage_byte_cost();
+        assert!(
+            env::attached_deposit() >= storage_cost,
+            "DepositUnderFlow:{}",
+            storage_cost
+        );
+
         Promise::new(env::current_account_id())
         Promise::new(env::current_account_id())
             .deploy_contract(v.to_vec())
             .deploy_contract(v.to_vec())
             .then(Self::ext(env::current_account_id()).update_contract_done(
             .then(Self::ext(env::current_account_id()).update_contract_done(

Datei-Diff unterdrückt, da er zu groß ist
+ 472 - 337
near/contracts/token-bridge/src/lib.rs


+ 231 - 200
near/contracts/wormhole/src/lib.rs

@@ -127,202 +127,229 @@ impl Default for Wormhole {
     }
     }
 }
 }
 
 
-// Nothing is mutable...
-fn parse_and_verify_vaa(storage: &Wormhole, data: &[u8]) -> state::ParsedVAA {
-    let vaa = state::ParsedVAA::parse(data);
-    if vaa.version != 1 {
-        env::panic_str("InvalidVersion");
-    }
-    let guardian_set = storage
-        .guardians
-        .get(&vaa.guardian_set_index)
-        .expect("InvalidGuardianSetIndex");
+impl Wormhole {
+    fn parse_and_verify_vaa(self: &Wormhole, data: &[u8]) -> state::ParsedVAA {
+        let vaa = state::ParsedVAA::parse(data);
+        if vaa.version != 1 {
+            env::panic_str("InvalidVersion");
+        }
+        let guardian_set = self
+            .guardians
+            .get(&vaa.guardian_set_index)
+            .expect("InvalidGuardianSetIndex");
 
 
-    if guardian_set.expiration_time != 0 && guardian_set.expiration_time < env::block_timestamp() {
-        env::panic_str("GuardianSetExpired");
-    }
+        if guardian_set.expiration_time != 0
+            && guardian_set.expiration_time < env::block_timestamp()
+        {
+            env::panic_str("GuardianSetExpired");
+        }
 
 
-    if (vaa.len_signers as usize) < guardian_set.quorum() {
-        env::panic_str("ContractError");
-    }
+        if (vaa.len_signers as usize) < guardian_set.quorum() {
+            env::panic_str("ContractError");
+        }
 
 
-    // Lets calculate the digest that we are comparing against
-    let mut pos =
-        state::ParsedVAA::HEADER_LEN + (vaa.len_signers * state::ParsedVAA::SIGNATURE_LEN); //  SIGNATURE_LEN: usize = 66;
-    let p1 = env::keccak256(&data[pos..]);
-    let digest = env::keccak256(&p1);
+        // Lets calculate the digest that we are comparing against
+        let mut pos =
+            state::ParsedVAA::HEADER_LEN + (vaa.len_signers * state::ParsedVAA::SIGNATURE_LEN); //  SIGNATURE_LEN: usize = 66;
+        let p1 = env::keccak256(&data[pos..]);
+        let digest = env::keccak256(&p1);
+
+        // Verify guardian signatures
+        let mut last_index: i32 = -1;
+        pos = state::ParsedVAA::HEADER_LEN; // HEADER_LEN: usize = 6;
+
+        for _ in 0..vaa.len_signers {
+            // which guardian signature is this?
+            let index = data.get_u8(pos) as i32;
+
+            // We can't go backwards or use the same guardian over again
+            if index <= last_index {
+                env::panic_str("WrongGuardianIndexOrder");
+            }
+            last_index = index;
+
+            pos += 1; // walk forward
+
+            // Grab the whole signature
+            let signature = &data[(pos)..(pos + state::ParsedVAA::SIG_DATA_LEN)]; // SIG_DATA_LEN: usize = 64;
+            let key = guardian_set.addresses.get(index as usize).unwrap();
+
+            pos += state::ParsedVAA::SIG_DATA_LEN; // SIG_DATA_LEN: usize = 64;
+            let recovery = data.get_u8(pos);
+
+            let v = env::ecrecover(&digest, signature, recovery, true).expect("cannot recover key");
+            let k = &env::keccak256(&v)[12..32];
+            if k != key.bytes {
+                env::log_str(&format!(
+                    "wormhole/{}#{}: signature_error: {} != {}",
+                    file!(),
+                    line!(),
+                    hex::encode(&k),
+                    hex::encode(&key.bytes),
+                ));
+
+                env::panic_str("GuardianSignatureError");
+            }
+            pos += 1;
+        }
 
 
-    // Verify guardian signatures
-    let mut last_index: i32 = -1;
-    pos = state::ParsedVAA::HEADER_LEN; // HEADER_LEN: usize = 6;
+        vaa
+    }
 
 
-    for _ in 0..vaa.len_signers {
-        // which guardian signature is this?
-        let index = data.get_u8(pos) as i32;
+    fn vaa_update_contract(
+        self: &mut Wormhole,
+        _vaa: &state::ParsedVAA,
+        data: &[u8],
+        deposit: Balance,
+        refund_to: AccountId,
+    ) -> PromiseOrValue<bool> {
+        let uh = data.get_bytes32(0);
+        env::log_str(&format!(
+            "wormhole/{}#{}: vaa_update_contract: {}",
+            file!(),
+            line!(),
+            hex::encode(&uh)
+        ));
+        self.upgrade_hash = uh.to_vec();
 
 
-        // We can't go backwards or use the same guardian over again
-        if index <= last_index {
-            env::panic_str("WrongGuardianIndexOrder");
+        if deposit > 0 {
+            PromiseOrValue::Promise(Promise::new(refund_to).transfer(deposit))
+        } else {
+            PromiseOrValue::Value(true)
         }
         }
-        last_index = index;
-
-        pos += 1; // walk forward
+    }
 
 
-        // Grab the whole signature
-        let signature = &data[(pos)..(pos + state::ParsedVAA::SIG_DATA_LEN)]; // SIG_DATA_LEN: usize = 64;
-        let key = guardian_set.addresses.get(index as usize).unwrap();
+    fn vaa_update_guardian_set(
+        self: &mut Wormhole,
+        _vaa: &state::ParsedVAA,
+        data: &[u8],
+        mut deposit: Balance,
+        refund_to: AccountId,
+    ) -> PromiseOrValue<bool> {
+        const ADDRESS_LEN: usize = 20;
+        let new_guardian_set_index = data.get_u32(0);
+
+        if self.guardian_set_index + 1 != new_guardian_set_index {
+            env::panic_str("InvalidGovernanceSetIndex");
+        }
 
 
-        pos += state::ParsedVAA::SIG_DATA_LEN; // SIG_DATA_LEN: usize = 64;
-        let recovery = data.get_u8(pos);
+        let n_guardians = data.get_u8(4);
 
 
-        let v = env::ecrecover(&digest, signature, recovery, true).expect("cannot recover key");
-        let k = &env::keccak256(&v)[12..32];
-        if k != key.bytes {
-            env::log_str(&format!(
-                "wormhole/{}#{}: signature_error: {} != {}",
-                file!(),
-                line!(),
-                hex::encode(&k),
-                hex::encode(&key.bytes),
-            ));
+        let mut addresses = vec![];
 
 
-            env::panic_str("GuardianSignatureError");
+        for i in 0..n_guardians {
+            let pos = 5 + (i as usize) * ADDRESS_LEN;
+            addresses.push(GuardianAddress {
+                bytes: data[pos..pos + ADDRESS_LEN].to_vec(),
+            });
         }
         }
-        pos += 1;
-    }
 
 
-    vaa
-}
+        let guardian_set = &mut self
+            .guardians
+            .get(&self.guardian_set_index)
+            .expect("InvalidPreviousGuardianSetIndex");
 
 
-fn vaa_update_contract(
-    storage: &mut Wormhole,
-    _vaa: &state::ParsedVAA,
-    data: &[u8],
-    deposit: Balance,
-    refund_to: AccountId,
-) -> PromiseOrValue<bool> {
-    let chain = data.get_u16(33);
-    if chain != CHAIN_ID_NEAR {
-        env::panic_str("InvalidContractUpgradeChain");
-    }
+        guardian_set.expiration_time = env::block_timestamp() + self.guardian_set_expirity;
 
 
-    let uh = data.get_bytes32(0);
-    env::log_str(&format!(
-        "wormhole/{}#{}: vaa_update_contract: {}",
-        file!(),
-        line!(),
-        hex::encode(&uh)
-    ));
-    storage.upgrade_hash = uh.to_vec();
-
-    if deposit > 0 {
-        PromiseOrValue::Promise(Promise::new(refund_to).transfer(deposit))
-    } else {
-        PromiseOrValue::Value(true)
-    }
-}
+        self.guardians
+            .insert(&self.guardian_set_index, guardian_set);
 
 
-fn vaa_update_guardian_set(
-    storage: &mut Wormhole,
-    _vaa: &state::ParsedVAA,
-    data: &[u8],
-    mut deposit: Balance,
-    refund_to: AccountId,
-) -> PromiseOrValue<bool> {
-    const ADDRESS_LEN: usize = 20;
-    let new_guardian_set_index = data.get_u32(0);
-
-    if storage.guardian_set_index + 1 != new_guardian_set_index {
-        env::panic_str("InvalidGovernanceSetIndex");
-    }
+        let g = GuardianSetInfo {
+            addresses,
+            expiration_time: 0,
+        };
 
 
-    let n_guardians = data.get_u8(4);
+        let storage_used = env::storage_usage();
 
 
-    let mut addresses = vec![];
+        self.guardians.insert(&new_guardian_set_index, &g);
+        self.guardian_set_index = new_guardian_set_index;
 
 
-    for i in 0..n_guardians {
-        let pos = 5 + (i as usize) * ADDRESS_LEN;
-        addresses.push(GuardianAddress {
-            bytes: data[pos..pos + ADDRESS_LEN].to_vec(),
-        });
-    }
+        let required_cost =
+            (Balance::from(env::storage_usage() - storage_used)) * env::storage_byte_cost();
 
 
-    let guardian_set = &mut storage
-        .guardians
-        .get(&storage.guardian_set_index)
-        .expect("InvalidPreviousGuardianSetIndex");
+        if required_cost > deposit {
+            env::panic_str("DepositUnderflowForGuardianSet");
+        }
+        deposit -= required_cost;
 
 
-    guardian_set.expiration_time = env::block_timestamp() + storage.guardian_set_expirity;
+        if deposit > 0 {
+            PromiseOrValue::Promise(Promise::new(refund_to).transfer(deposit))
+        } else {
+            PromiseOrValue::Value(true)
+        }
+    }
 
 
-    storage
-        .guardians
-        .insert(&storage.guardian_set_index, guardian_set);
+    fn handle_set_fee(
+        self: &mut Wormhole,
+        _vaa: &state::ParsedVAA,
+        payload: &[u8],
+        deposit: Balance,
+        refund_to: AccountId,
+    ) -> PromiseOrValue<bool> {
+        let (_, amount) = payload.get_u256(0);
 
 
-    let g = GuardianSetInfo {
-        addresses,
-        expiration_time: 0,
-    };
+        self.message_fee = amount as u128;
 
 
-    let storage_used = env::storage_usage();
+        if deposit > 0 {
+            PromiseOrValue::Promise(Promise::new(refund_to).transfer(deposit))
+        } else {
+            PromiseOrValue::Value(true)
+        }
+    }
 
 
-    storage.guardians.insert(&new_guardian_set_index, &g);
-    storage.guardian_set_index = new_guardian_set_index;
+    fn handle_transfer_fee(
+        self: &mut Wormhole,
+        _vaa: &state::ParsedVAA,
+        payload: &[u8],
+        deposit: Balance,
+    ) -> PromiseOrValue<bool> {
+        let (_, amount) = payload.get_u256(0);
+        let destination = payload.get_bytes32(32).to_vec();
+
+        if amount > self.bank {
+            env::panic_str("bankUnderFlow");
+        }
 
 
-    let required_cost =
-        (Balance::from(env::storage_usage() - storage_used)) * env::storage_byte_cost();
+        // We only support addresses 32 bytes or shorter...  No, we don't
+        // support hash addresses in this governance message
+        let d = AccountId::new_unchecked(get_string_from_32(&destination));
 
 
-    if required_cost > deposit {
-        env::panic_str("DepositUnderflowForGuardianSet");
+        if (deposit + amount) > 0 {
+            self.bank -= amount;
+            PromiseOrValue::Promise(Promise::new(d).transfer(deposit + amount))
+        } else {
+            PromiseOrValue::Value(true)
+        }
     }
     }
-    deposit -= required_cost;
 
 
-    if deposit > 0 {
-        PromiseOrValue::Promise(Promise::new(refund_to).transfer(deposit))
-    } else {
-        PromiseOrValue::Value(true)
-    }
-}
+    fn update_contract_work(&mut self, v: Vec<u8>) -> Promise {
+        let s = env::sha256(&v);
 
 
-fn handle_set_fee(
-    storage: &mut Wormhole,
-    _vaa: &state::ParsedVAA,
-    payload: &[u8],
-    deposit: Balance,
-    refund_to: AccountId,
-) -> PromiseOrValue<bool> {
-    let (_, amount) = payload.get_u256(0);
-
-    storage.message_fee = amount as u128;
-
-    if deposit > 0 {
-        PromiseOrValue::Promise(Promise::new(refund_to).transfer(deposit))
-    } else {
-        PromiseOrValue::Value(true)
-    }
-}
+        env::log_str(&format!(
+            "wormhole/{}#{}: update_contract: {}",
+            file!(),
+            line!(),
+            hex::encode(&s)
+        ));
 
 
-fn handle_transfer_fee(
-    storage: &mut Wormhole,
-    _vaa: &state::ParsedVAA,
-    payload: &[u8],
-    deposit: Balance,
-) -> PromiseOrValue<bool> {
-    let (_, amount) = payload.get_u256(0);
-    let destination = payload.get_bytes32(32).to_vec();
-
-    if amount > storage.bank {
-        env::panic_str("bankUnderFlow");
-    }
+        if s.to_vec() != self.upgrade_hash {
+            env::panic_str("invalidUpgradeContract");
+        }
 
 
-    // We only support addresses 32 bytes or shorter...  No, we don't
-    // support hash addresses in this governance message
-    let d = AccountId::new_unchecked(get_string_from_32(&destination));
+        let storage_cost = ((v.len() + 32) as Balance) * env::storage_byte_cost();
+        assert!(
+            env::attached_deposit() >= storage_cost,
+            "DepositUnderFlow:{}",
+            storage_cost
+        );
 
 
-    if (deposit + amount) > 0 {
-        storage.bank -= amount;
-        PromiseOrValue::Promise(Promise::new(d).transfer(deposit + amount))
-    } else {
-        PromiseOrValue::Value(true)
+        Promise::new(env::current_account_id())
+            .deploy_contract(v.to_vec())
+            .then(Self::ext(env::current_account_id()).update_contract_done(
+                env::predecessor_account_id(),
+                env::storage_usage(),
+                env::attached_deposit(),
+            ))
     }
     }
 }
 }
 
 
@@ -334,7 +361,7 @@ impl Wormhole {
     pub fn verify_vaa(&self, vaa: String) -> u32 {
     pub fn verify_vaa(&self, vaa: String) -> u32 {
         let g1 = env::used_gas();
         let g1 = env::used_gas();
         let h = hex::decode(vaa).expect("invalidVaa");
         let h = hex::decode(vaa).expect("invalidVaa");
-        parse_and_verify_vaa(self, &h);
+        self.parse_and_verify_vaa(&h);
         let g2 = env::used_gas();
         let g2 = env::used_gas();
 
 
         env::log_str(&format!(
         env::log_str(&format!(
@@ -349,12 +376,16 @@ impl Wormhole {
 
 
     #[payable]
     #[payable]
     pub fn register_emitter(&mut self, emitter: String) -> PromiseOrValue<bool> {
     pub fn register_emitter(&mut self, emitter: String) -> PromiseOrValue<bool> {
+        if self.emitters.contains_key(&emitter) {
+            env::panic_str("AlreadyRegistered");
+        }
+
         let storage_used = env::storage_usage();
         let storage_used = env::storage_usage();
 
 
         self.emitters.insert(&emitter, &1);
         self.emitters.insert(&emitter, &1);
 
 
         if env::storage_usage() < storage_used {
         if env::storage_usage() < storage_used {
-            env::panic_str("ImpossibleStorage");
+            env::panic_str("ImpossibleSelf");
         }
         }
 
 
         let required_cost =
         let required_cost =
@@ -375,6 +406,16 @@ impl Wormhole {
 
 
     #[payable]
     #[payable]
     pub fn publish_message(&mut self, data: String, nonce: u32) -> u64 {
     pub fn publish_message(&mut self, data: String, nonce: u32) -> u64 {
+        env::log_str(&format!(
+            "wormhole/{}#{}: publish_message  prepaid_gas: {}   used_gas: {}  delta: {}",
+            file!(),
+            line!(),
+            serde_json::to_string(&env::prepaid_gas()).unwrap(),
+            serde_json::to_string(&env::used_gas()).unwrap(),
+            serde_json::to_string(&(env::prepaid_gas() - env::used_gas())).unwrap()
+        ));
+
+
         require!(
         require!(
             env::prepaid_gas() >= Gas(10_000_000_000_000),
             env::prepaid_gas() >= Gas(10_000_000_000_000),
             &format!(
             &format!(
@@ -416,19 +457,28 @@ impl Wormhole {
 
 
     #[payable]
     #[payable]
     pub fn submit_vaa(&mut self, vaa: String) -> PromiseOrValue<bool> {
     pub fn submit_vaa(&mut self, vaa: String) -> PromiseOrValue<bool> {
+        env::log_str(&format!(
+            "wormhole/{}#{}: submit_vaa   prepaid_gas: {}   used_gas: {}  delta: {}",
+            file!(),
+            line!(),
+            serde_json::to_string(&env::prepaid_gas()).unwrap(),
+            serde_json::to_string(&env::used_gas()).unwrap(),
+            serde_json::to_string(&(env::prepaid_gas() - env::used_gas())).unwrap()
+        ));
+
         let refund_to = env::predecessor_account_id();
         let refund_to = env::predecessor_account_id();
         let mut deposit = env::attached_deposit();
         let mut deposit = env::attached_deposit();
 
 
         if env::attached_deposit() == 0 {
         if env::attached_deposit() == 0 {
-            env::panic_str("PayForStorage");
+            env::panic_str("PayForSelf");
         }
         }
 
 
-        if env::prepaid_gas() < Gas(150_000_000_000_000) {
+        if (env::prepaid_gas() - env::used_gas()) < Gas(140_000_000_000_000) {
             env::panic_str("NotEnoughGas");
             env::panic_str("NotEnoughGas");
         }
         }
 
 
         let h = hex::decode(vaa).expect("invalidVaa");
         let h = hex::decode(vaa).expect("invalidVaa");
-        let vaa = parse_and_verify_vaa(self, &h);
+        let vaa = self.parse_and_verify_vaa(&h);
 
 
         // Check if VAA with this hash was already accepted
         // Check if VAA with this hash was already accepted
         if self.dups.contains(&vaa.hash) {
         if self.dups.contains(&vaa.hash) {
@@ -477,11 +527,20 @@ impl Wormhole {
 
 
         let payload = &data[35..];
         let payload = &data[35..];
 
 
+        env::log_str(&format!(
+            "wormhole/{}#{}: submit_vaa   prepaid_gas: {}   used_gas: {}  delta: {}",
+            file!(),
+            line!(),
+            serde_json::to_string(&env::prepaid_gas()).unwrap(),
+            serde_json::to_string(&env::used_gas()).unwrap(),
+            serde_json::to_string(&(env::prepaid_gas() - env::used_gas())).unwrap()
+        ));
+
         match action {
         match action {
-            1u8 => vaa_update_contract(self, &vaa, payload, deposit, refund_to),
-            2u8 => vaa_update_guardian_set(self, &vaa, payload, deposit, refund_to),
-            3u8 => handle_set_fee(self, &vaa, payload, deposit, refund_to),
-            4u8 => handle_transfer_fee(self, &vaa, payload, deposit),
+            1u8 => self.vaa_update_contract(&vaa, payload, deposit, refund_to),
+            2u8 => self.vaa_update_guardian_set(&vaa, payload, deposit, refund_to),
+            3u8 => self.handle_set_fee(&vaa, payload, deposit, refund_to),
+            4u8 => self.handle_transfer_fee(&vaa, payload, deposit),
             _ => env::panic_str("InvalidGovernanceAction"),
             _ => env::panic_str("InvalidGovernanceAction"),
         }
         }
     }
     }
@@ -535,34 +594,6 @@ impl Wormhole {
         }
         }
     }
     }
 
 
-    #[private]
-    fn update_contract_work(&mut self, v: Vec<u8>) -> Promise {
-        if env::attached_deposit() == 0 {
-            env::panic_str("attach some cash");
-        }
-
-        let s = env::sha256(&v);
-
-        env::log_str(&format!(
-            "wormhole/{}#{}: update_contract: {}",
-            file!(),
-            line!(),
-            hex::encode(&s)
-        ));
-
-        if s.to_vec() != self.upgrade_hash {
-            env::panic_str("invalidUpgradeContract");
-        }
-
-        Promise::new(env::current_account_id())
-            .deploy_contract(v.to_vec())
-            .then(Self::ext(env::current_account_id()).update_contract_done(
-                env::predecessor_account_id(),
-                env::storage_usage(),
-                env::attached_deposit(),
-            ))
-    }
-
     #[payable]
     #[payable]
     pub fn pass(&mut self) -> bool {
     pub fn pass(&mut self) -> bool {
         env::log_str(&format!("wormhole::pass {} {}", file!(), line!()));
         env::log_str(&format!("wormhole::pass {} {}", file!(), line!()));

+ 132 - 3
near/package-lock.json

@@ -18,6 +18,7 @@
         "env-cmd": "^10.1.0",
         "env-cmd": "^10.1.0",
         "ethers": "^5.6.5",
         "ethers": "^5.6.5",
         "near-api-js": "^0.43.1",
         "near-api-js": "^0.43.1",
+        "near-seed-phrase": "^0.2.0",
         "prop-types": "^15.7.2",
         "prop-types": "^15.7.2",
         "react": "^17.0.2",
         "react": "^17.0.2",
         "react-dom": "^17.0.2",
         "react-dom": "^17.0.2",
@@ -40,10 +41,10 @@
     },
     },
     "../sdk/js": {
     "../sdk/js": {
       "name": "@certusone/wormhole-sdk",
       "name": "@certusone/wormhole-sdk",
-      "version": "0.5.1",
+      "version": "0.6.2",
       "license": "Apache-2.0",
       "license": "Apache-2.0",
       "dependencies": {
       "dependencies": {
-        "@certusone/wormhole-sdk-proto-web": "^0.0.1",
+        "@certusone/wormhole-sdk-proto-web": "^0.0.3",
         "@certusone/wormhole-sdk-wasm": "^0.0.1",
         "@certusone/wormhole-sdk-wasm": "^0.0.1",
         "@solana/spl-token": "^0.1.8",
         "@solana/spl-token": "^0.1.8",
         "@solana/web3.js": "^1.24.0",
         "@solana/web3.js": "^1.24.0",
@@ -3286,6 +3287,31 @@
         "file-uri-to-path": "1.0.0"
         "file-uri-to-path": "1.0.0"
       }
       }
     },
     },
+    "node_modules/bip39": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.0.2.tgz",
+      "integrity": "sha512-J4E1r2N0tUylTKt07ibXvhpT2c5pyAFgvuA5q1H9uDy6dEGpjV8jmymh3MTYJDLCNbIVClSB9FbND49I6N24MQ==",
+      "dependencies": {
+        "@types/node": "11.11.6",
+        "create-hash": "^1.1.0",
+        "pbkdf2": "^3.0.9",
+        "randombytes": "^2.0.1"
+      }
+    },
+    "node_modules/bip39-light": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/bip39-light/-/bip39-light-1.0.7.tgz",
+      "integrity": "sha512-WDTmLRQUsiioBdTs9BmSEmkJza+8xfJmptsNJjxnoq3EydSa/ZBXT6rm66KoT3PJIRYMnhSKNR7S9YL1l7R40Q==",
+      "dependencies": {
+        "create-hash": "^1.1.0",
+        "pbkdf2": "^3.0.9"
+      }
+    },
+    "node_modules/bip39/node_modules/@types/node": {
+      "version": "11.11.6",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.6.tgz",
+      "integrity": "sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ=="
+    },
     "node_modules/blakejs": {
     "node_modules/blakejs": {
       "version": "1.2.1",
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz",
       "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz",
@@ -9086,6 +9112,43 @@
         "base-x": "^3.0.2"
         "base-x": "^3.0.2"
       }
       }
     },
     },
+    "node_modules/near-hd-key": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/near-hd-key/-/near-hd-key-1.2.1.tgz",
+      "integrity": "sha512-SIrthcL5Wc0sps+2e1xGj3zceEa68TgNZDLuCx0daxmfTP7sFTB3/mtE2pYhlFsCxWoMn+JfID5E1NlzvvbRJg==",
+      "dependencies": {
+        "bip39": "3.0.2",
+        "create-hmac": "1.1.7",
+        "tweetnacl": "1.0.3"
+      }
+    },
+    "node_modules/near-seed-phrase": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/near-seed-phrase/-/near-seed-phrase-0.2.0.tgz",
+      "integrity": "sha512-NpmrnejpY1AdlRpDZ0schJQJtfBaoUheRfiYtQpcq9TkwPgqKZCRULV5L3hHmLc0ep7KRtikbPQ9R2ztN/3cyQ==",
+      "dependencies": {
+        "bip39-light": "^1.0.7",
+        "bs58": "^4.0.1",
+        "near-hd-key": "^1.2.1",
+        "tweetnacl": "^1.0.2"
+      }
+    },
+    "node_modules/near-seed-phrase/node_modules/base-x": {
+      "version": "3.0.9",
+      "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz",
+      "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==",
+      "dependencies": {
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "node_modules/near-seed-phrase/node_modules/bs58": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz",
+      "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==",
+      "dependencies": {
+        "base-x": "^3.0.2"
+      }
+    },
     "node_modules/negotiator": {
     "node_modules/negotiator": {
       "version": "0.6.3",
       "version": "0.6.3",
       "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
       "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
@@ -16419,7 +16482,7 @@
     "@certusone/wormhole-sdk": {
     "@certusone/wormhole-sdk": {
       "version": "file:../sdk/js",
       "version": "file:../sdk/js",
       "requires": {
       "requires": {
-        "@certusone/wormhole-sdk-proto-web": "^0.0.1",
+        "@certusone/wormhole-sdk-proto-web": "^0.0.3",
         "@certusone/wormhole-sdk-wasm": "^0.0.1",
         "@certusone/wormhole-sdk-wasm": "^0.0.1",
         "@improbable-eng/grpc-web-node-http-transport": "^0.15.0",
         "@improbable-eng/grpc-web-node-http-transport": "^0.15.0",
         "@openzeppelin/contracts": "^4.2.0",
         "@openzeppelin/contracts": "^4.2.0",
@@ -17510,6 +17573,33 @@
         "file-uri-to-path": "1.0.0"
         "file-uri-to-path": "1.0.0"
       }
       }
     },
     },
+    "bip39": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.0.2.tgz",
+      "integrity": "sha512-J4E1r2N0tUylTKt07ibXvhpT2c5pyAFgvuA5q1H9uDy6dEGpjV8jmymh3MTYJDLCNbIVClSB9FbND49I6N24MQ==",
+      "requires": {
+        "@types/node": "11.11.6",
+        "create-hash": "^1.1.0",
+        "pbkdf2": "^3.0.9",
+        "randombytes": "^2.0.1"
+      },
+      "dependencies": {
+        "@types/node": {
+          "version": "11.11.6",
+          "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.6.tgz",
+          "integrity": "sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ=="
+        }
+      }
+    },
+    "bip39-light": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/bip39-light/-/bip39-light-1.0.7.tgz",
+      "integrity": "sha512-WDTmLRQUsiioBdTs9BmSEmkJza+8xfJmptsNJjxnoq3EydSa/ZBXT6rm66KoT3PJIRYMnhSKNR7S9YL1l7R40Q==",
+      "requires": {
+        "create-hash": "^1.1.0",
+        "pbkdf2": "^3.0.9"
+      }
+    },
     "blakejs": {
     "blakejs": {
       "version": "1.2.1",
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz",
       "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz",
@@ -22192,6 +22282,45 @@
         }
         }
       }
       }
     },
     },
+    "near-hd-key": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/near-hd-key/-/near-hd-key-1.2.1.tgz",
+      "integrity": "sha512-SIrthcL5Wc0sps+2e1xGj3zceEa68TgNZDLuCx0daxmfTP7sFTB3/mtE2pYhlFsCxWoMn+JfID5E1NlzvvbRJg==",
+      "requires": {
+        "bip39": "3.0.2",
+        "create-hmac": "1.1.7",
+        "tweetnacl": "1.0.3"
+      }
+    },
+    "near-seed-phrase": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/near-seed-phrase/-/near-seed-phrase-0.2.0.tgz",
+      "integrity": "sha512-NpmrnejpY1AdlRpDZ0schJQJtfBaoUheRfiYtQpcq9TkwPgqKZCRULV5L3hHmLc0ep7KRtikbPQ9R2ztN/3cyQ==",
+      "requires": {
+        "bip39-light": "^1.0.7",
+        "bs58": "^4.0.1",
+        "near-hd-key": "^1.2.1",
+        "tweetnacl": "^1.0.2"
+      },
+      "dependencies": {
+        "base-x": {
+          "version": "3.0.9",
+          "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz",
+          "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==",
+          "requires": {
+            "safe-buffer": "^5.0.1"
+          }
+        },
+        "bs58": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz",
+          "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==",
+          "requires": {
+            "base-x": "^3.0.2"
+          }
+        }
+      }
+    },
     "negotiator": {
     "negotiator": {
       "version": "0.6.3",
       "version": "0.6.3",
       "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
       "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",

+ 1 - 0
near/package.json

@@ -21,6 +21,7 @@
     "env-cmd": "^10.1.0",
     "env-cmd": "^10.1.0",
     "ethers": "^5.6.5",
     "ethers": "^5.6.5",
     "near-api-js": "^0.43.1",
     "near-api-js": "^0.43.1",
+    "near-seed-phrase": "^0.2.0",
     "prop-types": "^15.7.2",
     "prop-types": "^15.7.2",
     "react": "^17.0.2",
     "react": "^17.0.2",
     "react-dom": "^17.0.2",
     "react-dom": "^17.0.2",

+ 157 - 0
near/test/foo.ts

@@ -0,0 +1,157 @@
+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";
+
+import {
+  CONTRACTS,
+  attestNearFromNear,
+  attestTokenFromNear,
+  attestFromAlgorand,
+  createWrappedOnAlgorand,
+  tryNativeToUint8Array,
+  createWrappedOnNear,
+  getEmitterAddressAlgorand,
+  getForeignAssetAlgorand,
+  getForeignAssetNear,
+  getIsTransferCompletedNear,
+  getIsWrappedAssetNear,
+  getOriginalAssetNear,
+  getSignedVAAWithRetry,
+  redeemOnAlgorand,
+  redeemOnNear,
+  transferFromAlgorand,
+  transferNearFromNear,
+  transferTokenFromNear,
+} from "@certusone/wormhole-sdk";
+
+const wh = require("@certusone/wormhole-sdk");
+
+import {
+  CHAIN_ID_ALGORAND,
+  CHAIN_ID_NEAR,
+  ChainId,
+  ChainName,
+  textToHexString,
+  textToUint8Array,
+} from "@certusone/wormhole-sdk/lib/cjs/utils";
+
+import {
+  _parseVAAAlgorand,
+} from "@certusone/wormhole-sdk/lib/cjs/algorand";
+
+
+function getConfig(env: any) {
+  switch (env) {
+    case "sandbox":
+    case "local":
+      return {
+        networkId: "sandbox",
+        nodeUrl: "http://localhost:3030",
+        masterAccount: "test.near",
+        wormholeAccount: "wormhole.test.near",
+        tokenAccount: "token.test.near",
+        nftAccount: "nft.test.near",
+        testAccount: "test.test.near",
+      };
+    case "testnet":
+      return {
+        networkId: "testnet",
+        nodeUrl: "https://rpc.testnet.near.org",
+        masterAccount: "wormhole.testnet",
+        wormholeAccount: "wormhole.wormhole.testnet",
+        tokenAccount: "token.wormhole.testnet",
+        nftAccount: "nft.wormhole.testnet",
+        testAccount: "test.wormhole.testnet",
+      };
+  }
+  return {};
+}
+
+async function initNear() {
+  let e = "testnet";
+
+  let config = getConfig(e);
+
+  let masterKey = nearAPI.utils.KeyPair.fromString(
+    "ed25519:5dJ7Nsq4DQBdiGvZLPyjRVmhtRaScahsREpEPtaAyE9Z3CgyZFsaBwpybCRBMugiwhbFCUkqHk7PJ3BVcgZZ9Lgk"
+  );
+  let masterPubKey = masterKey.getPublicKey();
+
+  let keyStore = new nearAPI.keyStores.InMemoryKeyStore();
+  keyStore.setKey(config.networkId, config.masterAccount, masterKey);
+
+  let near = await nearAPI.connect({
+    deps: {
+      keyStore,
+    },
+    networkId: config.networkId,
+    nodeUrl: config.nodeUrl,
+  });
+  let masterAccount = new nearAPI.Account(
+    near.connection,
+    config.masterAccount
+  );
+
+  console.log(
+    "Finish init NEAR masterAccount: " +
+      JSON.stringify(await masterAccount.getAccountBalance())
+  );
+
+  console.log("setting key for new wormhole contract");
+  keyStore.setKey(config.networkId, config.wormholeAccount, masterKey);
+  keyStore.setKey(config.networkId, config.tokenAccount, masterKey);
+  keyStore.setKey(config.networkId, config.nftAccount, masterKey);
+
+  let tokenAccount = new nearAPI.Account(near.connection, config.tokenAccount);
+
+  let token_bridge = "token.wormhole.testnet";
+  let core_bridge = "wormhole.wormhole.testnet";
+
+  console.log(
+    await tokenAccount.viewFunction(config.tokenAccount, "emitter", {})
+  );
+
+  console.log("attesting Near itself");
+  let s = await attestNearFromNear(masterAccount, core_bridge, token_bridge);
+
+  const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
+    ["https://wormhole-v2-testnet-api.certus.one"],
+    CHAIN_ID_NEAR,
+    s[1],
+    s[0].toString(),
+    {
+      transport: NodeHttpTransport(),
+    }
+  );
+
+  console.log("vaa: " + Buffer.from(signedVAA).toString("hex"));
+
+  s = await transferNearFromNear(
+    masterAccount,
+    core_bridge,
+    token_bridge,
+    BigInt(1000000000000000000000000),
+    tryNativeToUint8Array("0x3bC7f2e458aC4E55F941C458cfD8c6851a591B4F", "ethereum"),
+    2,
+    BigInt(0)
+  );
+
+  const { vaaBytes: signedTrans } = await getSignedVAAWithRetry(
+    ["https://wormhole-v2-testnet-api.certus.one"],
+    CHAIN_ID_NEAR,
+    s[1],
+    s[0].toString(),
+    {
+      transport: NodeHttpTransport(),
+    }
+  );
+
+  console.log("vaa: " + Buffer.from(signedTrans).toString("hex"));
+
+    let p = _parseVAAAlgorand(signedTrans);
+    console.log(p);
+}
+
+initNear();

+ 1 - 1
near/test/sdk.ts

@@ -305,7 +305,7 @@ async function testNearSDK() {
   }
   }
 
 
   let usdc = await createWrappedOnNear(userAccount, token_bridge, usdcvaa);
   let usdc = await createWrappedOnNear(userAccount, token_bridge, usdcvaa);
-  console.log(usdc);
+  console.log("createWrappedOnNear returned " + usdc);
 
 
   if (usdc === "") {
   if (usdc === "") {
     console.log("null usdc ... we failed to create it?!");
     console.log("null usdc ... we failed to create it?!");

+ 3 - 3
near/test/test.ts

@@ -179,7 +179,7 @@ async function initTest() {
     "./contracts/wormhole/target/wasm32-unknown-unknown/release/near_wormhole.wasm"
     "./contracts/wormhole/target/wasm32-unknown-unknown/release/near_wormhole.wasm"
   );
   );
   const tokenContract = await fs.readFile(
   const tokenContract = await fs.readFile(
-    "./contracts/portal/target/wasm32-unknown-unknown/release/near_token_bridge.wasm"
+    "./contracts/token-bridge/target/wasm32-unknown-unknown/release/near_token_bridge.wasm"
   );
   );
   const testContract = await fs.readFile(
   const testContract = await fs.readFile(
     "./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"
@@ -296,14 +296,14 @@ async function test() {
   );
   );
 
 
   console.log("sending it to the core contract");
   console.log("sending it to the core contract");
-  await wormholeUseContract.submit_vaa({ args: { vaa: vaa } });
+  await wormholeUseContract.submit_vaa({ args: { vaa: vaa }, amount: "30000000000000000000001", gas: 150000000000000 } );
 
 
   seq = seq + 1;
   seq = seq + 1;
 
 
   if (!fastTest) {
   if (!fastTest) {
     console.log("Its parsed... lets do it again!!");
     console.log("Its parsed... lets do it again!!");
     try {
     try {
-      await wormholeUseContract.submit_vaa({ args: { vaa: vaa } });
+      await wormholeUseContract.submit_vaa({ args: { vaa: vaa }, amount: "30000000000000000000001", gas: 150000000000000 } );
       console.log("This should have thrown a exception..");
       console.log("This should have thrown a exception..");
       process.exit(1);
       process.exit(1);
     } catch {
     } catch {

+ 137 - 9
node/pkg/near/watcher.go

@@ -8,6 +8,7 @@ import (
 	"io/ioutil"
 	"io/ioutil"
 	"net/http"
 	"net/http"
 	"strings"
 	"strings"
+	"sync"
 	"time"
 	"time"
 
 
 	"github.com/certusone/wormhole/node/pkg/common"
 	"github.com/certusone/wormhole/node/pkg/common"
@@ -33,7 +34,20 @@ type (
 		msgChan  chan *common.MessagePublication
 		msgChan  chan *common.MessagePublication
 		obsvReqC chan *gossipv1.ObservationRequest
 		obsvReqC chan *gossipv1.ObservationRequest
 
 
-		next_round uint64
+		next_round  uint64
+		final_round uint64
+
+		pending   map[pendingKey]*pendingMessage
+		pendingMu sync.Mutex
+	}
+
+	pendingKey struct {
+		hash string
+	}
+
+	pendingMessage struct {
+		height uint64
+		ts     uint64
 	}
 	}
 )
 )
 
 
@@ -63,6 +77,8 @@ func NewWatcher(
 		msgChan:          lockEvents,
 		msgChan:          lockEvents,
 		obsvReqC:         obsvReqC,
 		obsvReqC:         obsvReqC,
 		next_round:       0,
 		next_round:       0,
+		final_round:      0,
+		pending:          map[pendingKey]*pendingMessage{},
 	}
 	}
 }
 }
 
 
@@ -116,13 +132,7 @@ func (e *Watcher) getTxStatus(logger *zap.Logger, tx string, src string) ([]byte
 	return ioutil.ReadAll(resp.Body)
 	return ioutil.ReadAll(resp.Body)
 }
 }
 
 
-func (e *Watcher) inspectStatus(logger *zap.Logger, hash string, receiver_id string, ts uint64) error {
-	t, err := e.getTxStatus(logger, hash, receiver_id)
-
-	if err != nil {
-		return err
-	}
-
+func (e *Watcher) parseStatus(logger *zap.Logger, t []byte, hash string, ts uint64) error {
 	outcomes := gjson.ParseBytes(t).Get("result.receipts_outcome")
 	outcomes := gjson.ParseBytes(t).Get("result.receipts_outcome")
 
 
 	if !outcomes.Exists() {
 	if !outcomes.Exists() {
@@ -226,6 +236,82 @@ func (e *Watcher) inspectStatus(logger *zap.Logger, hash string, receiver_id str
 	return nil
 	return nil
 }
 }
 
 
+func (e *Watcher) inspectStatus(logger *zap.Logger, hash string, receiver_id string, ts uint64) error {
+	t, err := e.getTxStatus(logger, hash, receiver_id)
+
+	if err != nil {
+		return err
+	}
+
+	return e.parseStatus(logger, t, hash, ts)
+}
+
+func (e *Watcher) lastBlock(logger *zap.Logger, hash string, receiver_id string, ts uint64) ([]byte, uint64, error) {
+	t, err := e.getTxStatus(logger, hash, receiver_id)
+
+	if err != nil {
+		return nil, 0, err
+	}
+
+	last_block := uint64(0)
+
+	outcomes := gjson.ParseBytes(t).Get("result.receipts_outcome")
+
+	if !outcomes.Exists() {
+		return nil, 0, err
+	}
+
+	for _, o := range outcomes.Array() {
+		outcome := o.Get("outcome")
+		if !outcome.Exists() {
+			continue
+		}
+
+		executor_id := outcome.Get("executor_id")
+		if !executor_id.Exists() {
+			continue
+		}
+
+		if executor_id.String() == e.wormholeContract {
+			l := outcome.Get("logs")
+			if !l.Exists() {
+				continue
+			}
+			for _, log := range l.Array() {
+				event := log.String()
+				if !strings.HasPrefix(event, "EVENT_JSON:") {
+					continue
+				}
+				logger.Info("event", zap.String("event", event[11:]))
+
+				event_json := gjson.ParseBytes([]byte(event[11:]))
+
+				standard := event_json.Get("standard")
+				if !standard.Exists() || standard.String() != "wormhole" {
+					continue
+				}
+				event_type := event_json.Get("event")
+				if !event_type.Exists() || event_type.String() != "publish" {
+					continue
+				}
+
+				block := event_json.Get("block")
+				if !block.Exists() {
+					continue
+				}
+
+				b := block.Uint()
+
+				if b > last_block {
+					last_block = b
+				}
+			}
+		}
+	}
+
+	return t, last_block, nil
+}
+
 func (e *Watcher) inspectBody(logger *zap.Logger, block uint64, body gjson.Result) error {
 func (e *Watcher) inspectBody(logger *zap.Logger, block uint64, body gjson.Result) error {
 	logger.Info("inspectBody", zap.Uint64("block", block))
 	logger.Info("inspectBody", zap.Uint64("block", block))
 
 
@@ -257,10 +343,32 @@ func (e *Watcher) inspectBody(logger *zap.Logger, block uint64, body gjson.Resul
 				continue
 				continue
 			}
 			}
 
 
-			err = e.inspectStatus(logger, hash.String(), receiver_id.String(), ts)
+			t, round, err := e.lastBlock(logger, hash.String(), receiver_id.String(), ts)
 			if err != nil {
 			if err != nil {
 				return err
 				return err
 			}
 			}
+			if round != 0 {
+
+				if round <= e.final_round {
+					logger.Info("parseStatus direct", zap.Uint64("block.height", round), zap.Uint64("e.final_round", e.final_round))
+					e.parseStatus(logger, t, hash.String(), ts)
+				} else {
+					logger.Info("pushing pending",
+						zap.Uint64("block.height", round),
+						zap.Uint64("e.final_round", e.final_round),
+					)
+					key := pendingKey{
+						hash: hash.String(),
+					}
+
+					e.pendingMu.Lock()
+					e.pending[key] = &pendingMessage{
+						height: round,
+						ts:     ts,
+					}
+					e.pendingMu.Unlock()
+				}
+			}
 		}
 		}
 
 
 	}
 	}
@@ -321,6 +429,26 @@ func (e *Watcher) Run(ctx context.Context) error {
 				}
 				}
 				parsedFinalBody := gjson.ParseBytes(finalBody)
 				parsedFinalBody := gjson.ParseBytes(finalBody)
 				lastBlock := parsedFinalBody.Get("result.chunks.0.height_created").Uint()
 				lastBlock := parsedFinalBody.Get("result.chunks.0.height_created").Uint()
+				e.final_round = lastBlock
+
+				e.pendingMu.Lock()
+				for key, bLock := range e.pending {
+					if bLock.height <= e.final_round {
+						logger.Info("finalBlock",
+							zap.Uint64("block.height", bLock.height),
+							zap.Uint64("e.final_round", e.final_round),
+							zap.String("key.hash", key.hash),
+						)
+
+						err := e.inspectStatus(logger, key.hash, e.wormholeContract, bLock.ts)
+						delete(e.pending, key)
+
+						if err != nil {
+							logger.Error(fmt.Sprintf("inspectStatus", err.Error()))
+						}
+					}
+				}
+				e.pendingMu.Unlock()
 
 
 				logger.Info("lastBlock", zap.Uint64("lastBlock", lastBlock), zap.Uint64("next_round", e.next_round))
 				logger.Info("lastBlock", zap.Uint64("lastBlock", lastBlock), zap.Uint64("next_round", e.next_round))
 
 

+ 1 - 0
sdk/js/src/utils/index.ts

@@ -3,3 +3,4 @@ export * from "./createNonce";
 export * from "./parseVaa";
 export * from "./parseVaa";
 export * from "./array";
 export * from "./array";
 export * from "./bigint";
 export * from "./bigint";
+export * from "./near";

+ 26 - 0
sdk/js/src/utils/near.ts

@@ -0,0 +1,26 @@
+import nearAPI from "near-api-js";
+
+export function logNearGas(result: any, comment: string) {
+  const { totalGasBurned, totalTokensBurned } = result.receipts_outcome.reduce(
+    (acc: any, receipt: any) => {
+      acc.totalGasBurned += receipt.outcome.gas_burnt;
+      acc.totalTokensBurned += nearAPI.utils.format.formatNearAmount(
+        receipt.outcome.tokens_burnt
+      );
+      return acc;
+    },
+    {
+      totalGasBurned: result.transaction_outcome.outcome.gas_burnt,
+      totalTokensBurned: nearAPI.utils.format.formatNearAmount(
+        result.transaction_outcome.outcome.tokens_burnt
+      ),
+    }
+  );
+  console.log(
+    comment,
+    "totalGasBurned",
+    totalGasBurned,
+    "totalTokensBurned",
+    totalTokensBurned
+  );
+}

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.