merged solana and part 2 from intro branch

This commit is contained in:
skojenov 2022-01-11 19:08:19 +00:00 committed by Evan Gray
parent f6f7e8fa65
commit b70b5b3f1c
325 changed files with 13576 additions and 172663 deletions

1
.gitignore vendored
View File

@ -10,3 +10,4 @@ venv
.env
bigtable-admin.json
bigtable-writer.json
*.so

201
LICENSE
View File

@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -11,9 +11,26 @@
```bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup default nightly-2021-08-01
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
sh -c "$(curl -sSfL https://release.solana.com/v1.8.1/install)"
```
Actually:
```bash
rustup default nightly
```
Works instead of nightly-2021-08-01
And in messenger_solana_bg.js following code needs to commented out for now:
```
free() {
const ptr = this.__destroy_into_raw();
wasm.__wbg_systeminstruction_free(ptr);
}
```
restart your terminal or run the provided export command
## Setup
@ -25,6 +42,7 @@ Deploy the EVM contracts
```bash
cd ethereum
npm ci
npm run build
npm run migrate
```
@ -34,11 +52,13 @@ Deploy the Solana contracts and generate wasm (for UI)
cd solana
EMITTER_ADDRESS="11111111111111111111111111111115" cargo build-bpf
# if running the local (tilt) devnet
# if kubectl was not found - use "minikube kubectl -- " in place of "kubectl "
kubectl cp -c devnet target/deploy/messenger_solana.so solana-devnet-0:/usr/src/
kubectl cp -c devnet id.json solana-devnet-0:/usr/src/
kubectl exec -c devnet solana-devnet-0 -- solana program deploy -u l --output json -k /usr/src/id.json /usr/src/messenger_solana.so > ../ui/src/contract-addresses/solana.json
# else if running the minimal-devnet
solana program deploy -u l --output json -k id.json target/deploy/messenger_solana.so > ../ui/src/contract-addresses/solana.json
# Generate wasm for JS code (Tilt and minimal-devnet)
EMITTER_ADDRESS="11111111111111111111111111111115" wasm-pack build --target bundler -d bundler -- --features wasm
```

File diff suppressed because it is too large Load Diff

View File

@ -12,15 +12,18 @@ no-entrypoint = []
wasm = ["no-entrypoint"]
[dependencies]
borsh = "0.9.1"
solana-program = { version="=1.7.0" }
borsh = "=0.9.1"
solana-program = { version="=1.9.4" }
nom = { version="7", default-features=false, features=["alloc"] }
wasm-bindgen = { version = "0.2.74", features = ["serde-serialize"] }
wormhole-sdk = { path = "../../../sdk/rust/sdk", features = ["devnet", "solana"] }
wasm-bindgen = { version = "0.2.74", features = ["serde-serialize"] }
wormhole-sdk = { git = "https://github.com/certusone/wormhole", features = ["devnet", "solana"] }
#wormhole-sdk = { git = "https://github.com/certusone/wormhole", branch="fix/sdk/terra", features = ["devnet", "solana"] }
#wormhole-sdk = { git = "https://github.com/certusone/wormhole", branch="feat/solana-update-1-9-4", features = ["devnet", "solana"] }
#wormhole-sdk = { path = "../../../../wormhole/sdk/rust/sdk", features = ["devnet", "solana"] }
[dev-dependencies]
solana-program-test = "=1.7.0"
solana-sdk = "=1.7.0"
solana-program-test = "=1.9.4"
solana-sdk = "=1.9.4"
rand = "0.7.3"
[patch.crates-io]

101
intro/part1/solana/memmap2-rs/Cargo.lock generated Normal file
View File

@ -0,0 +1,101 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]]
name = "libc"
version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c"
[[package]]
name = "memmap2"
version = "0.1.0"
dependencies = [
"libc",
"tempdir",
]
[[package]]
name = "rand"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
dependencies = [
"fuchsia-cprng",
"libc",
"rand_core 0.3.1",
"rdrand",
"winapi",
]
[[package]]
name = "rand_core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
dependencies = [
"rand_core 0.4.2",
]
[[package]]
name = "rand_core"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
[[package]]
name = "rdrand"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "remove_dir_all"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
dependencies = [
"winapi",
]
[[package]]
name = "tempdir"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
dependencies = [
"rand",
"remove_dir_all",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@ -41,7 +41,7 @@ pub fn send_message(
AccountMeta::new_readonly(emitter, false),
AccountMeta::new(message, true),
AccountMeta::new(config, false),
AccountMeta::new_readonly(fee_collector, false),
AccountMeta::new(fee_collector, false),
AccountMeta::new(sequence, false),
AccountMeta::new_readonly(wormhole, false),
AccountMeta::new_readonly(system_program::id(), false),

View File

@ -13,7 +13,7 @@ use solana_program::account_info::{
AccountInfo,
};
use solana_program::entrypoint::ProgramResult;
use solana_program::program::invoke_signed;
//use solana_program::program::invoke_signed; // For non-SDK calls
use solana_program::pubkey::Pubkey;
use solana_program::{
entrypoint,
@ -21,7 +21,10 @@ use solana_program::{
// Import Solana Wormhole SDK.
use wormhole_sdk::{
instructions::post_message,
// instructions::post_message, // For non-SDK calls
// read_config, // For non-SDK calls
// fee_collector, // For non-SDK calls
post_message, // SDK call.
ConsistencyLevel,
};
@ -64,7 +67,7 @@ pub fn process_instruction(id: &Pubkey, accs: &[AccountInfo], data: &[u8]) -> Pr
}
/// Sends a message from this chain to a remote target chain.
/// Sends a message from this chain to wormhole.
fn send_message(id: &Pubkey, accs: &[AccountInfo], payload: Vec<u8>) -> ProgramResult {
let accounts = &mut accs.iter();
let payer = next_account_info(accounts)?;
@ -80,12 +83,49 @@ fn send_message(id: &Pubkey, accs: &[AccountInfo], payload: Vec<u8>) -> ProgramR
let _rent = next_account_info(accounts)?;
let _clock = next_account_info(accounts)?;
// Emit Message via the Wormhole.
let (emitter, mut seeds, bump) = wormhole_sdk::emitter(id);
let bump = &[bump];
seeds.push(bump);
// Use SDK:
// Extract seeds for emitter account only if needed to pass to post_message
// let (emitter, mut seeds, bump) = wormhole_sdk::emitter(id);
// let bump = &[bump];
// seeds.push(bump);
post_message(
*id,
*payer.key,
*message.key,
payload,
ConsistencyLevel::Confirmed,
None, //Some(&seeds), // If needed.
accs,
0
)?;
/*
// Emit Message via the Wormhole.
let (emitter, mut seeds, bump) = wormhole_sdk::emitter(id);
let bump = &[bump];
seeds.push(bump);
// Test from SDK.
// Filter for the Config AccountInfo so we can access its data.
let config = wormhole_sdk::config(&wormhole_sdk::id());
let config = accs.iter().find(|item| *item.key == config).unwrap();
let config = read_config(config).unwrap();
let fee_collector = fee_collector(&wormhole_sdk::id());
invoke_signed(
&solana_program::system_instruction::transfer(
payer.key,
&fee_collector,
config.fee
),
accs,
&[&seeds],
)?;
//zz Use instructions:
invoke_signed(
&post_message(
wormhole_sdk::id(),
*payer.key,
@ -99,6 +139,6 @@ fn send_message(id: &Pubkey, accs: &[AccountInfo], payload: Vec<u8>) -> ProgramR
accs,
&[&seeds],
)?;
*/
Ok(())
}

View File

@ -17,6 +17,12 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "ahash"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e"
[[package]]
name = "ahash"
version = "0.7.6"
@ -78,10 +84,111 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
[[package]]
name = "bumpalo"
version = "3.8.0"
name = "borsh"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c"
checksum = "09a7111f797cc721407885a323fb071636aee57f750b1a4ddc27397eba168a74"
dependencies = [
"borsh-derive 0.8.2",
"hashbrown 0.9.1",
]
[[package]]
name = "borsh"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18dda7dc709193c0d86a1a51050a926dc3df1cf262ec46a23a25dba421ea1924"
dependencies = [
"borsh-derive 0.9.1",
"hashbrown 0.9.1",
]
[[package]]
name = "borsh-derive"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "307f3740906bac2c118a8122fe22681232b244f1369273e45f1156b45c43d2dd"
dependencies = [
"borsh-derive-internal 0.8.2",
"borsh-schema-derive-internal 0.8.2",
"proc-macro-crate",
"proc-macro2",
"syn",
]
[[package]]
name = "borsh-derive"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "684155372435f578c0fa1acd13ebbb182cc19d6b38b64ae7901da4393217d264"
dependencies = [
"borsh-derive-internal 0.9.1",
"borsh-schema-derive-internal 0.9.1",
"proc-macro-crate",
"proc-macro2",
"syn",
]
[[package]]
name = "borsh-derive-internal"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2104c73179359431cc98e016998f2f23bc7a05bc53e79741bcba705f30047bc"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "borsh-derive-internal"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2102f62f8b6d3edeab871830782285b64cc1830168094db05c8e458f209bc5c3"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "borsh-schema-derive-internal"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae29eb8418fcd46f723f8691a2ac06857d31179d33d2f2d91eb13967de97c728"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "borsh-schema-derive-internal"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "196c978c4c9b0b142d446ef3240690bf5a8a33497074a113ff9a337ccb750483"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "bstr"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
dependencies = [
"lazy_static",
"memchr",
"regex-automata",
]
[[package]]
name = "bumpalo"
version = "3.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899"
[[package]]
name = "bytecheck"
@ -136,9 +243,9 @@ checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b"
[[package]]
name = "cosmwasm-crypto"
version = "0.16.2"
version = "0.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec9bdd1f4da5fc0d085251b0322661c5aaf773ab299e3e205fb18130b7f6ba3"
checksum = "3703ca1b98c8d890b82c3978f3c5bd47116f8767340dfaa4fd9bdcaa15ebcc64"
dependencies = [
"digest",
"ed25519-zebra",
@ -149,18 +256,18 @@ dependencies = [
[[package]]
name = "cosmwasm-derive"
version = "0.16.2"
version = "0.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ac17a14b4ab09a5d89b5301218067acca33d9311376e5c34c9877f09e562395"
checksum = "c04f4923c080df70b04ff3e0680c92e3b8357f3b125ed65ce4bd4aa1f522c06f"
dependencies = [
"syn",
]
[[package]]
name = "cosmwasm-std"
version = "0.16.2"
version = "0.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e47306c113f4d964c35a74a87ceb8ccfb5811e9810a9dc427101148b5b9134ca"
checksum = "c80dbbb380c23a4f10ae6178dd411ed90c9f9931ddf4932156cc5e5ab78d1c19"
dependencies = [
"base64",
"cosmwasm-crypto",
@ -174,9 +281,9 @@ dependencies = [
[[package]]
name = "cosmwasm-storage"
version = "0.16.2"
version = "0.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e3472d8e0e7155c5f4d89674ad47adede4b1491ad14f4141610e1522028a6a7"
checksum = "8b4cc64cb7104bcf64e935e074aa291466d7c714374f5ec5e3fd8e9d3f0e5ce5"
dependencies = [
"cosmwasm-std",
"serde",
@ -184,9 +291,9 @@ dependencies = [
[[package]]
name = "cosmwasm-vm"
version = "0.16.2"
version = "0.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d90f1d30e2d01d815c520dad2738f93188f2e64b3dda3e11609c13eb73109b8"
checksum = "dbdabee8ab212295ec3ab04aaca4e0e054e1def63e0c00bf8ebd0dd63509b716"
dependencies = [
"clru",
"cosmwasm-crypto",
@ -283,9 +390,9 @@ dependencies = [
[[package]]
name = "crossbeam-channel"
version = "0.5.1"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4"
checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa"
dependencies = [
"cfg-if",
"crossbeam-utils",
@ -304,9 +411,9 @@ dependencies = [
[[package]]
name = "crossbeam-epoch"
version = "0.9.5"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd"
checksum = "97242a70df9b89a65d0b6df3c4bf5b9ce03c5b7309019777fbde37e7537f8762"
dependencies = [
"cfg-if",
"crossbeam-utils",
@ -317,9 +424,9 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
version = "0.8.5"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
checksum = "cfcae03edb34f947e64acdb1c33ec169824e20657e9ecb61cef6c8c74dcb8120"
dependencies = [
"cfg-if",
"lazy_static",
@ -366,6 +473,103 @@ dependencies = [
"zeroize",
]
[[package]]
name = "cw-storage-plus"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1e867b9972b83b32e00e878dfbff48299ba26618dabeb19b9c56fae176dc225"
dependencies = [
"cosmwasm-std",
"schemars",
"serde",
]
[[package]]
name = "cw0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c497f885a40918a02df7d938c81809965fa05cfc21b3dc591e9950237b5de0a9"
dependencies = [
"cosmwasm-std",
"schemars",
"serde",
"thiserror",
]
[[package]]
name = "cw2"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d48454f96494aa1018556cd457977375cc8c57ef3e5c767cfa2ea5ec24b0258"
dependencies = [
"cosmwasm-std",
"cw-storage-plus",
"schemars",
"serde",
]
[[package]]
name = "cw20"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a11a2adbd52258f5b4ed5323f62bc6e559f2cefbe52ef0e58290016fde5bb083"
dependencies = [
"cosmwasm-std",
"cw0",
"schemars",
"serde",
]
[[package]]
name = "cw20-base"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe3791e0f6b4a0a82b86541d48dcc67c2d607da8e5691a91b40b2c06ddf09c52"
dependencies = [
"cosmwasm-std",
"cw-storage-plus",
"cw0",
"cw2",
"cw20",
"schemars",
"serde",
"thiserror",
]
[[package]]
name = "cw20-legacy"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d27b11827323369993519abb494f3ae9b6aac4d716d058bea5e181b9b0074b7"
dependencies = [
"cosmwasm-std",
"cosmwasm-storage",
"cw-storage-plus",
"cw0",
"cw2",
"cw20",
"cw20-base",
"schemars",
"serde",
"thiserror",
]
[[package]]
name = "cw20-wrapped"
version = "0.1.0"
source = "git+https://github.com/certusone/wormhole?branch=fix/sdk/terra#7b1473e56a1b0a5b08e25c0f95b34d6b17c0dfe0"
dependencies = [
"cosmwasm-std",
"cosmwasm-storage",
"cw-storage-plus",
"cw2",
"cw20",
"cw20-legacy",
"schemars",
"serde",
"thiserror",
]
[[package]]
name = "darling"
version = "0.13.1"
@ -526,6 +730,15 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
[[package]]
name = "fastrand"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "779d043b6a0b90cc4c0ed7ee380a6504394cee7efd7db050e3774eee387324b2"
dependencies = [
"instant",
]
[[package]]
name = "ff"
version = "0.10.1"
@ -536,6 +749,15 @@ dependencies = [
"subtle",
]
[[package]]
name = "fixed-hash"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c"
dependencies = [
"static_assertions",
]
[[package]]
name = "fnv"
version = "1.0.7"
@ -544,9 +766,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "generic-array"
version = "0.14.4"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803"
dependencies = [
"typenum",
"version_check",
@ -602,13 +824,22 @@ dependencies = [
"subtle",
]
[[package]]
name = "hashbrown"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
dependencies = [
"ahash 0.4.7",
]
[[package]]
name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
dependencies = [
"ahash",
"ahash 0.7.6",
]
[[package]]
@ -644,15 +875,24 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "indexmap"
version = "1.7.0"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5"
checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223"
dependencies = [
"autocfg",
"hashbrown",
"hashbrown 0.11.2",
"serde",
]
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]]
name = "itoa"
version = "1.0.1"
@ -761,9 +1001,9 @@ checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "memmap2"
version = "0.5.0"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4647a11b578fead29cdbb34d4adef8dd3dc35b876c9c6d5240d83f205abfe96e"
checksum = "fe3179b85e1fd8b14447cbebadb75e45a1002f541b925f0bfec366d56a81c56d"
dependencies = [
"libc",
]
@ -777,6 +1017,12 @@ dependencies = [
"autocfg",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.4.4"
@ -793,6 +1039,17 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389"
[[package]]
name = "nom"
version = "7.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109"
dependencies = [
"memchr",
"minimal-lexical",
"version_check",
]
[[package]]
name = "num_cpus"
version = "1.13.1"
@ -834,9 +1091,9 @@ checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92"
[[package]]
name = "pin-project-lite"
version = "0.2.7"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443"
checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c"
[[package]]
name = "pkcs8"
@ -849,10 +1106,23 @@ dependencies = [
]
[[package]]
name = "ppv-lite86"
version = "0.2.15"
name = "primitive-types"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba"
checksum = "06345ee39fbccfb06ab45f3a1a5798d9dafa04cb8921a76d227040003a234b0e"
dependencies = [
"fixed-hash",
"uint",
]
[[package]]
name = "proc-macro-crate"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785"
dependencies = [
"toml",
]
[[package]]
name = "proc-macro-error"
@ -880,9 +1150,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.34"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f84e92c0f7c9d58328b85a78557813e4bd845130db68d7184635344399423b1"
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
dependencies = [
"unicode-xid",
]
@ -909,35 +1179,13 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.10"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
dependencies = [
"libc",
"rand_chacha",
"rand_core 0.6.3",
"rand_hc",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core 0.6.3",
]
[[package]]
name = "rand_core"
version = "0.5.1"
@ -956,15 +1204,6 @@ dependencies = [
"getrandom 0.2.3",
]
[[package]]
name = "rand_hc"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
dependencies = [
"rand_core 0.6.3",
]
[[package]]
name = "rayon"
version = "1.5.1"
@ -1010,6 +1249,12 @@ dependencies = [
"smallvec",
]
[[package]]
name = "regex-automata"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
[[package]]
name = "region"
version = "3.0.0"
@ -1042,12 +1287,12 @@ dependencies = [
[[package]]
name = "rkyv"
version = "0.7.28"
version = "0.7.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "631f7d2a2854abb66724f492ce5256e79685a673dc210ac022194cedd5c914d3"
checksum = "49a37de5dfc60bae2d94961dacd03c7b80e426b66a99fa1b17799570dbdd8f96"
dependencies = [
"bytecheck",
"hashbrown",
"hashbrown 0.11.2",
"ptr_meta",
"rend",
"rkyv_derive",
@ -1056,9 +1301,9 @@ dependencies = [
[[package]]
name = "rkyv_derive"
version = "0.7.28"
version = "0.7.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c067e650861a749720952aed722fb344449bc95de33e6456d426f5c7d44f71c0"
checksum = "719d447dd0e84b23cee6cb5b32d97e21efb112a3e3c636c8da36647b938475a1"
dependencies = [
"proc-macro2",
"quote",
@ -1127,9 +1372,9 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
[[package]]
name = "serde"
version = "1.0.132"
version = "1.0.133"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b9875c23cf305cd1fd7eb77234cbb705f21ea6a72c637a5c6db5fe4b8e7f008"
checksum = "97565067517b60e2d1ea8b268e59ce036de907ac523ad83a0475da04e818989a"
dependencies = [
"serde_derive",
]
@ -1154,9 +1399,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.132"
version = "1.0.133"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecc0db5cb2556c0e558887d9bbdcf6ac4471e83ff66cf696e5419024d1606276"
checksum = "ed201699328568d8d08208fdd080e3ff594e6c422e438b6705905da01005d537"
dependencies = [
"proc-macro2",
"quote",
@ -1176,9 +1421,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.73"
version = "1.0.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcbd0344bc6533bc7ec56df11d42fb70f1b912351c0825ccb7211b59d8af7cf5"
checksum = "ee2bb9cd061c5865d345bb02ca49fcef1391741b672b54a0bf7b679badec3142"
dependencies = [
"itoa",
"ryu",
@ -1187,9 +1432,9 @@ dependencies = [
[[package]]
name = "sha2"
version = "0.9.8"
version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa"
checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800"
dependencies = [
"block-buffer",
"cfg-if",
@ -1261,9 +1506,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]]
name = "syn"
version = "1.0.83"
version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23a1dfb999630e338648c83e91c59a4e9fb7620f520c3194b6b89e276f2f1959"
checksum = "a684ac3dcd8913827e18cd09a68384ee66c1de24157e3c556c9ab16d85695fb7"
dependencies = [
"proc-macro2",
"quote",
@ -1278,13 +1523,13 @@ checksum = "d9bffcddbc2458fa3e6058414599e3c838a022abae82e5c67b4f7f80298d5bff"
[[package]]
name = "tempfile"
version = "3.2.0"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
dependencies = [
"cfg-if",
"fastrand",
"libc",
"rand",
"redox_syscall",
"remove_dir_all",
"winapi",
@ -1310,6 +1555,15 @@ dependencies = [
"syn",
]
[[package]]
name = "toml"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
dependencies = [
"serde",
]
[[package]]
name = "tracing"
version = "0.1.29"
@ -1345,9 +1599,9 @@ dependencies = [
[[package]]
name = "typenum"
version = "1.14.0"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec"
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
[[package]]
name = "uint"
@ -1369,9 +1623,9 @@ checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "version_check"
version = "0.9.3"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
@ -1699,12 +1953,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "wormhole"
name = "wormhole-bridge-terra"
version = "0.1.0"
source = "git+https://github.com/certusone/wormhole?branch=fix/sdk/terra#7b1473e56a1b0a5b08e25c0f95b34d6b17c0dfe0"
dependencies = [
"cosmwasm-std",
"cosmwasm-storage",
"cosmwasm-vm",
"cw20",
"cw20-base",
"cw20-wrapped",
"generic-array",
"getrandom 0.2.3",
"hex",
@ -1712,13 +1969,60 @@ dependencies = [
"lazy_static",
"schemars",
"serde",
"serde_json",
"sha3",
"thiserror",
]
[[package]]
name = "wormhole-core"
version = "0.1.0"
source = "git+https://github.com/certusone/wormhole?branch=fix/sdk/terra#7b1473e56a1b0a5b08e25c0f95b34d6b17c0dfe0"
dependencies = [
"bstr",
"byteorder",
"hex",
"nom",
"primitive-types",
"sha3",
]
[[package]]
name = "wormhole-messenger-terra"
version = "0.0.1"
dependencies = [
"borsh 0.9.1",
"cosmwasm-std",
"cosmwasm-storage",
"cosmwasm-vm",
"schemars",
"serde",
"serde_json",
"wormhole-sdk",
]
[[package]]
name = "wormhole-sdk"
version = "0.1.0"
source = "git+https://github.com/certusone/wormhole?branch=fix/sdk/terra#7b1473e56a1b0a5b08e25c0f95b34d6b17c0dfe0"
dependencies = [
"borsh 0.8.2",
"cosmwasm-std",
"cosmwasm-storage",
"nom",
"primitive-types",
"schemars",
"serde",
"wormhole-bridge-terra",
"wormhole-core",
]
[[package]]
name = "zeroize"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619"
[[patch.unused]]
name = "memmap2"
version = "0.1.0"
source = "git+https://github.com/certusone/wormhole#4c7f6fb32f5047909388e43bec876e590b2cfc7e"

View File

@ -1,24 +1,17 @@
[package]
name = "wormhole-messenger-terra"
version = "0.0.1"
edition = "2018"
[workspace]
members = ["contracts/messenger"]
exclude = ["sdk-copy"]
[lib]
crate-type = ["cdylib", "rlib"]
[profile.release]
opt-level = 3
debug = false
rpath = false
lto = true
debug-assertions = false
codegen-units = 1
panic = 'abort'
incremental = false
overflow-checks = true
[features]
library = []
backtraces = ["cosmwasm-std/backtraces"]
[dependencies]
borsh = "*"
cosmwasm-std = { version = "0.16.0" }
cosmwasm-storage = { version = "0.16.0" }
schemars = "0.8.1"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
wormhole-sdk = { path = "../../../sdk/rust/sdk", features = ["terra", "vaa"] }
wormhole-messenger-common = { path = "../common" }
[dev-dependencies]
cosmwasm-vm = { version = "0.16.0", default-features = false }
serde_json = "1.0"
[patch.crates-io]
memmap2 = { git = "https://github.com/certusone/wormhole", package = "memmap2" }

View File

@ -0,0 +1,29 @@
[package]
name = "wormhole-messenger-terra"
version = "0.0.1"
edition = "2018"
exclude = [
# Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication.
"sdk-copy",
]
[lib]
crate-type = ["cdylib", "rlib"]
[features]
library = []
backtraces = ["cosmwasm-std/backtraces"]
[dependencies]
borsh = "*"
cosmwasm-std = { version = "0.16.0" }
cosmwasm-storage = { version = "0.16.0" }
schemars = "0.8.1"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
#wormhole-sdk = { path = "../../../sdk/rust/sdk", features = ["terra", "devnet"] }
wormhole-sdk = { git = "https://github.com/certusone/wormhole", branch="fix/sdk/terra", features = ["terra", "devnet"] }
[dev-dependencies]
cosmwasm-vm = { version = "0.16.0", default-features = false }
serde_json = "1.0"

View File

@ -10,6 +10,7 @@ use cosmwasm_std::{
Response,
StdError,
StdResult,
Binary,
};
use wormhole_sdk::{
parse_vaa,
@ -17,7 +18,12 @@ use wormhole_sdk::{
VAA,
};
use messenger_common::Message;
#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, PartialEq)]
//#[derive(Clone, Debug, PartialEq)]
struct Message {
/// Message text to be output on the target networks node logs.
pub text: String,
}
mod messages;
use messages::*;
@ -37,29 +43,26 @@ pub fn instantiate(
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult<Response> {
match msg {
// Emit a new message targetting an address on a foreign chain. The message is emitted via
// Wormhole and routed by the Guardians to the destination contract.
ExecuteMsg::SendMessage { nonce, nick, text } => Ok(Response::default()
// Emit a new message to be picked and signed by wormhole guardians.
ExecuteMsg::SendMessage { nonce, text } => Ok(Response::default()
.add_attribute("action", "send_message")
.add_message(post_message(
wormhole_sdk::id(),
nonce,
&Message { nick, text }
.try_to_vec()
.map_err(|_| StdError::generic_err("Encoding Failed"))?,
// &Message { text }
// .try_to_vec()
// &Binary::from(text.as_bytes()) //.as_bytes()
text.as_bytes()
)?)),
// Receive a VAA containing a message from another chain. The message is stored in the
// Terra contract state and can be read out via QueryMsg.
// Validate and Process VAA containing a message, typically from another chain.
ExecuteMsg::RecvMessage { vaa } => {
// Parse VAA and decode Payload into message.
let vaa = parse_vaa(wormhole_sdk::id(), deps, env, &vaa)?;
let vaa = parse_vaa(deps, env, &vaa)?;
let msg = Message::try_from_slice(&vaa.payload)
.map_err(|_| StdError::generic_err("Invalid Message"))?;
.map_err(|_| StdError::generic_err("Invalid payload Message"))?;
Ok(Response::default()
.add_attribute("action", "receive_message")
.add_attribute("nick", msg.nick)
.add_attribute("text", msg.text))
}
}
@ -67,6 +70,10 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S
#[cfg(test)]
mod testing {
use borsh::{
BorshDeserialize,
BorshSerialize,
};
use cosmwasm_std::testing::{
mock_dependencies,
mock_env,
@ -79,7 +86,6 @@ mod testing {
SubMsg,
WasmMsg,
};
use messenger_common::Message;
use super::{
execute,
@ -88,6 +94,12 @@ mod testing {
InstantiateMsg,
};
#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, PartialEq)]
struct Message {
/// Message text to be output on the target networks node logs.
pub text: String,
}
#[test]
fn test_send_message() {
// Test Messages
@ -97,8 +109,7 @@ mod testing {
let send_msg = ExecuteMsg::SendMessage {
nonce: 0,
nick: "Bob".to_string(),
text: "Hello Alice".to_string(),
text: "aaaaaaaaaa".to_string(),
};
// Instantiate Contract
@ -118,19 +129,21 @@ mod testing {
contract_addr: wormhole_sdk::id().to_string(),
funds: vec![],
msg: Binary::from(&[
123, 34, 112, 111, 115, 116, 95, 109, 101, 115, 115, 97, 103, 101, 34, 58, 123,
34, 109, 101, 115, 115, 97, 103, 101, 34, 58, 34, 87, 122, 77, 115, 77, 67,
119, 119, 76, 68, 65, 115, 78, 106, 89, 115, 77, 84, 69, 120, 76, 68, 107, 52,
76, 68, 69, 120, 76, 68, 65, 115, 77, 67, 119, 119, 76, 68, 99, 121, 76, 68,
69, 119, 77, 83, 119, 120, 77, 68, 103, 115, 77, 84, 65, 52, 76, 68, 69, 120,
77, 83, 119, 122, 77, 105, 119, 50, 78, 83, 119, 120, 77, 68, 103, 115, 77, 84,
65, 49, 76, 68, 107, 53, 76, 68, 69, 119, 77, 86, 48, 61, 34, 44, 34, 110, 111,
110, 99, 101, 34, 58, 48, 125, 125
123, 34, 112, 111, 115, 116, 95, 109, 101, 115,
115, 97, 103, 101, 34, 58, 123, 34, 109, 101, 115,
115, 97, 103, 101, 34, 58, 34, 87, 122, 69, 120, 76,
68, 65, 115, 77, 67, 119, 119, 76, 68, 99, 121, 76,
68, 69, 119, 77, 83, 119, 120, 77, 68, 103, 115, 77,
84, 65, 52, 76, 68, 69, 120, 77, 83, 119, 122, 77,
105, 119, 50, 78, 83, 119, 120, 77, 68, 103, 115, 77,
84, 65, 49, 76, 68, 107, 53, 76, 68, 69, 119, 77, 86,
48, 61, 34, 44, 34, 110, 111, 110, 99, 101, 34, 58, 48, 125, 125
]),
}))]
);
}
/*
#[test]
fn test_recv_message() {
// Test Messages
@ -162,8 +175,7 @@ mod testing {
0xfa, // Sequence
0x00, // Consistency
// Payload
5, 0, 0, 0, 65, 108, 105, 99, 101, 9, 0, 0, 0, 72, 101, 108, 108, 111, 32, 66, 111,
98,
9, 0, 0, 0, 72, 101, 108, 108, 111, 32, 66, 111, 98,
]),
};
@ -171,7 +183,6 @@ mod testing {
println!(
"{:?}",
Message {
nick: "Alice".to_string(),
text: "Hello Bob".to_string(),
}
.try_to_vec()
@ -185,7 +196,7 @@ mod testing {
// Receive a Message
let info = mock_info("addr0000", &[]);
let result = execute(deps.as_mut(), mock_env(), info, recv_msg).unwrap();
let result = execute(deps.as_mut(), mock_env(), info, recv_msg).unwrap(); // Fails on empty querier mock.
assert_eq!(
result.attributes,
vec![
@ -193,10 +204,6 @@ mod testing {
key: "action".to_string(),
value: "receive_message".to_string(),
},
Attribute {
key: "nick".to_string(),
value: "Alice".to_string(),
},
Attribute {
key: "text".to_string(),
value: "Hello Bob".to_string(),
@ -204,4 +211,5 @@ mod testing {
]
);
}
*/
}

View File

@ -25,7 +25,6 @@ pub enum ExecuteMsg {
SendMessage {
nonce: u32,
nick: String,
text: String,
},
}

140
intro/part1/terra/deploy.js Normal file
View File

@ -0,0 +1,140 @@
import { Wallet, LCDClient, MnemonicKey } from "@terra-money/terra.js";
import {
StdFee,
MsgInstantiateContract,
MsgExecuteContract,
MsgStoreCode,
} from "@terra-money/terra.js";
import { readFileSync, readdirSync, writeFile } from "fs";
// TODO: Workaround /tx/estimate_fee errors.
const gas_prices = {
uluna: "0.15",
usdr: "0.1018",
uusd: "0.15",
ukrw: "178.05",
umnt: "431.6259",
ueur: "0.125",
ucny: "0.97",
ujpy: "16",
ugbp: "0.11",
uinr: "11",
ucad: "0.19",
uchf: "0.13",
uaud: "0.19",
usgd: "0.2",
};
async function main() {
const terra = new LCDClient({
URL: "http://localhost:1317",
chainID: "localterra",
});
const wallet = terra.wallet(
new MnemonicKey({
mnemonic:
"notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius",
})
);
await wallet.sequence();
// Deploy WASM blobs.
// Read a list of files from directory containing compiled contracts.
const artifacts = readdirSync("../artifacts/");
// Sort them to get a determinstic list for consecutive code ids.
artifacts.sort();
artifacts.reverse();
console.log(artifacts);
const hardcodedGas = {
"wormhole_messenger_terra.wasm": 4000000,
};
const codeIds = {};
const addresses = {};
// Deploy found WASM files and assign Code IDs.
for (const artifact in artifacts) {
if (
artifacts.hasOwnProperty(artifact) &&
artifacts[artifact].includes(".wasm")
) {
const file = artifacts[artifact];
const contract_bytes = readFileSync(`../artifacts/${file}`);
console.log(`Storing WASM: ${file} (${contract_bytes.length} bytes)`);
const store_code = new MsgStoreCode(
wallet.key.accAddress,
contract_bytes.toString("base64")
);
try {
const tx = await wallet.createAndSignTx({
msgs: [store_code],
memo: "",
fee: new StdFee(hardcodedGas[artifacts[artifact]], {
uluna: "100000",
}),
});
console.log("step1 done");
const rs = await terra.tx.broadcast(tx);
const ci = /"code_id","value":"([^"]+)/gm.exec(rs.raw_log)[1];
codeIds[file] = parseInt(ci);
} catch (e) {
console.log("Failed to Execute" + e);
}
}
}
console.log(codeIds);
// Instantiate messenger
console.log("Instantiating messenger");
await wallet
.createAndSignTx({
msgs: [
new MsgInstantiateContract(
wallet.key.accAddress,
wallet.key.accAddress,
codeIds["wormhole_messenger_terra.wasm"],
{
// TBD...
version: "1.0.0",
}
),
],
memo: "",
})
.then((tx) => terra.tx.broadcast(tx))
.then((rs) => {
const address = /"contract_address","value":"([^"]+)/gm.exec(
rs.raw_log
)[1];
addresses["wormhole_messenger_terra.wasm"] = address;
});
console.log(addresses);
// store contract address.
writeFile(
"/address/terra.js",
'export const address = "' +
addresses["wormhole_messenger_terra.wasm"] +
'"',
(err) => {
if (err) {
console.error(err);
}
}
);
}
main();

View File

@ -1,16 +1,18 @@
# Wait for node to start
while ! /bin/netcat -z localhost 26657; do
echo "waiting for node 26657"
while ! /bin/netcat -zv localhost 26657; do
sleep 1
done
# Wait for first block
echo "waiting for block"
while [ $(curl localhost:26657/status -ks | jq ".result.sync_info.latest_block_height|tonumber") -lt 1 ]; do
sleep 1
done
echo "Going to sleep 2s"
sleep 2
npm ci && node deploy.js
echo "Going to sleep, interrupt if running manually"
sleep infinity
echo "Got back from deploy.js"
#sleep infinity

View File

@ -1,22 +1,29 @@
# This is a multi-stage docker file, first stage builds contracts
# And the second one creates node.js environment to deploy them
# This is a multi-stage docker file
# 1. builds contract
# 2. creates node.js environment to deploy them
# Tilt: run following commands:
# docker build -t ctr .
# docker run -it --network=host -v "$(dirname `pwd`)/ui/src/contract-addresses":/address ctr
# Build Contract
FROM cosmwasm/workspace-optimizer:0.12.1@sha256:1508cf7545f4b656ecafa34e29c1acf200cdab47fced85c2bc076c0c158b1338 AS builder
ADD Cargo.lock /code/
ADD Cargo.toml /code/
#ADD sdk-copy /
ADD contracts /code/contracts
RUN optimize_workspace.sh
# Contract deployment stage
# Deploy Contract (Tilt version)
FROM node:16-buster-slim@sha256:93c9fc3550f5f7d159f282027228e90e3a7f8bf38544758024f005e82607f546
RUN apt update && apt install netcat curl jq -y
WORKDIR /app/tools
COPY --from=builder /code/artifacts /app/artifacts
ADD ./artifacts/cw20_base.wasm /app/artifacts/
ADD ./tools /app/tools
ADD deploy.js /app/tools
ADD deploy.sh /app/tools
ADD package.json /app/tools
ADD package-lock.json /app/tools
RUN chmod +x /app/tools/deploy.sh
ENTRYPOINT /app/tools/deploy.sh

View File

@ -20,6 +20,7 @@
"@testing-library/react": "^11.2.7",
"@testing-library/user-event": "^12.8.3",
"@typechain/ethers-v5": "^8.0.5",
"@terra-money/wallet-provider": "^2.2.0",
"ethers": "^5.5.2",
"notistack": "^2.0.3",
"react": "^17.0.2",

View File

@ -1,22 +1,4 @@
import {
ChainId,
CHAIN_ID_BSC,
CHAIN_ID_ETH,
CHAIN_ID_SOLANA,
createNonce,
getBridgeFeeIx,
getEmitterAddressEth,
getEmitterAddressSolana,
hexToNativeString,
ixFromRust,
parseSequenceFromLogEth,
parseSequenceFromLogSolana,
} from "@certusone/wormhole-sdk";
import { uint8ArrayToNative } from "@certusone/wormhole-sdk/lib/esm";
import getSignedVAAWithRetry from "@certusone/wormhole-sdk/lib/esm/rpc/getSignedVAAWithRetry";
import { importCoreWasm } from "@certusone/wormhole-sdk/lib/esm/solana/wasm";
import { hexlify, hexStripZeros } from "@ethersproject/bytes";
import { Web3Provider } from "@ethersproject/providers";
import React, { useCallback, useState } from "react";
import {
Box,
Button,
@ -30,14 +12,48 @@ import {
TextField,
Typography,
} from "@mui/material";
import { Connection, Keypair, Transaction } from "@solana/web3.js";
import { useSnackbar } from "notistack";
import React, { useCallback, useState } from "react";
// Wormhole
import {
ChainId,
CHAIN_ID_BSC,
CHAIN_ID_ETH,
CHAIN_ID_SOLANA,
CHAIN_ID_TERRA,
createNonce,
// getBridgeFeeIx,
getEmitterAddressEth,
getEmitterAddressSolana,
getEmitterAddressTerra,
hexToNativeString,
ixFromRust,
parseSequenceFromLogEth,
parseSequenceFromLogSolana,
parseSequenceFromLogTerra,
} from "@certusone/wormhole-sdk";
import { uint8ArrayToNative } from "@certusone/wormhole-sdk/lib/esm";
import getSignedVAAWithRetry from "@certusone/wormhole-sdk/lib/esm/rpc/getSignedVAAWithRetry";
import { importCoreWasm } from "@certusone/wormhole-sdk/lib/esm/solana/wasm";
// Ethereum
import { hexlify, hexStripZeros } from "@ethersproject/bytes";
import { Web3Provider } from "@ethersproject/providers";
import { useEthereumProvider } from "./contexts/EthereumProviderContext";
import { Messenger__factory } from "./ethers-contracts";
// Terra
import { MsgExecuteContract, LCDClient } from "@terra-money/terra.js";
import { useTerraWallet } from "./contexts/TerraWalletContext";
// Solana
import { Connection, Keypair, Transaction } from "@solana/web3.js";
import { useSolanaWallet } from "./contexts/SolanaWalletContext";
// Deployed contract addresses.
import { address as ETH_CONTRACT_ADDRESS } from "./contract-addresses/development";
import { address as BSC_CONTRACT_ADDRESS } from "./contract-addresses/development2";
import { Messenger__factory } from "./ethers-contracts";
import { address as TERRA_CONTRACT_ADDRESS } from "./contract-addresses/terra";
interface ParsedVaa {
consistency_level: number;
@ -52,7 +68,7 @@ interface ParsedVaa {
version: number;
}
const SOLANA_BRIDGE_ADDRESS = "Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o";
//const SOLANA_BRIDGE_ADDRESS = "Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o";
const SOLANA_PROGRAM = require("./contract-addresses/solana.json").programId;
const SOLANA_HOST = "http://localhost:8899";
const WORMHOLE_RPC_HOSTS = ["http://localhost:7071"];
@ -63,10 +79,24 @@ const chainToNetwork = (c: ChainId) =>
hexStripZeros(hexlify(chainToNetworkDec(c)));
const chainToContract = (c: ChainId) =>
c === 2 ? ETH_CONTRACT_ADDRESS : c === 4 ? BSC_CONTRACT_ADDRESS : "";
c === 2
? ETH_CONTRACT_ADDRESS
: c === 4
? BSC_CONTRACT_ADDRESS
: c === 3
? TERRA_CONTRACT_ADDRESS // This is actually not used because terra has separate TerraChain handler.
: "";
const chainToName = (c: ChainId) =>
c === 1 ? "Solana" : c === 2 ? "Ethereum" : c === 4 ? "BSC" : "Unknown";
c === 1
? "Solana"
: c === 2
? "Ethereum"
: c === 4
? "BSC"
: c === 3
? "terra"
: "Unknown";
const MM_ERR_WITH_INFO_START =
"VM Exception while processing transaction: revert ";
@ -79,6 +109,7 @@ const parseError = (e: any) =>
? e.message
: "An unknown error occurred";
// This is metamask, Ethereum.
const switchProviderNetwork = async (
provider: Web3Provider,
chainId: ChainId
@ -224,11 +255,11 @@ function SolanaChain({
(async () => {
try {
const connection = new Connection(SOLANA_HOST, "confirmed");
const transferIx = await getBridgeFeeIx(
connection,
SOLANA_BRIDGE_ADDRESS,
publicKey.toString()
);
// const transferIx = await getBridgeFeeIx(
// connection,
// SOLANA_BRIDGE_ADDRESS,
// publicKey.toString()
// );
const { send_message_ix } = await import("wormhole-messenger-solana");
const messageKey = Keypair.generate();
const emitter = hexToNativeString(
@ -249,7 +280,8 @@ function SolanaChain({
new Uint8Array(Buffer.from(messageText))
)
);
const transaction = new Transaction().add(transferIx, ix);
// const transaction = new Transaction().add(transferIx, ix);
const transaction = new Transaction().add(ix);
const { blockhash } = await connection.getRecentBlockhash();
transaction.recentBlockhash = blockhash;
transaction.feePayer = publicKey;
@ -308,20 +340,145 @@ function SolanaChain({
);
}
function TerraChain({
name,
chainId,
addMessage,
}: {
name: string;
chainId: ChainId;
addMessage: (m: ParsedVaa) => void;
}) {
const {
// connect: terraConnect,
// disconnect: terraDisconnect,
connected: terraConnected,
wallet: terraWallet,
} = useTerraWallet();
const [messageText, setMessageText] = useState("");
const { enqueueSnackbar } = useSnackbar(); //closeSnackbar
const handleChange = useCallback((event) => {
setMessageText(event.target.value);
}, []);
const sendClickHandler = useCallback(() => {
if (!terraConnected) return;
(async () => {
try {
// Sending message to Wormhole and waiting for it to be signed.
// 1. Create and post string transaction.
const sendMsg = new MsgExecuteContract(
terraWallet.wallets[0].terraAddress,
TERRA_CONTRACT_ADDRESS,
{
SendMessage: {
nonce: 1,
text: messageText,
},
},
{}
);
const txResult = await terraWallet.post({
msgs: [sendMsg],
//memo: "???",
});
// 2. Wait for receipt.
const TERRA_HOST = {
URL: "http://localhost:1317",
chainID: "columbus-5",
name: "localterra",
};
const lcd = new LCDClient(TERRA_HOST);
let info;
while (!info) {
await new Promise((resolve) => setTimeout(resolve, 1000));
try {
info = await lcd.tx.txInfo(txResult.result.txhash);
} catch (e) {
console.error(e);
}
}
if (info.code !== undefined && info.code !== 0) {
// error code
throw new Error(
`Tx ${txResult.result.txhash}: error code ${info.code}: ${info.raw_log}`
);
}
const sequence = parseSequenceFromLogTerra(info);
console.log(sequence);
// 3. Retrieve signed VAA. For this chain and sequence.
const { vaaBytes } = await getSignedVAAWithRetry(
WORMHOLE_RPC_HOSTS,
chainId,
await getEmitterAddressTerra(TERRA_CONTRACT_ADDRESS),
sequence.toString()
);
// 4. Parse signed VAA and store it for display and use.
// VAA use example is in part2.
const { parse_vaa } = await importCoreWasm();
const parsedVaa = parse_vaa(vaaBytes);
console.log(parsedVaa);
addMessage(parsedVaa);
} catch (e) {
console.log("EXCEPTION in Send: " + e);
enqueueSnackbar("EXCEPTION in Send: " + parseError(e), {
persist: false,
});
}
})();
}, [
chainId,
messageText,
addMessage,
enqueueSnackbar,
terraConnected,
terraWallet,
]);
return (
<Chain
name={name}
value={messageText}
onChange={handleChange}
onClick={sendClickHandler}
disabled={!terraConnected}
/>
);
}
function App() {
const { connect, disconnect, signerAddress } = useEthereumProvider();
const {
wallet,
wallets,
select,
wallet: solanaWallet,
wallets: solanaWallets,
select: solanaSelect,
connect: connectSolanaWallet,
disconnect: disconnectSolanaWallet,
publicKey,
publicKey: solanaPublicKey,
} = useSolanaWallet();
const {
connect: terraConnect,
disconnect: terraDisconnect,
connected: terraConnected,
wallet: terraWallet,
} = useTerraWallet();
// const terraAddrStr = (terraWallet && terraWallet.walletAddress) || "";
const terraAddrStr =
(terraWallet &&
terraWallet.wallets &&
terraWallet.wallets.length > 0 &&
terraWallet.wallets[0].terraAddress) ||
"";
const [messages, setMessages] = useState<ParsedVaa[]>([]);
const addMessage = useCallback((message: ParsedVaa) => {
setMessages((arr) => [message, ...arr]);
}, []);
console.log(solanaPublicKey ? solanaPublicKey.toString() : "null");
return (
<Box my={2}>
<Typography variant="h4" component="h1" sx={{ textAlign: "center" }}>
@ -344,44 +501,72 @@ function App() {
Connect Metamask
</Button>
)}
{publicKey ? (
{solanaPublicKey ? (
<Button
variant="outlined"
color="inherit"
onClick={disconnectSolanaWallet}
sx={{ textTransform: "none", ml: 1 }}
>
{publicKey.toString().substr(0, 5)}
{solanaPublicKey.toString().substr(0, 5)}
...
{publicKey.toString().substr(publicKey.toString().length - 3)}
{solanaPublicKey
.toString()
.substr(solanaPublicKey.toString().length - 3)}
</Button>
) : wallet ? (
) : solanaWallet ? (
<Button
variant="contained"
color="secondary"
onClick={connectSolanaWallet}
sx={{ ml: 1 }}
>
Connect {wallet.name}
Connect {solanaWallet.name}
</Button>
) : (
wallets.map((wallet) => (
solanaWallets.map((wallet) => (
<Button
variant="contained"
color="secondary"
key={wallet.name}
onClick={() => {
select(wallet.name);
solanaSelect(wallet.name);
}}
sx={{ ml: 1 }}
>
Connect {wallet.name}
Select {wallet.name}
</Button>
))
)}
{terraConnected ? (
<Button
variant="outlined"
color="inherit"
onClick={terraDisconnect}
sx={{ ml: 1 }}
>
{terraAddrStr.substr(0, 5)}
...
{terraAddrStr.substr(terraAddrStr.toString().length - 3)}
</Button>
) : (
<Button
variant="contained"
color="secondary"
onClick={terraConnect}
sx={{ ml: 1 }}
>
Connect Terra
</Button>
)}
</Box>
<Box sx={{ display: "flex" }}>
<Box sx={{ flexBasis: "66%" }}>
<TerraChain
name="Terra"
chainId={CHAIN_ID_TERRA}
addMessage={addMessage}
/>
<SolanaChain
name="Solana"
chainId={CHAIN_ID_SOLANA}
@ -392,7 +577,11 @@ function App() {
chainId={CHAIN_ID_ETH}
addMessage={addMessage}
/>
<EVMChain name="BSC" chainId={CHAIN_ID_BSC} addMessage={addMessage} />
<EVMChain
name="BSC "
chainId={CHAIN_ID_BSC}
addMessage={addMessage}
/>
</Box>
<Box sx={{ flexGrow: 1, p: 2, pl: 0 }}>
<Card sx={{ width: "100%", height: "100%" }}>

View File

@ -0,0 +1,104 @@
import {
NetworkInfo,
Wallet,
WalletProvider,
useWallet,
} from "@terra-money/wallet-provider";
import React, {
ReactChildren,
useCallback,
useContext,
useMemo,
useState,
} from "react";
const mainnet = {
name: "mainnet",
chainID: "columbus-4",
lcd: "https://lcd.terra.dev",
};
const localnet = {
name: "localnet",
chainID: "localnet",
lcd: "http://localhost:1317",
};
const walletConnectChainIds: Record<number, NetworkInfo> = {
0: localnet,
1: mainnet,
};
interface ITerraWalletContext {
connect(): void;
disconnect(): void;
connected: boolean;
wallet: any;
}
const TerraWalletContext = React.createContext<ITerraWalletContext>({
connect: () => {},
disconnect: () => {},
connected: false,
wallet: null,
});
export const TerraWalletWrapper = ({
children,
}: {
children: ReactChildren;
}) => {
// TODO: Use wallet instead of useConnectedWallet.
const terraWallet = useWallet();
const [, setWallet] = useState<Wallet | undefined>(undefined);
const [connected, setConnected] = useState(false);
const connect = useCallback(() => {
const CHROME_EXTENSION = 1;
if (terraWallet) {
terraWallet.connect(terraWallet.availableConnectTypes[CHROME_EXTENSION]);
setWallet(terraWallet);
setConnected(true);
}
}, [terraWallet]);
const disconnect = useCallback(() => {
setConnected(false);
setWallet(undefined);
}, []);
const contextValue = useMemo(
() => ({
connect,
disconnect,
connected,
wallet: terraWallet,
}),
[connect, disconnect, connected, terraWallet]
);
return (
<TerraWalletContext.Provider value={contextValue}>
{children}
</TerraWalletContext.Provider>
);
};
export const TerraWalletProvider = ({
children,
}: {
children: ReactChildren;
}) => {
return (
<WalletProvider
defaultNetwork={localnet}
walletConnectChainIds={walletConnectChainIds}
>
<TerraWalletWrapper>{children}</TerraWalletWrapper>
</WalletProvider>
);
};
export const useTerraWallet = () => {
return useContext(TerraWalletContext);
};

View File

@ -5,6 +5,7 @@ import ReactDOM from "react-dom";
import App from "./App";
import { EthereumProviderProvider } from "./contexts/EthereumProviderContext";
import { SolanaWalletProvider } from "./contexts/SolanaWalletContext";
import { TerraWalletProvider } from "./contexts/TerraWalletContext.tsx";
import { SnackbarProvider } from "notistack";
const darkTheme = createTheme({
@ -21,7 +22,9 @@ ReactDOM.render(
<SnackbarProvider maxSnack={3}>
<SolanaWalletProvider>
<EthereumProviderProvider>
<App />
<TerraWalletProvider>
<App />
</TerraWalletProvider>
</EthereumProviderProvider>
</SolanaWalletProvider>
</SnackbarProvider>

View File

@ -4,13 +4,42 @@
Start a [minimal_devnet](../../../minimal_devnet/)
Deploy the EVM contracts
### Deploy the EVM contracts
```bash
cd ethereum
npm ci
npm run migrate
npm run register
npm run register -- Run this after all chain contracts are deployed.
```
### Deploy the Solana contracts and generate wasm (for UI)
```bash
cd solana
EMITTER_ADDRESS="11111111111111111111111111111115" cargo build-bpf
# if running the local (tilt) devnet
# if kubectl was not found - use "minikube kubectl -- " in place of "kubectl "
kubectl cp -c devnet target/deploy/messenger_solana.so solana-devnet-0:/usr/src/
kubectl cp -c devnet id.json solana-devnet-0:/usr/src/
# ^ this file needs to be copied only once per Tilt run.
kubectl exec -c devnet solana-devnet-0 -- solana program deploy -u l --output json -k /usr/src/id.json /usr/src/messenger_solana.so > ../ui/src/contract-addresses/solana.json
# else if running the minimal-devnet
solana program deploy -u l --output json -k id.json target/deploy/messenger_solana.so > ../ui/src/contract-addresses/solana.json
# Generate wasm for JS code (Tilt and minimal-devnet)
EMITTER_ADDRESS="11111111111111111111111111111115" wasm-pack build --target bundler -d bundler -- --features wasm
EMITTER_ADDRESS="11111111111111111111111111111115" wasm-pack build --target nodejs -d nodejs -- --features wasm
node scripts/register_chains.js -- Run this after all chain contracts are deployed.
```
BUG WORKAROUND FOR SOLANA:
And in messenger_solana_bg.js following code needs to commented out for now:
```
free() {
const ptr = this.__destroy_into_raw();
wasm.__wbg_systeminstruction_free(ptr);
}
```
Run the UI

View File

@ -5,6 +5,7 @@ pragma solidity ^0.8.0;
import "./interfaces/IWormhole.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
contract Messenger is Ownable {
// Hardcode the Wormhole Core Bridge contract address
@ -21,6 +22,22 @@ contract Messenger is Ownable {
return sequence;
}
function bytes32ToString(bytes32 _bytes32) public pure returns (string memory) {
uint8 i = 0;
bytes memory bytesArray = new bytes(64);
for (i = 0; i < bytesArray.length; i++) {
bytesArray[i] = toByte((_bytes32[i/2] >> 4) & 0x0f);
i = i + 1;
bytesArray[i] = toByte(_bytes32[i/2] & 0x0f);
}
return string(bytesArray);
}
function toByte(bytes1 _b1) public pure returns (bytes1) {
uint8 _b = uint8(_b1);
return (_b < 10)? bytes1(_b + 48): bytes1(_b + 87);
}
// receiveStr confirms VAA and processes message on the receiving chain.
// Returns true when bytes are seen first time.
function receiveBytes(bytes memory encodedVm, uint32 /*nonce*/) public {
@ -29,7 +46,12 @@ contract Messenger is Ownable {
require(valid, reason);
// 2. Check if emtter chain contract is registered.
require(verifyBridgeVM(vm), " invalid emitter"); // ??? Can I check emitter here ??
// Print incoming emitter address.
// string memory smsg = string(abi.encodePacked(" invalid em ", Strings.toString(vm.emitterChainId), "-", bytes32ToString(vm.emitterAddress)));
// Print map entry address.
string memory smsg = string(abi.encodePacked(" invalid_emitter ", Strings.toString(vm.emitterChainId), "+", bytes32ToString(_bridgeContracts[vm.emitterChainId])));
require(verifyBridgeVM(vm), smsg);
// 3. Drop duplicate VAA.
require(!_completedMessages[vm.hash], " message already received");
@ -41,8 +63,7 @@ contract Messenger is Ownable {
// Check if receiveBytes emmiter is actually registered chan.
function verifyBridgeVM(IWormhole.VM memory vm) internal view returns (bool){
if(_bridgeContracts[vm.emitterChainId] == vm.emitterAddress) return true;
return false;
return (_bridgeContracts[vm.emitterChainId] == vm.emitterAddress);
}
// We register chain,bridge in [mpn run register] command.
function registerChain(uint16 chainId_, bytes32 bridgeContract_) public onlyOwner {

File diff suppressed because it is too large Load Diff

View File

@ -17,11 +17,12 @@
"scripts": {
"build": "truffle compile",
"migrate": "truffle migrate && truffle migrate --network development2",
"register": "truffle exec scripts/register_bsc_chain.js && truffle exec scripts/register_eth_chain.js --network development2"
"register": "truffle exec scripts/register_eth_chain.js --network development && truffle exec scripts/register_eth_chain.js --network development2"
},
"author": "",
"license": "Apache-2.0",
"dependencies": {
"@solana/web3.js": "^1.36.0",
"elliptic": "^6.5.2",
"ganache-cli": "^6.12.1",
"jsonfile": "^4.0.0",

View File

@ -1,54 +0,0 @@
// run this script with truffle exec
const fss = require("fs");
const jsonfile = require("jsonfile");
const Messenger = artifacts.require("Messenger");
const MessengerFullABI = jsonfile.readFileSync(
"../build/contracts/Messenger.json"
).abi;
function prependTo32Bytes(str) {
var stripStr=(str.substring(0,2) === "0x")? str.substring(2): str;
while(stripStr.length < 64) {
stripStr = "00"+stripStr;
}
return "0x"+stripStr;
}
module.exports = async function(callback) {
try {
const ETH_CONTRACT_ADDRESS = fss.readFileSync('./scripts/contract-addresses/development').toString();
const BSC_CONTRACT_ADDRESS = fss.readFileSync('./scripts/contract-addresses/development2').toString();
const accounts = await web3.eth.getAccounts();
const initialized = new web3.eth.Contract(
MessengerFullABI,
Messenger.address
);
// Register the Eth endpoint on BSC
await initialized.methods
.registerChain(
2,
prependTo32Bytes(ETH_CONTRACT_ADDRESS)
)
.send({
value: 0,
from: accounts[0],
gasLimit: 2000000,
});
// Register the BSC endpoint on BSC
await initialized.methods
.registerChain(
4,
prependTo32Bytes(BSC_CONTRACT_ADDRESS)
)
.send({
value: 0,
from: accounts[0],
gasLimit: 2000000,
});
callback();
} catch (e) {
callback(e);
}
};

View File

@ -1,52 +1,81 @@
// run this script with truffle exec
// Eth is for port 8545
// Bsc is for port 8546 (devnet2)
const { PublicKey } = require("@solana/web3.js");
const jsonfile = require("jsonfile");
const fss = require("fs");
const bs58 = require("bs58");
const Messenger = artifacts.require("Messenger");
const MessengerFullABI = jsonfile.readFileSync(
"../build/contracts/Messenger.json"
).abi;
function prependTo32Bytes(str) {
if(str.substring(0,2) === "0x") {
return "0x000000000000000000000000" + str.substring(2);
} else {
return "0x000000000000000000000000" + str;
var stripStr = str.substring(0, 2) === "0x" ? str.substring(2) : str;
while (stripStr.length < 64) {
stripStr = "00" + stripStr;
}
return "0x" + stripStr;
}
module.exports = async function(callback) {
try {
const ETH_CONTRACT_ADDRESS =fss.readFileSync('./scripts/contract-addresses/development').toString();
const BSC_CONTRACT_ADDRESS = fss.readFileSync('./scripts/contract-addresses/development2').toString();
const ETH_CONTRACT_ADDRESS = fss
.readFileSync("./scripts/contract-addresses/development")
.toString();
const BSC_CONTRACT_ADDRESS = fss
.readFileSync("./scripts/contract-addresses/development2")
.toString();
// const SOLANA_CONTRACT_ADDRESS =
// "MK3nUKWkx4qRpLPXJzVa9zN4jZWpZE1syNnu8qQrupH"; // Messenger address from intro/part2/ui/src/contract-addresses/solana.json.
const SOLANA_CONTRACT_ADDRESS = require("../../ui/src/contract-addresses/solana.json")
.programId;
const accounts = await web3.eth.getAccounts();
const initialized = new web3.eth.Contract(
MessengerFullABI,
Messenger.address
MessengerFullABI,
Messenger.address
);
// Register the Eth endpoint on Eth
// code to derive solana emitter from SOLANA_CONTRACT_ADDRESS
// export const SOLANA_CONTRACT_ADDRESS = require("./contract-addresses/solana.json").programId;
const solanaProgramId = new PublicKey(SOLANA_CONTRACT_ADDRESS);
let [solanaEmitter, nonce] = await PublicKey.findProgramAddress(
[Buffer.from("emitter", "utf8")],
solanaProgramId
);
// Register the Eth endpoint
await initialized.methods
.registerChain(
2,
prependTo32Bytes(ETH_CONTRACT_ADDRESS)
)
.registerChain(2, prependTo32Bytes(ETH_CONTRACT_ADDRESS))
.send({
value: 0,
from: accounts[0],
gasLimit: 2000000,
});
// Register the BSC endpoint on Eth
await initialized.methods
.registerChain(
4,
prependTo32Bytes(BSC_CONTRACT_ADDRESS)
)
.send({
value: 0,
from: accounts[0],
gasLimit: 2000000,
});
// Register the BSC endpoint
await initialized.methods
.registerChain(4, prependTo32Bytes(BSC_CONTRACT_ADDRESS))
.send({
value: 0,
from: accounts[0],
gasLimit: 2000000,
});
// Register Terra. TBD chain=3
// Register Solana
console.log(
"Solana emitter: " +
bs58.encode(solanaEmitter.toBytes()) +
" or " +
solanaEmitter.toBuffer().toString("hex")
);
await initialized.methods.registerChain(1, solanaEmitter.toBuffer()).send({
value: 0,
from: accounts[0],
gasLimit: 2000000,
});
callback();
} catch (e) {

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,36 @@
[package]
name = "wormhole-messenger-solana"
version = "0.1.0"
edition = "2018"
[lib]
name = "messenger_solana"
crate-type = ["cdylib", "lib"]
[features]
no-entrypoint = []
wasm = ["no-entrypoint"]
[dependencies]
web-sys = "0.3.56"
borsh = "0.9.1"
sha3 = "0.9.1"
thiserror = "1.0.24"
byteorder = "1.4.3"
hex = "0.4.3"
solana-program = { version="=1.9.4" }
nom = { version="7", default-features=false, features=["alloc"] }
wasm-bindgen = { version = "0.2.74", features = ["serde-serialize"] }
wormhole-sdk = { git = "https://github.com/certusone/wormhole", features = ["devnet", "solana"] }
#wormhole-sdk = { path="../../../sdk/rust/sdk", features = ["devnet", "solana"] }
[dev-dependencies]
solana-program-test = "=1.9.4"
solana-sdk = "=1.9.4"
rand = "0.7.3"
[patch.crates-io]
memmap2 = { path = "memmap2-rs" }
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@ -0,0 +1,6 @@
[
14, 173, 153, 4, 176, 224, 201, 111, 32, 237, 183, 185, 159, 247, 22, 161, 89,
84, 215, 209, 212, 137, 10, 92, 157, 49, 29, 192, 101, 164, 152, 70, 87, 65,
8, 174, 214, 157, 175, 126, 98, 90, 54, 24, 100, 177, 247, 77, 19, 112, 47,
44, 165, 109, 233, 102, 14, 86, 109, 29, 134, 145, 132, 141
]

3734
intro/part2/solana/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,9 @@
{
"dependencies": {
"@certusone/wormhole-sdk": "^0.2.1",
"@solana/web3.js": "^1.36.0",
"bn.js": "^5.2.0",
"ethers": "^5.4.1",
"ts-node": "^10.7.0"
}
}

View File

@ -0,0 +1,133 @@
// run this script with ??
//const { PublicKey } = require("@solana/web3.js");
const {
Connection,
Keypair,
PublicKey,
Transaction,
} = require("@solana/web3.js");
const fs = require("fs");
const sdk = require("@certusone/wormhole-sdk");
const bs58 = require("bs58");
sdk.setDefaultWasm("node");
console.log("-- start -- ");
function prependTo32Bytes(str) {
var stripStr = str.substring(0, 2) === "0x" ? str.substring(2) : str;
while (stripStr.length < 64) {
stripStr = "00" + stripStr;
}
return stripStr;
}
function cut0x(str) {
const p = str.indexOf("0x");
return str.substring(p, str.length - 1);
}
(async () => {
try {
const ETH_CONTRACT_ADDRESS_STR = cut0x(
fs.readFileSync("../ui/src/contract-addresses/development.js").toString()
);
const BSC_CONTRACT_ADDRESS_STR = cut0x(
fs.readFileSync("../ui/src/contract-addresses/development2.js").toString()
);
const SOLANA_CONTRACT_ADDRESS =
require("../../ui/src/contract-addresses/solana.json").programId;
// This is emitter_address in VAA from ETH
// [00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 43, 39, 31, 6E, 04, CF, FB, 59, 61, D1, C4, 1F, EF, 8E, 44, BF, A2, A7, FB, D1]
// const EthProgramId = new PublicKey(ETH_CONTRACT_ADDRESS);
console.log("Eth Contract: " + ETH_CONTRACT_ADDRESS_STR);
console.log("Bsc Contract: " + BSC_CONTRACT_ADDRESS_STR);
console.log("Solana Contract: " + SOLANA_CONTRACT_ADDRESS);
const ethEmitter = sdk.getEmitterAddressEth(ETH_CONTRACT_ADDRESS_STR);
const bscEmitter = sdk.getEmitterAddressEth(BSC_CONTRACT_ADDRESS_STR);
const solanaEmitterPDAHex = await sdk.getEmitterAddressSolana(
SOLANA_CONTRACT_ADDRESS
);
console.log("EthDevEmitter: " + ethEmitter);
console.log("BscDevEmitter: " + bscEmitter);
console.log("SolDevEmitter: " + solanaEmitterPDAHex);
// create a keypair for Solana
const SOLANA_PRIVATE_KEY = new Uint8Array([
14, 173, 153, 4, 176, 224, 201, 111, 32, 237, 183, 185, 159, 247, 22, 161,
89, 84, 215, 209, 212, 137, 10, 92, 157, 49, 29, 192, 101, 164, 152, 70,
87, 65, 8, 174, 214, 157, 175, 126, 98, 90, 54, 24, 100, 177, 247, 77, 19,
112, 47, 44, 165, 109, 233, 102, 14, 86, 109, 29, 134, 145, 132, 141,
]);
const keypair = Keypair.fromSecretKey(SOLANA_PRIVATE_KEY);
const payerAddress = keypair.publicKey;
// Create register_chain instruction using wasm to rust.
const { register_chain_ix } = await require("../nodejs");
const connection = new Connection("http://localhost:8899", "confirmed");
// Register all chaind in solana contract.
await registerChainOnSolana(
SOLANA_CONTRACT_ADDRESS,
register_chain_ix,
payerAddress,
sdk.CHAIN_ID_ETH,
ethEmitter,
connection,
keypair
);
await registerChainOnSolana(
SOLANA_CONTRACT_ADDRESS,
register_chain_ix,
payerAddress,
sdk.CHAIN_ID_BSC,
bscEmitter,
connection,
keypair
);
await registerChainOnSolana(
SOLANA_CONTRACT_ADDRESS,
register_chain_ix,
payerAddress,
sdk.CHAIN_ID_SOLANA,
solanaEmitterPDAHex,
connection,
keypair
);
} catch (e) {
console.log("Caught Exception: " + e);
}
async function registerChainOnSolana(
solanaContractAddr,
register_chain_ix,
payerAddress,
chainId,
emitterAddress,
connection,
keypair
) {
const ix = sdk.ixFromRust(
register_chain_ix(
solanaContractAddr,
payerAddress.toString(),
chainId,
emitterAddress
)
);
// console.log("payerAddress: " + payerAddress);
// Create register_chain transaction and call it.
const transaction = new Transaction().add(ix);
const { blockhash } = await connection.getRecentBlockhash();
transaction.recentBlockhash = blockhash;
transaction.feePayer = payerAddress;
transaction.partialSign(keypair);
const txid = await connection.sendRawTransaction(transaction.serialize());
// console.log("txid: " + txid);
await connection.confirmTransaction(txid);
}
})();

View File

@ -0,0 +1,155 @@
use borsh::BorshSerialize;
use solana_program::instruction::{
AccountMeta,
Instruction,
};
use solana_program::pubkey::Pubkey;
use solana_program::system_program;
use solana_program::sysvar::{
clock,
rent,
};
//use solana_program::pubkey::Pubkey::find_program_address;
use wormhole_sdk::{VAA};
use wormhole_sdk::{
id,
config,
fee_collector,
sequence,
};
use sha3::Digest;
use byteorder::{
BigEndian,
WriteBytesExt,
};
use std::io::{
Cursor,
Write,
};
//use std::slice;
use crate::Instruction::{
SendMessage,
ConfirmMessage,
RegisterChain,
};
use web_sys::console;
/// Create a RegisterChain instruction.
pub fn register_chain(
program_id: Pubkey,
payer: Pubkey,
chain_id: u16,
emitter: String, // This is expected to be 32 byte as 64 char hex string.
) -> Instruction {
// Derive chainId -> emitter PDA.
let chain_id_bytes = chain_id.to_le_bytes();
let (emitter_address, _) = Pubkey::find_program_address(&[b"EmitterAddress", &chain_id_bytes], &program_id);
// ChainId,emitter go into data Payload.
let mut payload: Vec<u8> = chain_id_bytes.to_vec();
payload.extend(hex::decode(emitter).unwrap());
console::log_1(&format!("--- register_chain size: {}", payload.len()).into());
Instruction {
program_id,
accounts: vec![
AccountMeta::new(payer, true),
AccountMeta::new(emitter_address, false),
AccountMeta::new_readonly(system_program::id(), false),
AccountMeta::new_readonly(rent::id(), false),
AccountMeta::new_readonly(clock::id(), false),
],
data: RegisterChain(payload).try_to_vec().unwrap(),
}
}
/// Create a SendMessage instruction.
pub fn send_message(
program_id: Pubkey,
payer: Pubkey,
emitter: Pubkey,
message: Pubkey,
payload: Vec<u8>,
) -> Instruction {
let wormhole = id();
let config = config(&wormhole);
let fee_collector = fee_collector(&wormhole);
let sequence = sequence(&wormhole, &emitter);
Instruction {
program_id,
accounts: vec![
AccountMeta::new(payer, true),
AccountMeta::new_readonly(emitter, false),
AccountMeta::new(message, true),
AccountMeta::new(config, false),
AccountMeta::new(fee_collector, false),
AccountMeta::new(sequence, false),
AccountMeta::new_readonly(wormhole, false),
AccountMeta::new_readonly(system_program::id(), false),
AccountMeta::new_readonly(rent::id(), false),
AccountMeta::new_readonly(clock::id(), false),
],
data: SendMessage(payload).try_to_vec().unwrap(),
}
}
// Convert a full VAA structure into the serialization of its unique components, this structure is
// what is hashed and verified by Guardians.
fn serialize_vaa(vaa: &VAA) -> Vec<u8> {
let mut v = Cursor::new(Vec::new());
v.write_u32::<BigEndian>(vaa.timestamp).unwrap();
v.write_u32::<BigEndian>(vaa.nonce).unwrap();
v.write_u16::<BigEndian>(vaa.emitter_chain.clone() as u16).unwrap();
v.write(&vaa.emitter_address).unwrap();
v.write_u64::<BigEndian>(vaa.sequence).unwrap();
v.write_u8(vaa.consistency_level).unwrap();
v.write(&vaa.payload).unwrap();
v.into_inner()
}
/// Create a ConfirmMessage instruction.
/// Payload is signedVAA.
pub fn confirm_message(
program_id: Pubkey,
payer: Pubkey,
claim: Pubkey,
payload: Vec<u8>,
) -> Instruction {
let wormhole = wormhole_sdk::id();
let config = config(&wormhole);
let fee_collector = fee_collector(&wormhole);
// Hash a VAA extract (serialize_vaa function does it), to derive VAA key.
let vaa = VAA::from_bytes(payload.as_slice()).unwrap();
let bbf: Vec<u8> =serialize_vaa(&vaa);
console::log_1(&format!("--- Size1: {}", bbf.len()).into());
let mut h = sha3::Keccak256::default();
h.write(bbf.as_slice()).unwrap();
let vaa_hash: [u8; 32] = h.finalize().into();
let (vaa_key, _bump) = solana_program::pubkey::Pubkey::find_program_address(&["PostedVAA".as_bytes(), &vaa_hash], &wormhole);
console::log_1(&format!("-++> signedVAA: {}", vaa_key).into());
// Derive chainId -> emitter PDA.
let chain_id_bytes = (vaa.emitter_chain.clone() as u16).to_le_bytes();
let (emitter_address, _) = Pubkey::find_program_address(&[b"EmitterAddress", &chain_id_bytes], &program_id);
Instruction {
program_id,
accounts: vec![
AccountMeta::new(payer, true),
AccountMeta::new(config, false),
AccountMeta::new(claim, false),
AccountMeta::new_readonly(emitter_address, false),
AccountMeta::new(fee_collector, false),
AccountMeta::new_readonly(vaa_key, false),
AccountMeta::new_readonly(wormhole, false),
AccountMeta::new_readonly(system_program::id(), false),
AccountMeta::new_readonly(rent::id(), false),
AccountMeta::new_readonly(clock::id(), false),
],
data: ConfirmMessage.try_to_vec().unwrap(),
}
}

View File

@ -0,0 +1,290 @@
#![deny(unused_must_use)]
// A common serialization library used in the blockchain space, which we'll use to serialize our
// cross chain message payloads.
use borsh::{
BorshDeserialize,
BorshSerialize,
};
// Solana SDK imports to interact with the solana runtime.
use solana_program::msg;
use solana_program::account_info::{
next_account_info,
AccountInfo,
};
use solana_program::entrypoint::ProgramResult;
use solana_program::program::invoke_signed;
//use solana_program::pubkey::Pubkey::{find_program_address };
use solana_program::pubkey::Pubkey;
use solana_program::rent::Rent;
use solana_program::{
entrypoint,
system_instruction,
};
// Import Solana Wormhole SDK.
use wormhole_sdk::{
read_vaa,
ConsistencyLevel,
post_message, // SDK call.
id as bridge_id, // Get Bridge Id
};
#[cfg(feature = "wasm")]
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
extern crate wasm_bindgen;
#[cfg(feature = "wasm")]
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
pub mod wasm;
pub mod instruction;
// Define contract errors.
use thiserror::Error;
use solana_program::program_error::ProgramError;
#[derive(Error, Debug, Copy, Clone)]
pub enum MessengerError {
/// Invalid SignedVAA owner.
#[error("Invalid SignedVAA owner")]
InvalidSignedVAAOwner,
/// Unknown VAA emitter chain.
#[error("Unknown VAA emitter chain")]
UnknownVAAEmitterChain,
/// Invalid VAA Emitter.
#[error("Invalid VAA Emitter")]
InvalidVAAEmitter,
}
impl From<MessengerError> for ProgramError {
fn from(e: MessengerError) -> Self {
ProgramError::Custom(e as u32)
}
}
#[derive(BorshSerialize, BorshDeserialize, Clone)]
pub enum Instruction {
/// This instruction is used to send a message to another chain by emitting it as a wormhole message
/// 0: Payer [Signer]
/// 1: Emitter [Signer]
/// 2: Message [PDA]
/// 3: Worm Config [Worm PDA]
/// 4: Worm Fee [Worm PDA]
/// 5: Worm Sequence [Worm PDA]
/// 6: Wormhole [Program] -- Needed for invoke_signed.
/// 7: System [Program] -- Needed for wormhole to take fees.
/// 8: Rent [Program] -- Needed for fee calculation on the message account.
/// 9: Clock [Program] -- Needed for message generation
SendMessage(Vec<u8>),
/// ConfirmMessage instruction is used to check validity of signed VAA, check that it was not processed yet and Process it.
/// ConfirmMessage instruction checks that VAAs were emitted from known address.
/// 0: Payer [Signer]
/// 2: Worm Config [Worm PDA]
/// 3: Claim [this program PDA]
/// 4: Chain [this program PDA] Derived from chainId, 32bytes data.
/// 5: Worm Fee [Worm PDA]
/// 6: Message [Worm PDA] ???
/// 7: Wormhole [Program] -- Needed for invoke_signed.
/// 8: System [Program] -- Needed for wormhole to take fees.
/// 9: Rent [Program] -- Needed for fee calculation on the message account.
/// 10: Clock [Program] -- Needed for message generation
ConfirmMessage,
/// RegisterChain instruction is used to create or update emitter address for chainId data.
/// 0: Payer [Signer]
/// 1: Chain [this program PDA] Derived from chainId, 32bytes data.
/// 2: System [Program] -- Needed for wormhole to take fees.
/// 3: Rent [Program] -- Needed for fee calculation on the message account.
/// 4: Clock [Program] -- Needed for message generation
RegisterChain(Vec<u8>),
}
entrypoint!(process_instruction);
/// The Solana entrypoint, here we deserialize our Borsh encoded Instruction and dispatch to our
/// program handlers.
pub fn process_instruction(id: &Pubkey, accs: &[AccountInfo], data: &[u8]) -> ProgramResult {
match BorshDeserialize::try_from_slice(data).unwrap() {
Instruction::SendMessage(msg) => send_message(id, accs, msg),
Instruction::ConfirmMessage => confirm_message(id, accs),
Instruction::RegisterChain(msg) => register_chain(id, accs, msg),
}?;
Ok(())
}
#[derive(BorshSerialize, BorshDeserialize, Debug)]
struct ChainEmitter {
chain_id: u16,
emitter_addr: [u8;32],
}
/// Regiter chai emitter address
fn register_chain(id: &Pubkey, accs: &[AccountInfo], payload: Vec<u8>) -> ProgramResult {
let accounts = &mut accs.iter();
let payer = next_account_info(accounts)?;
let chain = next_account_info(accounts)?;
let system = next_account_info(accounts)?;
let chain_info: ChainEmitter = ChainEmitter::try_from_slice(&payload).unwrap();
msg!("bblp 0. chain: {}", chain.key);
msg!("bblp 1. register_chain, data len: {} id: {} addr: {:?}", payload.len(), chain_info.chain_id, chain_info.emitter_addr);
if **chain.lamports.borrow() != 0 {
msg!("bblp Chain PDA already exist");
}
else
{
// Create the data account for new chain.
let emitter_chain = &*chain_info.chain_id.to_le_bytes().to_vec();
let mut chain_seeds: Vec<&[u8]> = vec![b"EmitterAddress", emitter_chain ];
let (chain_key, chain_bump) = Pubkey::find_program_address(&chain_seeds, &id);
let cb = &[chain_bump];
chain_seeds.push(cb);
let chain_emitter_size = 32; // put size if there is state stored in claim.
msg!("bblp Create Chain PDA key: {}", chain_key);
// Create Clain will fail if it is alredy there.
let chain_ix = system_instruction::create_account(
payer.key,
&chain_key,
Rent::default().minimum_balance(chain_emitter_size),
chain_emitter_size as u64,
id
);
invoke_signed(&chain_ix,
&[
payer.clone(),
chain.clone(),
system.clone(),
],
&[&chain_seeds])?;
}
if chain.is_writable {
// data: Rc<RefCell<&'a mut [u8]>>,
msg!("bblp data[31] was: {}", chain.data.borrow()[31]);
chain.data.borrow_mut().copy_from_slice(&chain_info.emitter_addr[..]);
msg!("bblp data[31] became: {}", chain.data.borrow()[31]);
} else {
msg!("bblp Chain PDA is not wriable!");
}
Ok(())
}
/// Sends a message from this chain to wormhole.
fn send_message(id: &Pubkey, accs: &[AccountInfo], payload: Vec<u8>) -> ProgramResult {
let accounts = &mut accs.iter();
let payer = next_account_info(accounts)?;
let _emitter = next_account_info(accounts)?;
let message = next_account_info(accounts)?;
let _config = next_account_info(accounts)?;
let _fee_collector = next_account_info(accounts)?;
let _sequence = next_account_info(accounts)?;
let _wormhole = next_account_info(accounts)?;
let _system = next_account_info(accounts)?;
let _rent = next_account_info(accounts)?;
let _clock = next_account_info(accounts)?;
// Use SDK:
// Extract seeds for emitter account only if needed to pass to post_message
// let (emitter, mut seeds, bump) = wormhole_sdk::emitter(id);
// let bump = &[bump];
// seeds.push(bump);
msg!("bblp Processing send_message");
post_message(
*id,
*payer.key,
*message.key,
payload,
ConsistencyLevel::Confirmed,
None, //Some(&seeds), // If needed.
accs,
0
)?;
Ok(())
}
/// Checks wormhole message to be correct and signed.
/// payload is signed VAA bytes. It is not used here because it needs to be re - checked.
/// Instead we can use payload from signed_vaa. It is what was signed by Core Bridge and can not be spoofed.
fn confirm_message(id: &Pubkey, accs: &[AccountInfo]) -> ProgramResult {
// Read remaining unreferenced accounts.
let accounts = &mut accs.iter();
let payer = next_account_info(accounts)?;
let _config = next_account_info(accounts)?;
let claim = next_account_info(accounts)?;
let chain = next_account_info(accounts)?;
let _fee_collector = next_account_info(accounts)?;
let signed_vaa = next_account_info(accounts)?;
let _wormhole = next_account_info(accounts)?;
let system = next_account_info(accounts)?;
let _rent = next_account_info(accounts)?;
let _clock = next_account_info(accounts)?;
// Need to check that SignedVAA is owned by Core Bridge.
if *signed_vaa.owner != bridge_id() {
return Err(MessengerError::InvalidSignedVAAOwner.into());
}
// Deserialize vaa_data data into PostedVAAData.
let vaa: wormhole_sdk::PostedVAAData = read_vaa(signed_vaa).unwrap();
msg!("bblp vaa.sequence: {}", vaa.sequence);
msg!("bblp signed_vaa key: {}", signed_vaa.key);
msg!("bblp emitter address: {:02X?}", vaa.emitter_address);
// Check if chain is registered and register emitter is same as VAA emitter,
if **chain.lamports.borrow() == 0 {
return Err(MessengerError::UnknownVAAEmitterChain.into());
}
if chain.data.borrow()[..] != vaa.emitter_address[..] {
// data: Rc<RefCell<&'a mut [u8]>>,
msg!("bblp registered emitter address: {:02X?}", chain.data.borrow());
return Err(MessengerError::InvalidVAAEmitter.into());
}
// Need to create Claim PDA. Owned by this program.
// This needs to include at least emitter_address, emitter_chain, sequence.
let claim_size = 0; // put size if there is state stored in claim.
let emitter_address = &*vaa.emitter_address.to_vec();
let emitter_chain = &*(vaa.emitter_chain as u16).to_be_bytes().to_vec();
let sequence = &*vaa.sequence.to_be_bytes().to_vec();
let mut claim_seeds: Vec<&[u8]> = vec![
emitter_address,
emitter_chain,
sequence,
];
let (claim_key, claim_bump) = Pubkey::find_program_address(&claim_seeds, &id);
let cb = &[claim_bump];
claim_seeds.push(cb);
msg!("bblp Claim pda key: {}", claim_key);
// Create Clain will fail if it is alredy there.
let claim_ix = system_instruction::create_account(
payer.key,
&claim_key,
Rent::default().minimum_balance(claim_size),
claim_size as u64,
id
);
invoke_signed(&claim_ix,
&[
payer.clone(),
claim.clone(),
system.clone(),
],
&[&claim_seeds])?;
// Print the message.
msg!("bblp ProcessMessage: {}", std::str::from_utf8(&vaa.payload).unwrap());
Ok(())
}

View File

@ -0,0 +1,64 @@
use solana_program::pubkey::Pubkey;
use std::str::FromStr;
use crate::instruction::{
send_message,
confirm_message,
register_chain,
};
use wasm_bindgen::prelude::*;
/// Create a RegisterChain instruction.
#[wasm_bindgen]
pub fn register_chain_ix(
program_id: String,
payer: String,
chain_id: u16,
emitter: String, // This is expected to be 32 byte as 64 char hex string.
) -> JsValue {
let ix = register_chain(
Pubkey::from_str(program_id.as_str()).unwrap(),
Pubkey::from_str(payer.as_str()).unwrap(),
chain_id,
emitter,
);
return JsValue::from_serde(&ix).unwrap();
}
/// Create a SendMessage instruction.
#[wasm_bindgen]
pub fn send_message_ix(
program_id: String,
payer: String,
emitter: String,
message: String,
payload: Vec<u8>,
) -> JsValue {
let ix = send_message(
Pubkey::from_str(program_id.as_str()).unwrap(),
Pubkey::from_str(payer.as_str()).unwrap(),
Pubkey::from_str(emitter.as_str()).unwrap(),
Pubkey::from_str(message.as_str()).unwrap(),
payload,
);
return JsValue::from_serde(&ix).unwrap();
}
/// Create a ConfirmMessage instruction.
#[wasm_bindgen]
pub fn confirm_message_ix(
program_id: String,
payer: String,
claim: String,
payload: Vec<u8>,
) -> JsValue {
let ix = confirm_message(
Pubkey::from_str(program_id.as_str()).unwrap(),
Pubkey::from_str(payer.as_str()).unwrap(),
Pubkey::from_str(claim.as_str()).unwrap(),
payload,
);
return JsValue::from_serde(&ix).unwrap();
}

View File

@ -0,0 +1,3 @@
target
tools/node_modules
tools/dist

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,6 @@
[workspace]
members = ["contracts/cw20-wrapped", "contracts/wormhole", "contracts/token-bridge", "contracts/pyth-bridge"]
members = ["contracts/messenger"]
exclude = ["sdk-copy"]
[profile.release]
opt-level = 3
@ -13,4 +14,4 @@ incremental = false
overflow-checks = true
[patch.crates-io]
memmap2 = { git = "https://github.com/certusone/wormhole", package = "memmap2" }
memmap2 = { git = "https://github.com/certusone/wormhole", package = "memmap2" }

6
intro/part2/terra/build.sh Executable file
View File

@ -0,0 +1,6 @@
#!/usr/bin/env bash
docker run --rm -v "$(pwd)":/code \
--mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \
--mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
cosmwasm/workspace-optimizer:0.12.1

View File

@ -0,0 +1,29 @@
[package]
name = "wormhole-messenger-terra"
version = "0.0.1"
edition = "2018"
exclude = [
# Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication.
"sdk-copy",
]
[lib]
crate-type = ["cdylib", "rlib"]
[features]
library = []
backtraces = ["cosmwasm-std/backtraces"]
[dependencies]
borsh = "*"
cosmwasm-std = { version = "0.16.0" }
cosmwasm-storage = { version = "0.16.0" }
schemars = "0.8.1"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
#wormhole-sdk = { path = "../../../sdk/rust/sdk", features = ["terra", "devnet"] }
wormhole-sdk = { git = "https://github.com/certusone/wormhole", branch="fix/sdk/terra", features = ["terra", "devnet"] }
[dev-dependencies]
cosmwasm-vm = { version = "0.16.0", default-features = false }
serde_json = "1.0"

View File

@ -0,0 +1,215 @@
use borsh::{
BorshDeserialize,
BorshSerialize,
};
use cosmwasm_std::{
entry_point,
DepsMut,
Env,
MessageInfo,
Response,
StdError,
StdResult,
Binary,
};
use wormhole_sdk::{
parse_vaa,
post_message,
VAA,
};
#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, PartialEq)]
//#[derive(Clone, Debug, PartialEq)]
struct Message {
/// Message text to be output on the target networks node logs.
pub text: String,
}
mod messages;
use messages::*;
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
_deps: DepsMut,
_env: Env,
_info: MessageInfo,
msg: InstantiateMsg,
) -> StdResult<Response> {
Ok(Response::default().add_attribute("version", msg.version))
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult<Response> {
match msg {
// Emit a new message to be picked and signed by wormhole guardians.
ExecuteMsg::SendMessage { nonce, text } => Ok(Response::default()
.add_attribute("action", "send_message")
.add_message(post_message(
nonce,
// &Message { text }
// .try_to_vec()
// &Binary::from(text.as_bytes()) //.as_bytes()
text.as_bytes()
)?)),
// Validate and Process VAA containing a message, typically from another chain.
ExecuteMsg::RecvMessage { vaa } => {
// Parse VAA and decode Payload into message.
let vaa = parse_vaa(deps, env, &vaa)?;
let msg = Message::try_from_slice(&vaa.payload)
.map_err(|_| StdError::generic_err("Invalid payload Message"))?;
Ok(Response::default()
.add_attribute("action", "receive_message")
.add_attribute("text", msg.text))
}
}
}
#[cfg(test)]
mod testing {
use borsh::{
BorshDeserialize,
BorshSerialize,
};
use cosmwasm_std::testing::{
mock_dependencies,
mock_env,
mock_info,
};
use cosmwasm_std::{
Attribute,
Binary,
CosmosMsg,
SubMsg,
WasmMsg,
};
use super::{
execute,
instantiate,
ExecuteMsg,
InstantiateMsg,
};
#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, PartialEq)]
struct Message {
/// Message text to be output on the target networks node logs.
pub text: String,
}
#[test]
fn test_send_message() {
// Test Messages
let instantiate_msg = InstantiateMsg {
version: "1.0.0".to_string(),
};
let send_msg = ExecuteMsg::SendMessage {
nonce: 0,
text: "aaaaaaaaaa".to_string(),
};
// Instantiate Contract
let mut deps = mock_dependencies(&[]);
let env = mock_env();
let info = mock_info("addr0000", &[]);
instantiate(deps.as_mut(), env, info, instantiate_msg).unwrap();
// Send a Message
let info = mock_info("addr0000", &[]);
let result = execute(deps.as_mut(), mock_env(), info, send_msg).unwrap();
// Contract should have emitted a Msg targetting the wormhole contract.
assert_eq!(
result.messages,
vec![SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: wormhole_sdk::id().to_string(),
funds: vec![],
msg: Binary::from(&[
123, 34, 112, 111, 115, 116, 95, 109, 101, 115,
115, 97, 103, 101, 34, 58, 123, 34, 109, 101, 115,
115, 97, 103, 101, 34, 58, 34, 87, 122, 69, 120, 76,
68, 65, 115, 77, 67, 119, 119, 76, 68, 99, 121, 76,
68, 69, 119, 77, 83, 119, 120, 77, 68, 103, 115, 77,
84, 65, 52, 76, 68, 69, 120, 77, 83, 119, 122, 77,
105, 119, 50, 78, 83, 119, 120, 77, 68, 103, 115, 77,
84, 65, 49, 76, 68, 107, 53, 76, 68, 69, 119, 77, 86,
48, 61, 34, 44, 34, 110, 111, 110, 99, 101, 34, 58, 48, 125, 125
]),
}))]
);
}
/*
#[test]
fn test_recv_message() {
// Test Messages
let instantiate_msg = InstantiateMsg {
version: "1.0.0".to_string(),
};
// Submit a pre-encoded Message VAA.
let recv_msg = ExecuteMsg::RecvMessage {
vaa: Binary::from(&[
0x01, // Version
0x00, 0x00, 0x00, 0x00, // Guardian Set Index
0x01, // Guardian Signature Len
0x00, // Guardian 0.
0xb0, // Recovery 0.
// Signature 0
0x72, 0x50, 0x5b, 0x5b, 0x99, 0x9c, 0x1d, 0x08, 0x90, 0x5c, 0x02, 0xe2, 0xb6, 0xb2,
0x83, 0x2e, 0xf7, 0x2c, 0x0b, 0xa6, 0xc8, 0xdb, 0x4f, 0x77, 0xfe, 0x45, 0x7e, 0xf2,
0xb3, 0xd0, 0x53, 0x41, 0x0b, 0x1e, 0x92, 0xa9, 0x19, 0x4d, 0x92, 0x10, 0xdf, 0x24,
0xd9, 0x87, 0xac, 0x83, 0xd7, 0xb6, 0xf0, 0xc2, 0x1c, 0xe9, 0x0f, 0x8b, 0xc1, 0x86,
0x9d, 0xe0, 0x89, 0x8b, 0xda, 0x7e, 0x98, 0x01, 0x00, 0x00, 0x00,
0x01, // Timestamp
0x00, 0x00, 0x00, 0x01, // Nonce
0x00, 0x01, // Chain Solana
// Emitter Solana
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x3c, 0x1b,
0xfa, // Sequence
0x00, // Consistency
// Payload
9, 0, 0, 0, 72, 101, 108, 108, 111, 32, 66, 111, 98,
]),
};
use borsh::BorshSerialize;
println!(
"{:?}",
Message {
text: "Hello Bob".to_string(),
}
.try_to_vec()
);
// Instantiate Contract
let mut deps = mock_dependencies(&[]);
let env = mock_env();
let info = mock_info("addr0000", &[]);
instantiate(deps.as_mut(), env, info, instantiate_msg).unwrap();
// Receive a Message
let info = mock_info("addr0000", &[]);
let result = execute(deps.as_mut(), mock_env(), info, recv_msg).unwrap(); // Fails on empty querier mock.
assert_eq!(
result.attributes,
vec![
Attribute {
key: "action".to_string(),
value: "receive_message".to_string(),
},
Attribute {
key: "text".to_string(),
value: "Hello Bob".to_string(),
}
]
);
}
*/
}

View File

@ -0,0 +1,30 @@
//! CosmWasm defines Message types to its various entrypoints. These are JSON encoded and decoded
//! by the runtime before being passed into the various entrypoints.
use cosmwasm_std::Binary;
use schemars::JsonSchema;
use serde::{
Deserialize,
Serialize,
};
/// InstantiateMsg is passed into the contract initialiser when the contract is first deployed,
/// this is a one off message.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InstantiateMsg {
pub version: String,
}
/// ExecuteMsg is passed into the execute contract handler whenever a user submits a transaction
/// targetting our contract, this is our "main" entrypoint.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub enum ExecuteMsg {
RecvMessage {
vaa: Binary,
},
SendMessage {
nonce: u32,
text: String,
},
}

140
intro/part2/terra/deploy.js Normal file
View File

@ -0,0 +1,140 @@
import { Wallet, LCDClient, MnemonicKey } from "@terra-money/terra.js";
import {
StdFee,
MsgInstantiateContract,
MsgExecuteContract,
MsgStoreCode,
} from "@terra-money/terra.js";
import { readFileSync, readdirSync, writeFile } from "fs";
// TODO: Workaround /tx/estimate_fee errors.
const gas_prices = {
uluna: "0.15",
usdr: "0.1018",
uusd: "0.15",
ukrw: "178.05",
umnt: "431.6259",
ueur: "0.125",
ucny: "0.97",
ujpy: "16",
ugbp: "0.11",
uinr: "11",
ucad: "0.19",
uchf: "0.13",
uaud: "0.19",
usgd: "0.2",
};
async function main() {
const terra = new LCDClient({
URL: "http://localhost:1317",
chainID: "localterra",
});
const wallet = terra.wallet(
new MnemonicKey({
mnemonic:
"notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius",
})
);
await wallet.sequence();
// Deploy WASM blobs.
// Read a list of files from directory containing compiled contracts.
const artifacts = readdirSync("../artifacts/");
// Sort them to get a determinstic list for consecutive code ids.
artifacts.sort();
artifacts.reverse();
console.log(artifacts);
const hardcodedGas = {
"wormhole_messenger_terra.wasm": 4000000,
};
const codeIds = {};
const addresses = {};
// Deploy found WASM files and assign Code IDs.
for (const artifact in artifacts) {
if (
artifacts.hasOwnProperty(artifact) &&
artifacts[artifact].includes(".wasm")
) {
const file = artifacts[artifact];
const contract_bytes = readFileSync(`../artifacts/${file}`);
console.log(`Storing WASM: ${file} (${contract_bytes.length} bytes)`);
const store_code = new MsgStoreCode(
wallet.key.accAddress,
contract_bytes.toString("base64")
);
try {
const tx = await wallet.createAndSignTx({
msgs: [store_code],
memo: "",
fee: new StdFee(hardcodedGas[artifacts[artifact]], {
uluna: "100000",
}),
});
console.log("step1 done");
const rs = await terra.tx.broadcast(tx);
const ci = /"code_id","value":"([^"]+)/gm.exec(rs.raw_log)[1];
codeIds[file] = parseInt(ci);
} catch (e) {
console.log("Failed to Execute" + e);
}
}
}
console.log(codeIds);
// Instantiate messenger
console.log("Instantiating messenger");
await wallet
.createAndSignTx({
msgs: [
new MsgInstantiateContract(
wallet.key.accAddress,
wallet.key.accAddress,
codeIds["wormhole_messenger_terra.wasm"],
{
// TBD...
version: "1.0.0",
}
),
],
memo: "",
})
.then((tx) => terra.tx.broadcast(tx))
.then((rs) => {
const address = /"contract_address","value":"([^"]+)/gm.exec(
rs.raw_log
)[1];
addresses["wormhole_messenger_terra.wasm"] = address;
});
console.log(addresses);
// store contract address.
writeFile(
"/address/terra.js",
'export const address = "' +
addresses["wormhole_messenger_terra.wasm"] +
'"',
(err) => {
if (err) {
console.error(err);
}
}
);
}
main();

View File

@ -0,0 +1,18 @@
# Wait for node to start
echo "waiting for node 26657"
while ! /bin/netcat -zv localhost 26657; do
sleep 1
done
# Wait for first block
echo "waiting for block"
while [ $(curl localhost:26657/status -ks | jq ".result.sync_info.latest_block_height|tonumber") -lt 1 ]; do
sleep 1
done
echo "Going to sleep 2s"
sleep 2
npm ci && node deploy.js
echo "Got back from deploy.js"
#sleep infinity

View File

@ -0,0 +1,29 @@
# This is a multi-stage docker file
# 1. builds contract
# 2. creates node.js environment to deploy them
# Tilt: run following commands:
# docker build -t ctr .
# docker run -it --network=host -v "$(dirname `pwd`)/ui/src/contract-addresses":/address ctr
# Build Contract
FROM cosmwasm/workspace-optimizer:0.12.1@sha256:1508cf7545f4b656ecafa34e29c1acf200cdab47fced85c2bc076c0c158b1338 AS builder
ADD Cargo.lock /code/
ADD Cargo.toml /code/
#ADD sdk-copy /
ADD contracts /code/contracts
RUN optimize_workspace.sh
# Deploy Contract (Tilt version)
FROM node:16-buster-slim@sha256:93c9fc3550f5f7d159f282027228e90e3a7f8bf38544758024f005e82607f546
RUN apt update && apt install netcat curl jq -y
WORKDIR /app/tools
COPY --from=builder /code/artifacts /app/artifacts
ADD deploy.js /app/tools
ADD deploy.sh /app/tools
ADD package.json /app/tools
ADD package-lock.json /app/tools
RUN chmod +x /app/tools/deploy.sh
ENTRYPOINT /app/tools/deploy.sh

1042
intro/part2/terra/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,15 @@
{
"name": "tools",
"version": "1.0.0",
"description": "",
"main": "deploy.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"@terra-money/terra.js": "^2.0.11"
}
}

View File

@ -16,7 +16,7 @@ module.exports = {
const wasmLoader = {
test: /\.wasm$/,
exclude: /node_modules/,
include: /node_modules\/wormhole-messenger-solana/,
loaders: ["wasm-loader"],
};

File diff suppressed because it is too large Load Diff

View File

@ -3,12 +3,20 @@
"version": "0.0.1",
"private": true,
"dependencies": {
"@certusone/wormhole-sdk": "^0.1.4",
"@craco/craco": "^6.4.3",
"@certusone/wormhole-sdk": "^0.2.0",
"@emotion/react": "^11.7.1",
"@emotion/styled": "^11.6.0",
"@metamask/detect-provider": "^1.2.0",
"@mui/icons-material": "^5.2.5",
"@mui/material": "^5.2.4",
"@solana/wallet-adapter-base": "^0.5.2",
"@solana/wallet-adapter-phantom": "^0.5.3",
"@solana/wallet-adapter-react": "^0.11.0",
"@solana/wallet-adapter-sollet": "^0.6.0",
"@solana/wallet-adapter-wallets": "^0.9.0",
"@solana/wallet-base": "^0.0.1",
"@solana/web3.js": "^1.31.0",
"@terra-money/wallet-provider": "^3.8.0",
"@testing-library/jest-dom": "^5.16.1",
"@testing-library/react": "^11.2.7",
"@testing-library/user-event": "^12.8.3",
@ -18,7 +26,8 @@
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-scripts": "4.0.3",
"web-vitals": "^1.1.2"
"web-vitals": "^1.1.2",
"wormhole-messenger-solana": "file:../solana/bundler"
},
"scripts": {
"start": "craco start",
@ -46,7 +55,9 @@
]
},
"devDependencies": {
"@craco/craco": "^6.2.0",
"@types/react": "^17.0.37",
"typechain": "^6.0.5"
"typechain": "^6.0.5",
"wasm-loader": "^1.3.0"
}
}

View File

@ -1,36 +1,47 @@
import {
ChainId,
CHAIN_ID_BSC,
CHAIN_ID_ETH,
createNonce,
getEmitterAddressEth,
parseSequenceFromLogEth,
} from "@certusone/wormhole-sdk";
import { uint8ArrayToNative } from "@certusone/wormhole-sdk/lib/esm";
import getSignedVAAWithRetry from "@certusone/wormhole-sdk/lib/esm/rpc/getSignedVAAWithRetry";
import { importCoreWasm } from "@certusone/wormhole-sdk/lib/esm/solana/wasm";
import { hexlify, hexStripZeros } from "@ethersproject/bytes";
import { Web3Provider } from "@ethersproject/providers";
import React, { useCallback, useState } from "react";
import {
Box,
Button,
Card,
CardActions,
CardContent,
CardHeader,
List,
ListItem,
ListItemButton,
ListItemText,
TextField,
Typography,
} from "@mui/material";
import React, { useCallback, useState } from "react";
} from "@mui/material";
// Wormhole
import {
ChainId,
CHAIN_ID_BSC,
CHAIN_ID_ETH,
CHAIN_ID_SOLANA,
CHAIN_ID_TERRA,
} from "@certusone/wormhole-sdk";
import { uint8ArrayToNative } from "@certusone/wormhole-sdk/lib/esm";
// Ethereum
import { hexlify, hexStripZeros } from "@ethersproject/bytes";
import { Web3Provider } from "@ethersproject/providers";
import { useEthereumProvider } from "./contexts/EthereumProviderContext";
// Terra
import { useTerraWallet } from "./contexts/TerraWalletContext";
// Solana
import { useSolanaWallet } from "./contexts/SolanaWalletContext";
// Deployed contract addresses.
import { address as ETH_CONTRACT_ADDRESS } from "./contract-addresses/development";
import { address as BSC_CONTRACT_ADDRESS } from "./contract-addresses/development2";
import { useEthereumProvider } from "./EthereumProviderContext";
import { Messenger__factory } from "./ethers-contracts";
import { useSnackbar } from 'notistack';
import { address as TERRA_CONTRACT_ADDRESS } from "./contract-addresses/terra";
import { EVMChain } from "./EVMChain";
import { SolanaChain } from "./SolanaChain";
import { TerraChain } from "./TerraChain";
export const SOLANA_PROGRAM =
require("./contract-addresses/solana.json").programId;
interface ParsedVaa {
consistency_level: number;
@ -44,32 +55,43 @@ interface ParsedVaa {
timestamp: number;
version: number;
}
interface SentVaaData {
export interface SentVaaData {
vaa: ParsedVaa;
bytes: Uint8Array;
}
const WORMHOLE_RPC_HOSTS = ["http://localhost:7071"];
//const SOLANA_BRIDGE_ADDRESS = "Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o";
export const SOLANA_HOST = "http://localhost:8899";
export const WORMHOLE_RPC_HOSTS = ["http://localhost:7071"];
const chainToNetworkDec = (c: ChainId) =>
c === 2 ? 1337 : c === 4 ? 1397 : 0;
const chainToNetworkDec = (c: ChainId) => (c === 2 ? 1337 : c === 4 ? 1397 : 0);
const chainToNetwork = (c: ChainId) =>
hexStripZeros(hexlify(chainToNetworkDec(c)));
const chainToContract = (c: ChainId) =>
c === 2 ? ETH_CONTRACT_ADDRESS : c === 4 ? BSC_CONTRACT_ADDRESS : "";
export const chainToContract = (c: ChainId) =>
c === 2
? ETH_CONTRACT_ADDRESS
: c === 4
? BSC_CONTRACT_ADDRESS
: c === 3
? TERRA_CONTRACT_ADDRESS // This is actually not used because terra has separate TerraChain handler.
: "";
const chainToName = (c: ChainId) =>
c === 2
c === 1
? "Solana"
: c === 2
? "Ethereum"
: c === 4
? "BSC"
: c === 3
? "terra"
: "Unknown";
const MM_ERR_WITH_INFO_START =
"VM Exception while processing transaction: revert ";
const parseError = (e: any) =>
export const parseError = (e: any) =>
e?.data?.message?.startsWith(MM_ERR_WITH_INFO_START)
? e.data.message.replace(MM_ERR_WITH_INFO_START, "")
: e?.response?.data?.error // terra error
@ -78,159 +100,50 @@ const parseError = (e: any) =>
? e.message
: "An unknown error occurred";
const switchProviderNetwork = async(provider: Web3Provider, chainId: ChainId) => {
export const switchProviderNetwork = async (
provider: Web3Provider,
chainId: ChainId
) => {
await provider.send("wallet_switchEthereumChain", [
{ chainId: chainToNetwork(chainId) },
]);
const cNetwork = await provider.getNetwork();
// This is workaround for when Metamask fails to switch network.
if(cNetwork.chainId !== chainToNetworkDec(chainId)) {
console.log('switchProviderNetwork did not work');
throw new Error("Metamask could not switch network");
if (cNetwork.chainId !== chainToNetworkDec(chainId)) {
console.log("switchProviderNetwork did not work");
throw new Error("Metamask could not switch network");
}
}
function Chain({
name,
chainId,
addMessage,
getSelectedVaa,
appMsgIdx,
}: {
name: string;
chainId: ChainId;
addMessage: (m: SentVaaData) => void;
getSelectedVaa: () => SentVaaData;
appMsgIdx: number;
}) {
const { provider, signer, signerAddress } = useEthereumProvider();
const [messageText, setMessageText] = useState("");
const [resultText, setResultText] = useState("");
const handleChange = useCallback((event) => {
setMessageText(event.target.value);
}, []);
const { enqueueSnackbar } = useSnackbar(); //closeSnackbar
const processClickHandler = useCallback(() => {
console.log('---Process---- idx:' + appMsgIdx);
const vaaData = getSelectedVaa();
console.log('vaa seq: '+vaaData.vaa.sequence+' chain: '+vaaData.vaa.emitter_chain+' will be processed on:'+chainId);
console.log('string was: '+Buffer.from(vaaData.vaa.payload).toString());
// console.log('emmiter: '+vaaData.vaa.emitter_address);
if (!signer || !provider) return;
(async () => {
try {
await switchProviderNetwork(provider, chainId);
const Messenger = Messenger__factory.connect(
chainToContract(chainId),
signer
);
const nonce = createNonce();
const sendTx = await Messenger.receiveBytes(
vaaData.bytes,
nonce
);
const sendReceipt = await sendTx.wait();
console.log(sendReceipt);
setResultText('Success: ' + Buffer.from(vaaData.vaa.payload).toString());
} catch (e) {
console.log("receiveBytes failed ", e);
setResultText('Exception: ' + parseError(e));
enqueueSnackbar("EXCEPTION in Process: " + parseError(e), {
persist: false,
});
}
})();
}, [signer, provider, chainId, appMsgIdx, getSelectedVaa, enqueueSnackbar]); //appMsgIdx, getSelectedVaa]);
const sendClickHandler = useCallback(() => {
if (!signer || !provider) return;
(async () => {
try {
await switchProviderNetwork(provider, chainId);
const Messenger = Messenger__factory.connect(
chainToContract(chainId),
signer
);
const nonce = createNonce();
const bytesToSend = Buffer.from(messageText);
const sendTx = await Messenger.sendStr(
new Uint8Array(bytesToSend),
nonce
);
const sendReceipt = await sendTx.wait();
const sequence = parseSequenceFromLogEth(
sendReceipt,
await Messenger.wormhole()
);
const { vaaBytes } = await getSignedVAAWithRetry(
WORMHOLE_RPC_HOSTS,
chainId,
getEmitterAddressEth(chainToContract(chainId)),
sequence.toString()
);
const { parse_vaa } = await importCoreWasm();
addMessage({vaa:parse_vaa(vaaBytes), bytes:vaaBytes});
} catch (e) {
console.log("EXCEPTION in Send: " + e);
enqueueSnackbar("EXCEPTION in Send: " + parseError(e), {
persist: false,
});
}
})();
}, [provider, signer, chainId, messageText, addMessage, enqueueSnackbar]);
return (
<Card sx={{ m: 2 }}>
<CardHeader title={name} />
<CardContent>
<TextField
multiline
fullWidth
rows="3"
placeholder="Type a message"
value={messageText}
onChange={handleChange}
/>
</CardContent>
<CardActions>
<Button sx={{mr: 2}}
onClick={sendClickHandler}
variant="contained"
disabled={!signerAddress}
>
Send
</Button>
<Button
onClick={processClickHandler}
variant="contained"
disabled={appMsgIdx < 0}
>
Process
</Button>
<br/>
<TextField
sx={{ml: 5, mr: 5, color: 'black'}}
inputProps={{readOnly: true}}
fullWidth
id="process-result"
label="last process result"
variant="standard"
value={resultText}
>
</TextField>
</CardActions>
</Card>
);
}
};
function App() {
const { connect, disconnect, signerAddress } = useEthereumProvider();
const {
wallet: solanaWallet,
wallets: solanaWallets,
select: solanaSelect,
connect: connectSolanaWallet,
disconnect: disconnectSolanaWallet,
publicKey: solanaPublicKey,
} = useSolanaWallet();
const {
connect: terraConnect,
disconnect: terraDisconnect,
connected: terraConnected,
wallet: terraWallet,
} = useTerraWallet();
// const terraAddrStr = (terraWallet && terraWallet.walletAddress) || "";
const terraAddrStr =
(terraWallet &&
terraWallet.wallets &&
terraWallet.wallets.length > 0 &&
terraWallet.wallets[0].terraAddress) ||
"";
const [messages, setMessages] = useState<SentVaaData[]>([]);
const [selectedIndex, setSelectedIndex] = useState(-1);
const getSelectedVaa = (() => {
const getSelectedVaa = () => {
return messages[selectedIndex];
});
};
const addMessage = useCallback((message: SentVaaData) => {
// setMessages((arr) => arr.concat(message)); // Recent at the bottom
setMessages((arr) => [message, ...arr]);
@ -254,34 +167,108 @@ function App() {
</Button>
) : (
<Button variant="contained" color="secondary" onClick={connect}>
Connect Wallet
Connect Metamask
</Button>
)}
{solanaPublicKey ? (
<Button
variant="outlined"
color="inherit"
onClick={disconnectSolanaWallet}
sx={{ textTransform: "none", ml: 1 }}
>
{solanaPublicKey.toString().substr(0, 5)}
...
{solanaPublicKey
.toString()
.substr(solanaPublicKey.toString().length - 3)}
</Button>
) : solanaWallet ? (
<Button
variant="contained"
color="secondary"
onClick={connectSolanaWallet}
sx={{ ml: 1 }}
>
Connect {solanaWallet.name}
</Button>
) : (
solanaWallets.map((wallet) => (
<Button
variant="contained"
color="secondary"
key={wallet.name}
onClick={() => {
solanaSelect(wallet.name);
}}
sx={{ ml: 1 }}
>
Select {wallet.name}
</Button>
))
)}
{terraConnected ? (
<Button
variant="outlined"
color="inherit"
onClick={terraDisconnect}
sx={{ ml: 1 }}
>
{terraAddrStr.substr(0, 5)}
...
{terraAddrStr.substr(terraAddrStr.toString().length - 3)}
</Button>
) : (
<Button
variant="contained"
color="secondary"
onClick={terraConnect}
sx={{ ml: 1 }}
>
Connect Terra
</Button>
)}
</Box>
<Box sx={{ display: "flex" }}>
<Box sx={{ flexBasis: "66%" }}>
<Chain
<TerraChain
name="Terra"
chainId={CHAIN_ID_TERRA}
addMessage={addMessage}
getSelectedVaa={getSelectedVaa}
appMsgIdx={selectedIndex}
/>
<SolanaChain
name="Solana"
chainId={CHAIN_ID_SOLANA}
addMessage={addMessage}
getSelectedVaa={getSelectedVaa}
appMsgIdx={selectedIndex}
/>
<EVMChain
name="Ethereum"
chainId={CHAIN_ID_ETH}
addMessage={addMessage}
getSelectedVaa={getSelectedVaa}
appMsgIdx={selectedIndex}
></Chain>
<Chain
></EVMChain>
<EVMChain
name="BSC"
chainId={CHAIN_ID_BSC}
addMessage={addMessage}
getSelectedVaa={getSelectedVaa}
appMsgIdx={selectedIndex}
></Chain>
></EVMChain>
</Box>
<Box sx={{ flexGrow: 1, p: 2, pl: 0 }}>
<Card sx={{ width: "100%", height: "100%" }}>
<CardHeader title="Observed Messages" />
<CardContent>
<List dense={true} sx={{maxHeight: 400, overflow: 'auto',}}>
<List dense={true} sx={{ maxHeight: 400, overflow: "auto" }}>
{messages.map((message, index) => {
const key = `${chainToName(message.vaa.emitter_chain)}-${uint8ArrayToNative(
const key = `${chainToName(
message.vaa.emitter_chain
)}-${uint8ArrayToNative(
message.vaa.emitter_address,
message.vaa.emitter_chain
)}-${message.vaa.sequence}`;
@ -289,9 +276,12 @@ function App() {
<ListItem key={key} divider>
<ListItemButton
selected={selectedIndex === index}
onClick={()=>{setSelectedIndex(index);}}
onClick={() => {
setSelectedIndex(index);
}}
>
<ListItemText sx={{wordBreak: 'break-all'}}
<ListItemText
sx={{ wordBreak: "break-all" }}
primary={Buffer.from(message.vaa.payload).toString()}
secondary={key}
/>

View File

@ -0,0 +1,63 @@
import React from "react";
import {
Button,
Card,
CardActions,
CardContent,
CardHeader,
TextField,
} from "@mui/material";
export function ChainUI(
name: string,
messageText: string,
handleChange: (event: any) => void,
sendClickHandler: () => void,
signerAddress: string | undefined,
processClickHandler: () => void,
appMsgIdx: number,
resultText: string
) {
return (
<Card sx={{ m: 2 }}>
<CardHeader title={name} />
<CardContent>
<TextField
multiline
fullWidth
rows="3"
placeholder="Type a message"
value={messageText}
onChange={handleChange}
/>
</CardContent>
<CardActions>
<Button
sx={{ mr: 2 }}
onClick={sendClickHandler}
variant="contained"
disabled={!signerAddress}
>
Send
</Button>
<Button
onClick={processClickHandler}
variant="contained"
disabled={appMsgIdx < 0 || !signerAddress}
>
Process
</Button>
<br />
<TextField
sx={{ ml: 5, mr: 5, color: "black" }}
inputProps={{ readOnly: true }}
fullWidth
id="process-result"
label="last process result"
variant="standard"
value={resultText}
></TextField>
</CardActions>
</Card>
);
}

View File

@ -0,0 +1,136 @@
import { useCallback, useState } from "react";
import { useSnackbar } from "notistack";
import {
ChainId,
createNonce,
//getBridgeFeeIx,
getEmitterAddressEth,
parseSequenceFromLogEth,
} from "@certusone/wormhole-sdk";
import getSignedVAAWithRetry from "@certusone/wormhole-sdk/lib/esm/rpc/getSignedVAAWithRetry";
import { importCoreWasm } from "@certusone/wormhole-sdk/lib/esm/solana/wasm";
import { ChainUI } from "./ChainUI";
import { useEthereumProvider } from "./contexts/EthereumProviderContext";
import { Messenger__factory } from "./ethers-contracts";
import {
SentVaaData,
switchProviderNetwork,
chainToContract,
parseError,
WORMHOLE_RPC_HOSTS,
} from "./App";
export function EVMChain({
name,
chainId,
addMessage,
getSelectedVaa,
appMsgIdx,
}: {
name: string;
chainId: ChainId;
addMessage: (m: SentVaaData) => void;
getSelectedVaa: () => SentVaaData;
appMsgIdx: number;
}) {
const { provider, signer, signerAddress } = useEthereumProvider();
const [messageText, setMessageText] = useState("");
const [resultText, setResultText] = useState("");
const handleChange = useCallback((event) => {
setMessageText(event.target.value);
}, []);
const { enqueueSnackbar } = useSnackbar(); //closeSnackbar
const processClickHandler = useCallback(() => {
console.log("---Process---- idx:" + appMsgIdx);
const vaaData = getSelectedVaa();
console.log(
"vaa seq: " +
vaaData.vaa.sequence +
" chain: " +
vaaData.vaa.emitter_chain +
" will be processed on:" +
chainId
);
console.log("string was: " + Buffer.from(vaaData.vaa.payload).toString());
// console.log('emmiter: '+vaaData.vaa.emitter_address);
if (!signer || !provider) return;
(async () => {
try {
await switchProviderNetwork(provider, chainId);
const Messenger = Messenger__factory.connect(
chainToContract(chainId),
signer
);
const nonce = createNonce();
const sendTx = await Messenger.receiveBytes(vaaData.bytes, nonce);
const sendReceipt = await sendTx.wait();
console.log(sendReceipt);
setResultText(
"Success: " + Buffer.from(vaaData.vaa.payload).toString()
);
} catch (e) {
console.log("receiveBytes failed ", e);
setResultText("Exception: " + parseError(e));
enqueueSnackbar("EXCEPTION in Process: " + parseError(e), {
persist: false,
});
}
})();
}, [signer, provider, chainId, appMsgIdx, getSelectedVaa, enqueueSnackbar]); //appMsgIdx, getSelectedVaa]);
const sendClickHandler = useCallback(() => {
if (!signer || !provider) return;
(async () => {
try {
await switchProviderNetwork(provider, chainId);
const Messenger = Messenger__factory.connect(
chainToContract(chainId),
signer
);
const nonce = createNonce();
// Sending message to Wormhole and waiting for it to be signed.
// 1. Send string transaction. And wait for Receipt.
// sendStr is defined in example contract Messenger.sol
const sendTx = await Messenger.sendStr(
new Uint8Array(Buffer.from(messageText)),
nonce
);
const sendReceipt = await sendTx.wait();
// 2. Call into wormhole sdk to get this message sequence.
// Sequence is specific to originator.
const sequence = parseSequenceFromLogEth(
sendReceipt,
await Messenger.wormhole()
);
// 3. Retrieve signed VAA. For this chain and sequence.
const { vaaBytes } = await getSignedVAAWithRetry(
WORMHOLE_RPC_HOSTS,
chainId,
getEmitterAddressEth(chainToContract(chainId)),
sequence.toString()
);
// 4. Parse signed VAA and store it for display and use.
// VAA use example is in part2.
const { parse_vaa } = await importCoreWasm();
addMessage({ vaa: parse_vaa(vaaBytes), bytes: vaaBytes });
} catch (e) {
console.log("EXCEPTION in Send: " + e);
enqueueSnackbar("EXCEPTION in Send: " + parseError(e), {
persist: false,
});
}
})();
}, [provider, signer, chainId, messageText, addMessage, enqueueSnackbar]);
return ChainUI(
name,
messageText,
handleChange,
sendClickHandler,
signerAddress,
processClickHandler,
appMsgIdx,
resultText
);
}

View File

@ -0,0 +1,225 @@
import { useCallback, useState } from "react";
import { useSnackbar } from "notistack";
import {
ChainId,
CHAIN_ID_SOLANA,
getEmitterAddressSolana,
hexToNativeString,
ixFromRust,
parseSequenceFromLogSolana,
postVaaSolanaWithRetry,
getClaimAddressSolana,
} from "@certusone/wormhole-sdk";
import getSignedVAAWithRetry from "@certusone/wormhole-sdk/lib/esm/rpc/getSignedVAAWithRetry";
import { importCoreWasm } from "@certusone/wormhole-sdk/lib/esm/solana/wasm";
import { ChainUI } from "./ChainUI";
import {
Connection,
Keypair,
// SystemProgram,
Transaction,
} from "@solana/web3.js";
import { useSolanaWallet } from "./contexts/SolanaWalletContext";
import {
SentVaaData,
parseError,
SOLANA_HOST,
SOLANA_PROGRAM,
WORMHOLE_RPC_HOSTS,
} from "./App";
//import { ExplicitTwoTone } from "@mui/icons-material";
//import { PublicKey } from "@solana/web3.js";
const SOLANA_BRIDGE_ADDRESS = "Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o";
export function SolanaChain({
name,
chainId,
addMessage,
getSelectedVaa,
appMsgIdx,
}: {
name: string;
chainId: ChainId;
addMessage: (m: SentVaaData) => void;
getSelectedVaa: () => SentVaaData;
appMsgIdx: number;
}) {
const { publicKey, signTransaction } = useSolanaWallet();
const [messageText, setMessageText] = useState("");
const [resultText, setResultText] = useState("");
const { enqueueSnackbar } = useSnackbar(); //closeSnackbar
const handleChange = useCallback((event) => {
setMessageText(event.target.value);
}, []);
const processClickHandler = useCallback(() => {
console.log("---Process---- idx:" + appMsgIdx);
const vaaData = getSelectedVaa();
console.log(
"vaa seq: " +
vaaData.vaa.sequence +
" chain: " +
vaaData.vaa.emitter_chain +
" will be processed on:" +
chainId
);
console.log("string was: " + Buffer.from(vaaData.vaa.payload).toString());
// console.log('emmiter: '+vaaData.vaa.emitter_address);
if (!publicKey || !signTransaction) return;
(async () => {
try {
const connection = new Connection(SOLANA_HOST, "confirmed");
// Verify VAA on core bridge and create VAA R/O account.
//console.log("vaaData.bytes.length=" + vaaData.bytes.length);
await postVaaSolanaWithRetry(
connection,
signTransaction,
SOLANA_BRIDGE_ADDRESS,
publicKey.toString(),
Buffer.from(vaaData.bytes),
0
);
// Check if this VAA was processed already. This will be re-checked on contract.
const claimAddress = await getClaimAddressSolana(
SOLANA_PROGRAM, // for claims on bridge use SOLANA_BRIDGE_ADDRESS,
vaaData.bytes
);
const claimInfo = await connection.getAccountInfo(claimAddress);
// Check if this VAA has been processed already. It will be checked in the contract also.
console.log("claimAddress: " + claimAddress.toString());
if (!claimInfo) {
// console.log("Not Claimed");
} else {
// console.log("Claimed");
throw new Error("This message was already processed");
}
// Call into messenger with signed VAA.
const { confirm_message_ix } = await import(
"wormhole-messenger-solana"
);
const ix = ixFromRust(
confirm_message_ix(
SOLANA_PROGRAM,
publicKey.toString(),
claimAddress.toString(),
new Uint8Array(Buffer.from(vaaData.bytes)) // Vaa is needed to form instruction. It is not used by contract.
)
);
const transaction = new Transaction().add(ix);
const { blockhash } = await connection.getRecentBlockhash();
transaction.recentBlockhash = blockhash;
transaction.feePayer = publicKey;
const signed = await signTransaction(transaction);
const txid = await connection.sendRawTransaction(signed.serialize());
console.log(txid);
await connection.confirmTransaction(txid);
// All back
setResultText(
"Success. Message: " + Buffer.from(vaaData.vaa.payload).toString()
);
} catch (e) {
console.log("receiveBytes failed ", e);
setResultText("Exception: " + parseError(e));
enqueueSnackbar("EXCEPTION in Process: " + parseError(e), {
persist: false,
});
}
})();
}, [
publicKey,
signTransaction,
chainId,
appMsgIdx,
getSelectedVaa,
enqueueSnackbar,
]);
const sendClickHandler = useCallback(() => {
console.log("Solana account key: " + publicKey);
if (!publicKey || !signTransaction) return;
(async () => {
try {
const connection = new Connection(SOLANA_HOST, "confirmed");
const { send_message_ix } = await import("wormhole-messenger-solana");
const messageKey = Keypair.generate();
console.log("message key: ", messageKey.publicKey.toString());
const emitter = hexToNativeString(
await getEmitterAddressSolana(SOLANA_PROGRAM),
CHAIN_ID_SOLANA
);
if (!emitter) {
throw new Error(
"An error occurred while calculating the emitter address"
);
}
const ix = ixFromRust(
send_message_ix(
SOLANA_PROGRAM,
publicKey.toString(),
emitter,
messageKey.publicKey.toString(),
new Uint8Array(Buffer.from(messageText))
)
);
const transaction = new Transaction().add(ix);
const { blockhash } = await connection.getRecentBlockhash();
transaction.recentBlockhash = blockhash;
transaction.feePayer = publicKey;
transaction.partialSign(messageKey);
const signed = await signTransaction(transaction);
const txid = await connection.sendRawTransaction(signed.serialize());
console.log(txid);
await connection.confirmTransaction(txid);
const info = await connection.getTransaction(txid);
console.log(info);
if (!info) {
throw new Error(
"An error occurred while fetching the transaction info"
);
}
const sequence = parseSequenceFromLogSolana(info);
console.log("seq: " + sequence);
console.log("emitter: " + emitter);
// 3. Retrieve signed VAA. For this chain and sequence.
const { vaaBytes } = await getSignedVAAWithRetry(
WORMHOLE_RPC_HOSTS,
chainId,
await getEmitterAddressSolana(SOLANA_PROGRAM),
sequence.toString()
);
// 4. Parse signed VAA and store it for display and use.
// VAA use example is in part2.
const { parse_vaa } = await importCoreWasm();
const parsedVaa = parse_vaa(vaaBytes);
addMessage({ vaa: parsedVaa, bytes: vaaBytes });
console.log(parsedVaa);
} catch (e) {
console.log("EXCEPTION in Send: " + e);
enqueueSnackbar("EXCEPTION in Send: " + parseError(e), {
persist: false,
});
}
})();
}, [
publicKey,
signTransaction,
chainId,
messageText,
addMessage,
enqueueSnackbar,
]);
return ChainUI(
name,
messageText,
handleChange,
sendClickHandler,
(publicKey && publicKey.toString()) || undefined,
processClickHandler,
appMsgIdx,
resultText
);
}

View File

@ -0,0 +1,161 @@
import { useCallback, useState } from "react";
import { useSnackbar } from "notistack";
import {
ChainId,
getEmitterAddressTerra,
parseSequenceFromLogTerra,
} from "@certusone/wormhole-sdk";
import getSignedVAAWithRetry from "@certusone/wormhole-sdk/lib/esm/rpc/getSignedVAAWithRetry";
import { importCoreWasm } from "@certusone/wormhole-sdk/lib/esm/solana/wasm";
import { ChainUI } from "./ChainUI";
import { MsgExecuteContract, LCDClient } from "@terra-money/terra.js";
import { useTerraWallet } from "./contexts/TerraWalletContext";
import { address as TERRA_CONTRACT_ADDRESS } from "./contract-addresses/terra";
import { SentVaaData, parseError, WORMHOLE_RPC_HOSTS } from "./App";
export function TerraChain({
name,
chainId,
addMessage,
getSelectedVaa,
appMsgIdx,
}: {
name: string;
chainId: ChainId;
addMessage: (m: SentVaaData) => void;
getSelectedVaa: () => SentVaaData;
appMsgIdx: number;
}) {
const {
// connect: terraConnect,
// disconnect: terraDisconnect,
connected: terraConnected,
wallet: terraWallet,
} = useTerraWallet();
const [messageText, setMessageText] = useState("");
const [resultText, setResultText] = useState("");
const { enqueueSnackbar } = useSnackbar(); //closeSnackbar
const handleChange = useCallback((event) => {
setMessageText(event.target.value);
}, []);
const processClickHandler = useCallback(() => {
console.log("---Process---- idx:" + appMsgIdx);
const vaaData = getSelectedVaa();
console.log(
"vaa seq: " +
vaaData.vaa.sequence +
" chain: " +
vaaData.vaa.emitter_chain +
" will be processed on:" +
chainId
);
console.log("string was: " + Buffer.from(vaaData.vaa.payload).toString());
// console.log('emmiter: '+vaaData.vaa.emitter_address);
if (!terraConnected) return;
(async () => {
try {
// TBD: Verify on messenger
setResultText(
"Partial Success: " + Buffer.from(vaaData.vaa.payload).toString()
);
} catch (e) {
console.log("receiveBytes failed ", e);
setResultText("Exception: " + parseError(e));
enqueueSnackbar("EXCEPTION in Process: " + parseError(e), {
persist: false,
});
}
})();
}, [terraConnected, chainId, appMsgIdx, getSelectedVaa, enqueueSnackbar]);
const sendClickHandler = useCallback(() => {
if (!terraConnected) return;
(async () => {
try {
// Sending message to Wormhole and waiting for it to be signed.
// 1. Create and post string transaction.
const sendMsg = new MsgExecuteContract(
terraWallet.wallets[0].terraAddress,
TERRA_CONTRACT_ADDRESS,
{
SendMessage: {
nonce: 1,
text: messageText,
},
},
{}
);
const txResult = await terraWallet.post({
msgs: [sendMsg],
//memo: "???",
});
// 2. Wait for receipt.
const TERRA_HOST = {
URL: "http://localhost:1317",
chainID: "columbus-5",
name: "localterra",
};
const lcd = new LCDClient(TERRA_HOST);
let info;
while (!info) {
await new Promise((resolve) => setTimeout(resolve, 1000));
try {
info = await lcd.tx.txInfo(txResult.result.txhash);
} catch (e) {
console.error(e);
}
}
if (info.code !== undefined && info.code !== 0) {
// error code
throw new Error(
`Tx ${txResult.result.txhash}: error code ${info.code}: ${info.raw_log}`
);
}
const sequence = parseSequenceFromLogTerra(info);
console.log(sequence);
// 3. Retrieve signed VAA. For this chain and sequence.
const { vaaBytes } = await getSignedVAAWithRetry(
WORMHOLE_RPC_HOSTS,
chainId,
await getEmitterAddressTerra(TERRA_CONTRACT_ADDRESS),
sequence.toString()
);
// 4. Parse signed VAA and store it for display and use.
// VAA use example is in part2.
const { parse_vaa } = await importCoreWasm();
const parsedVaa = parse_vaa(vaaBytes);
console.log(parsedVaa);
addMessage({ vaa: parsedVaa, bytes: vaaBytes });
} catch (e) {
console.log("EXCEPTION in Send: " + e);
enqueueSnackbar("EXCEPTION in Send: " + parseError(e), {
persist: false,
});
}
})();
}, [
chainId,
messageText,
addMessage,
enqueueSnackbar,
terraConnected,
terraWallet,
]);
return ChainUI(
name,
messageText,
handleChange,
sendClickHandler,
terraConnected ? "Y" : undefined,
processClickHandler,
appMsgIdx,
resultText
);
}

View File

@ -0,0 +1,28 @@
import { useWallet, WalletProvider } from "@solana/wallet-adapter-react";
import {
getPhantomWallet,
getSolletWallet,
} from "@solana/wallet-adapter-wallets";
import React, { FC, useMemo } from "react";
export const SolanaWalletProvider: FC = (props) => {
// @solana/wallet-adapter-wallets includes all the adapters but supports tree shaking --
// Only the wallets you want to instantiate here will be compiled into your application
const wallets = useMemo(() => {
return [
getPhantomWallet(),
// getSolflareWallet(),
// getTorusWallet({
// options: { clientId: 'Go to https://developer.tor.us and create a client ID' }
// }),
// getLedgerWallet(),
// getSolongWallet(),
// getMathWallet(),
getSolletWallet(),
];
}, []);
return <WalletProvider wallets={wallets}>{props.children}</WalletProvider>;
};
export const useSolanaWallet = useWallet;

View File

@ -0,0 +1,104 @@
import {
NetworkInfo,
Wallet,
WalletProvider,
useWallet,
} from "@terra-money/wallet-provider";
import React, {
ReactChildren,
useCallback,
useContext,
useMemo,
useState,
} from "react";
const mainnet = {
name: "mainnet",
chainID: "columbus-4",
lcd: "https://lcd.terra.dev",
};
const localnet = {
name: "localnet",
chainID: "localnet",
lcd: "http://localhost:1317",
};
const walletConnectChainIds: Record<number, NetworkInfo> = {
0: localnet,
1: mainnet,
};
interface ITerraWalletContext {
connect(): void;
disconnect(): void;
connected: boolean;
wallet: any;
}
const TerraWalletContext = React.createContext<ITerraWalletContext>({
connect: () => {},
disconnect: () => {},
connected: false,
wallet: null,
});
export const TerraWalletWrapper = ({
children,
}: {
children: ReactChildren;
}) => {
// TODO: Use wallet instead of useConnectedWallet.
const terraWallet = useWallet();
const [, setWallet] = useState<Wallet | undefined>(undefined);
const [connected, setConnected] = useState(false);
const connect = useCallback(() => {
const CHROME_EXTENSION = 1;
if (terraWallet) {
terraWallet.connect(terraWallet.availableConnectTypes[CHROME_EXTENSION]);
setWallet(terraWallet);
setConnected(true);
}
}, [terraWallet]);
const disconnect = useCallback(() => {
setConnected(false);
setWallet(undefined);
}, []);
const contextValue = useMemo(
() => ({
connect,
disconnect,
connected,
wallet: terraWallet,
}),
[connect, disconnect, connected, terraWallet]
);
return (
<TerraWalletContext.Provider value={contextValue}>
{children}
</TerraWalletContext.Provider>
);
};
export const TerraWalletProvider = ({
children,
}: {
children: ReactChildren;
}) => {
return (
<WalletProvider
defaultNetwork={localnet}
walletConnectChainIds={walletConnectChainIds}
>
<TerraWalletWrapper>{children}</TerraWalletWrapper>
</WalletProvider>
);
};
export const useTerraWallet = () => {
return useContext(TerraWalletContext);
};

View File

@ -1,11 +1,12 @@
import { ThemeProvider } from "@emotion/react";
import { createTheme, CssBaseline, responsiveFontSizes } from "@mui/material";
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { EthereumProviderProvider } from "./EthereumProviderContext";
import { SnackbarProvider } from 'notistack';
import { EthereumProviderProvider } from "./contexts/EthereumProviderContext";
import { SolanaWalletProvider } from "./contexts/SolanaWalletContext";
import { TerraWalletProvider } from "./contexts/TerraWalletContext.tsx";
import { SnackbarProvider } from "notistack";
const darkTheme = createTheme({
palette: {
@ -19,9 +20,13 @@ ReactDOM.render(
<ThemeProvider theme={theme}>
<CssBaseline />
<SnackbarProvider maxSnack={3}>
<EthereumProviderProvider>
<App />
</EthereumProviderProvider>
<SolanaWalletProvider>
<EthereumProviderProvider>
<TerraWalletProvider>
<App />
</TerraWalletProvider>
</EthereumProviderProvider>
</SolanaWalletProvider>
</SnackbarProvider>
</ThemeProvider>
</React.StrictMode>,

View File

@ -1,40 +0,0 @@
# Intro to Wormhole - Part 2
## Setup
Start a [minimal_devnet](../../../minimal_devnet/)
Deploy the EVM contracts
```bash
cd ethereum
npm ci
npm run migrate
npm run register
```
Run the UI
```bash
cd ui
npm install notistack // Only once.
npm ci
npm run typechain
npm start
```
# Tech
### Chain side
The Ethereum smart contract is in
`ethereum/contracts/Messenger.sol`
Added function: `receiveBytes`. This function receives VAA from UI and verifies it. There are three checks:
1. Verify Wormhole signatures. This is done via Wormhole SDK `parseAndVerifyVM` method.
2. Verify that VAA was emitted from one of known contract addresses. (Messenger on one of chains in this case). We register all addresses with each Smart contract in `npm run register` script, after all contracts have been migrated.
3. Check if this VAA was already processed. This is done by checking if VAA hash has been already processed.
### Client side
Calling code is in
`ui/src/App.tsx`
`processClickHandler`
All work is done in call `Messenger.receiveBytes`.
Call either returns, which is indication that VAA was verified, or it trows exception if verification fails in one of the steps above.

View File

@ -1,2 +0,0 @@
# dependencies
/scripts/contract-addresses

View File

@ -1,44 +0,0 @@
# Wormhole bridge - ETH
These smart contracts allow to use Ethereum as foreign chain in the Wormhole protocol.
The `Wormhole` contract is the bridge contract and allows tokens to be transferred out of ETH and VAAs to be submitted
to transfer tokens in or change configuration settings.
The `WrappedAsset` is a ERC-20 token contract that holds metadata about a wormhole asset on ETH. Wormhole assets are all
wrapped non-ETH assets that are currently held on ETH.
### Deploying
To deploy the bridge on Ethereum you first need to compile all smart contracts:
`npx truffle compile`
To deploy you can either use the bytecode from the `build/contracts` folder or the oz cli `oz deploy <Contract>`
([Documentation](https://docs.openzeppelin.com/learn/deploying-and-interacting)).
You first need to deploy one `Wrapped Asset` and initialize it using dummy data.
Then deploy the `Wormhole` using the initial guardian key (`key_x,y_parity,0`) and the address of the previously deployed
`WrappedAsset`. The wrapped asset contract will be used as proxy library to all the creation of cheap proxy wrapped
assets.
### Testing
For each test run:
Run `npx ganache-cli --deterministic --time "1970-01-01T00:00:00+00:00"` to start a chain.
Run the tests using `npm run test`
### User methods
`submitVAA(bytes vaa)` can be used to execute a VAA.
`lockAssets(address asset, uint256 amount, bytes32 recipient, uint8 target_chain)` can be used
to transfer any ERC20 compliant asset out of ETH to any recipient on another chain that is connected to the Wormhole
protocol. `asset` is the asset to be transferred, `amount` is the amount to transfer (this must be <= the allowance that
you have previously given to the bridge smart contract if the token is not a wormhole token), `recipient` is the foreign
chain address of the recipient, `target_chain` is the id of the chain to transfer to.
`lockETH(bytes32 recipient, uint8 target_chain)` is a convenience function to wrap the Ether sent with the function call
and transfer it as described in `lockAssets`.

View File

@ -1,56 +0,0 @@
// contracts/Messenger.sol
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;
import "./interfaces/IWormhole.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract Messenger is Ownable {
// Hardcode the Wormhole Core Bridge contract address
// In a real contract, we would set this in a constructor or Setup
address a = address(0xC89Ce4735882C9F0f0FE26686c53074E09B0D550);
IWormhole _wormhole = IWormhole(a);
mapping(bytes32 => bool) _completedMessages;
mapping(uint16 => bytes32) _bridgeContracts;
// sendStr sends bytes to the wormhole.
function sendStr(bytes memory str, uint32 nonce) public returns (uint64 sequence) {
sequence = _wormhole.publishMessage(nonce, str, 1);
return sequence;
}
// receiveStr confirms VAA and processes message on the receiving chain.
// Returns true when bytes are seen first time.
function receiveBytes(bytes memory encodedVm, uint32 /*nonce*/) public {
(IWormhole.VM memory vm, bool valid, string memory reason) = _wormhole.parseAndVerifyVM(encodedVm);
// 1. check wormhole signatures/
require(valid, reason);
// 2. Check if emtter chain contract is registered.
require(verifyBridgeVM(vm), " invalid emitter"); // ??? Can I check emitter here ??
// 3. Drop duplicate VAA.
require(!_completedMessages[vm.hash], " message already received");
_completedMessages[vm.hash] = true;
// Action place.. Store payload somewhere for later retrieval. [time?? -> bytes]
}
// Check if receiveBytes emmiter is actually registered chan.
function verifyBridgeVM(IWormhole.VM memory vm) internal view returns (bool){
if(_bridgeContracts[vm.emitterChainId] == vm.emitterAddress) return true;
return false;
}
// We register chain,bridge in [mpn run register] command.
function registerChain(uint16 chainId_, bytes32 bridgeContract_) public onlyOwner {
_bridgeContracts[chainId_] = bridgeContract_;
}
function wormhole() public view returns (IWormhole) {
return _wormhole;
}
}

View File

@ -1,40 +0,0 @@
// contracts/Structs.sol
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;
interface Structs {
struct Provider {
uint16 chainId;
uint16 governanceChainId;
bytes32 governanceContract;
}
struct GuardianSet {
address[] keys;
uint32 expirationTime;
}
struct Signature {
bytes32 r;
bytes32 s;
uint8 v;
uint8 guardianIndex;
}
struct VM {
uint8 version;
uint32 timestamp;
uint32 nonce;
uint16 emitterChainId;
bytes32 emitterAddress;
uint64 sequence;
uint8 consistencyLevel;
bytes payload;
uint32 guardianSetIndex;
Signature[] signatures;
bytes32 hash;
}
}

View File

@ -1,42 +0,0 @@
// contracts/Messages.sol
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;
import "../Structs.sol";
interface IWormhole is Structs {
event LogMessagePublished(address indexed sender, uint64 sequence, uint32 nonce, bytes payload, uint8 consistencyLevel);
function publishMessage(
uint32 nonce,
bytes memory payload,
uint8 consistencyLevel
) external payable returns (uint64 sequence);
function parseAndVerifyVM(bytes calldata encodedVM) external view returns (Structs.VM memory vm, bool valid, string memory reason);
function verifyVM(Structs.VM memory vm) external view returns (bool valid, string memory reason);
function verifySignatures(bytes32 hash, Structs.Signature[] memory signatures, Structs.GuardianSet memory guardianSet) external pure returns (bool valid, string memory reason) ;
function parseVM(bytes memory encodedVM) external pure returns (Structs.VM memory vm);
function getGuardianSet(uint32 index) external view returns (Structs.GuardianSet memory) ;
function getCurrentGuardianSetIndex() external view returns (uint32) ;
function getGuardianSetExpiry() external view returns (uint32) ;
function governanceActionIsConsumed(bytes32 hash) external view returns (bool) ;
function isInitialized(address impl) external view returns (bool) ;
function chainId() external view returns (uint16) ;
function governanceChainId() external view returns (uint16);
function governanceContract() external view returns (bytes32);
function messageFee() external view returns (uint256) ;
}

View File

@ -1,22 +0,0 @@
const fsp = require("fs/promises");
const Messenger = artifacts.require("Messenger");
const uiAddressDir = "../ui/src/contract-addresses";
const scAddressDir = "scripts/contract-addresses";
module.exports = async function(deployer, network) {
// deploy Messenger
await deployer.deploy(Messenger);
// Store Messenger address to smart contract source for registration of message originators.
await fsp.mkdir(scAddressDir, { recursive: true });
await fsp.writeFile(
`${scAddressDir}/${network}`,
`${Messenger.address}`
);
// Store Messenger address to ui. To call it.
await fsp.mkdir(uiAddressDir, { recursive: true });
await fsp.writeFile(
`${uiAddressDir}/${network}.js`,
`export const address = "${Messenger.address}"`
);
};

File diff suppressed because it is too large Load Diff

View File

@ -1,30 +0,0 @@
{
"name": "messenger-contracts",
"version": "0.0.1",
"description": "",
"devDependencies": {
"@chainsafe/truffle-plugin-abigen": "0.0.1",
"@openzeppelin/cli": "^2.8.2",
"@openzeppelin/contracts": "^4.3.1",
"@openzeppelin/test-environment": "^0.1.6",
"@openzeppelin/test-helpers": "^0.5.9",
"chai": "^4.2.0",
"mocha": "^8.2.1",
"truffle": "^5.3.6",
"truffle-assertions": "^0.9.2",
"truffle-plugin-verify": "^0.5.11"
},
"scripts": {
"build": "truffle compile",
"migrate": "truffle migrate && truffle migrate --network development2",
"register": "truffle exec scripts/register_bsc_chain.js && truffle exec scripts/register_eth_chain.js --network development2"
},
"author": "",
"license": "Apache-2.0",
"dependencies": {
"elliptic": "^6.5.2",
"ganache-cli": "^6.12.1",
"jsonfile": "^4.0.0",
"solc": "^0.8.4"
}
}

View File

@ -1,54 +0,0 @@
// run this script with truffle exec
const fss = require("fs");
const jsonfile = require("jsonfile");
const Messenger = artifacts.require("Messenger");
const MessengerFullABI = jsonfile.readFileSync(
"../build/contracts/Messenger.json"
).abi;
function prependTo32Bytes(str) {
var stripStr=(str.substring(0,2) === "0x")? str.substring(2): str;
while(stripStr.length < 64) {
stripStr = "00"+stripStr;
}
return "0x"+stripStr;
}
module.exports = async function(callback) {
try {
const ETH_CONTRACT_ADDRESS = fss.readFileSync('./scripts/contract-addresses/development').toString();
const BSC_CONTRACT_ADDRESS = fss.readFileSync('./scripts/contract-addresses/development2').toString();
const accounts = await web3.eth.getAccounts();
const initialized = new web3.eth.Contract(
MessengerFullABI,
Messenger.address
);
// Register the Eth endpoint on BSC
await initialized.methods
.registerChain(
2,
prependTo32Bytes(ETH_CONTRACT_ADDRESS)
)
.send({
value: 0,
from: accounts[0],
gasLimit: 2000000,
});
// Register the BSC endpoint on BSC
await initialized.methods
.registerChain(
4,
prependTo32Bytes(BSC_CONTRACT_ADDRESS)
)
.send({
value: 0,
from: accounts[0],
gasLimit: 2000000,
});
callback();
} catch (e) {
callback(e);
}
};

View File

@ -1,55 +0,0 @@
// run this script with truffle exec
const jsonfile = require("jsonfile");
const fss = require("fs");
const Messenger = artifacts.require("Messenger");
const MessengerFullABI = jsonfile.readFileSync(
"../build/contracts/Messenger.json"
).abi;
function prependTo32Bytes(str) {
if(str.substring(0,2) === "0x") {
return "0x000000000000000000000000" + str.substring(2);
} else {
return "0x000000000000000000000000" + str;
}
}
module.exports = async function(callback) {
try {
const ETH_CONTRACT_ADDRESS =fss.readFileSync('./scripts/contract-addresses/development').toString();
const BSC_CONTRACT_ADDRESS = fss.readFileSync('./scripts/contract-addresses/development2').toString();
const accounts = await web3.eth.getAccounts();
const initialized = new web3.eth.Contract(
MessengerFullABI,
Messenger.address
);
// Register the Eth endpoint on Eth
await initialized.methods
.registerChain(
2,
prependTo32Bytes(ETH_CONTRACT_ADDRESS)
)
.send({
value: 0,
from: accounts[0],
gasLimit: 2000000,
});
// Register the BSC endpoint on Eth
await initialized.methods
.registerChain(
4,
prependTo32Bytes(BSC_CONTRACT_ADDRESS)
)
.send({
value: 0,
from: accounts[0],
gasLimit: 2000000,
});
callback();
} catch (e) {
callback(e);
}
};

View File

@ -1,28 +0,0 @@
module.exports = {
networks: {
development: {
host: "127.0.0.1",
port: 8545,
network_id: "*",
},
development2: {
host: "127.0.0.1",
port: 8546,
network_id: "*",
},
},
compilers: {
solc: {
version: "0.8.4",
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
},
plugins: ["@chainsafe/truffle-plugin-abigen", "truffle-plugin-verify"],
};

View File

@ -1,38 +0,0 @@
[package]
name = "messenger"
version = "0.1.0"
edition = "2021"
authors = ["Wormhole Contributors <contact@certus.one>"]
description = "Pyth price receiver"
[lib]
crate-type = ["cdylib", "rlib"]
[features]
backtraces = ["cosmwasm-std/backtraces"]
# use library feature to disable all init/handle/query exports
library = []
[dependencies]
cosmwasm-std = { version = "0.16.0" }
cosmwasm-storage = { version = "0.16.0" }
schemars = "0.8.1"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
serde_derive = { version = "1.0.103"}
#cw20 = "0.8.0"
#cw20-base = { version = "0.8.0", features = ["library"] }
#terraswap = "2.4.0"
wormhole = { path = "../wormhole", features = ["library"] }
thiserror = { version = "1.0.20" }
k256 = { version = "0.9.4", default-features = false, features = ["ecdsa"] }
sha3 = { version = "0.9.1", default-features = false }
generic-array = { version = "0.14.4" }
hex = "0.4.2"
lazy_static = "1.4.0"
bigint = "4"
pyth-client = {git = "https://github.com/pyth-network/pyth-client-rs", branch = "v2"}
solana-program = "=1.7.0"
[dev-dependencies]
cosmwasm-vm = { version = "0.16.0", default-features = false }
serde_json = "1.0"

View File

@ -1,144 +0,0 @@
#[allow(unused_imports)]
use cosmwasm_std::{
entry_point,
to_binary,
Binary,
CosmosMsg,
Deps,
DepsMut,
Env,
MessageInfo,
QueryRequest,
Response,
StdError,
StdResult,
WasmMsg,
WasmQuery,
};
use crate::{
msg::{
ExecuteMsg,
InstantiateMsg,
MigrateMsg,
QueryMsg,
},
state::{
config,
config_read,
ConfigInfo,
},
};
#[allow(unused_imports)]
use wormhole::{
byte_utils::get_string_from_32,
error::ContractError,
msg::QueryMsg as WormholeQueryMsg,
msg::ExecuteMsg as WormholeExecuteMsg,
state::{
vaa_archive_add,
vaa_archive_check,
GovernancePacket,
ParsedVAA,
},
};
// Chain ID of Terra
const CHAIN_ID: u16 = 3;
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult<Response> {
Ok(Response::new())
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
deps: DepsMut,
_env: Env,
_info: MessageInfo,
msg: InstantiateMsg,
) -> StdResult<Response> {
// Save general wormhole info
let state = ConfigInfo {
wormhole_contract: msg.wormhole_contract,
owner_address: msg.emitter_addr.as_slice().to_vec(),
emitter_addresses: Vec::new(),
};
config(deps.storage).save(&state)?;
Ok(Response::default())
}
pub fn send_wormhole_message(deps: DepsMut, _info: &MessageInfo, data: &Binary, nonce: u32) -> StdResult<u32> {
let cfg = config_read(deps.storage).load()?;
let seq: u32 = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart {
contract_addr: cfg.wormhole_contract.clone(),
msg: to_binary(&WormholeExecuteMsg::PostMessage {
message: data.clone(),
nonce,
})?,
}))?;
Ok(seq)
}
pub fn parse_vaa(deps: DepsMut, block_time: u64, data: &Binary) -> StdResult<ParsedVAA> {
let cfg = config_read(deps.storage).load()?;
let vaa: ParsedVAA = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart {
contract_addr: cfg.wormhole_contract.clone(),
msg: to_binary(&WormholeQueryMsg::VerifyVAA {
vaa: data.clone(),
block_time,
})?,
}))?;
Ok(vaa)
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult<Response> {
match msg {
ExecuteMsg::SendMessage { data, nonce } => send_to_wormhole(deps, env, info, &data, nonce),
ExecuteMsg::ProcessMessage { data } => process_vaa(deps, env, info, &data),
}
}
fn send_to_wormhole(
deps: DepsMut,
_env: Env,
info: MessageInfo,
data: &Binary, // This is string we pass in
nonce: u32,
) -> StdResult<Response> {
let resp = send_wormhole_message(deps, &info, data, nonce)?;
// Send new message sequnce to caller.
Ok(Response::new().add_attribute("seqence", resp.to_string()))
}
fn process_vaa(
mut deps: DepsMut,
env: Env,
_info: MessageInfo,
data: &Binary,
) -> StdResult<Response> {
// Check and Parse VAA.
let vaa = parse_vaa(deps.branch(), env.block.time.seconds(), data)?;
// Check for duplicate and add new one to storage.
if vaa_archive_check(deps.storage, vaa.hash.as_slice()) {
return ContractError::VaaAlreadyExecuted.std_err();
}
vaa_archive_add(deps.storage, vaa.hash.as_slice())?;
Ok(Response::new().add_attribute("action", "process"))
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {
QueryMsg::MessengerInfo { } => {
to_binary(&query_messenger_info(deps)?)
}
}
}
pub fn query_messenger_info(_deps: Deps) -> StdResult<String> {
ContractError::AssetNotFound.std_err()
}

View File

@ -1,27 +0,0 @@
use cosmwasm_std::StdError;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ContractError {
// CW20 errors
#[error("{0}")]
Std(#[from] StdError),
#[error("Unauthorized")]
Unauthorized {},
#[error("Cannot set to own account")]
CannotSetOwnAccount {},
#[error("Invalid zero amount")]
InvalidZeroAmount {},
#[error("Allowance is expired")]
Expired {},
#[error("No allowance for this account")]
NoAllowance {},
#[error("Minting cannot exceed the cap")]
CannotExceedCap {},
}

View File

@ -1,6 +0,0 @@
#[cfg(test)]
extern crate lazy_static;
pub mod contract;
pub mod msg;
pub mod state;

View File

@ -1,38 +0,0 @@
use cosmwasm_std::{
Binary,
};
use schemars::JsonSchema;
use serde::{
Deserialize,
Serialize,
};
type HumanAddr = String;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InstantiateMsg {
pub wormhole_contract: HumanAddr,
pub emitter_addr: Binary,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
SendMessage {
data: Binary, // String
nonce: u32,
},
ProcessMessage {
data: Binary, // VAA bytes
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct MigrateMsg {}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
MessengerInfo { },
}

View File

@ -1,45 +0,0 @@
use schemars::JsonSchema;
use serde::{
Deserialize,
Serialize,
};
#[allow(unused_imports)]
use cosmwasm_std::{
StdResult,
Storage,
};
#[allow(unused_imports)]
use cosmwasm_storage::{
bucket,
bucket_read,
singleton,
singleton_read,
Bucket,
ReadonlyBucket,
ReadonlySingleton,
Singleton,
};
//use wormhole::byte_utils::ByteUtils;
type HumanAddr = String;
pub static CONFIG_KEY: &[u8] = b"config";
// Guardian set information
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct ConfigInfo {
pub wormhole_contract: HumanAddr,
pub owner_address: Vec<u8>,
pub emitter_addresses: Vec<Vec<u8>>, // all valid contracts
}
pub fn config(storage: &mut dyn Storage) -> Singleton<ConfigInfo> {
singleton(storage, CONFIG_KEY)
}
pub fn config_read(storage: &dyn Storage) -> ReadonlySingleton<ConfigInfo> {
singleton_read(storage, CONFIG_KEY)
}

View File

@ -1,5 +0,0 @@
[alias]
wasm = "build --release --target wasm32-unknown-unknown"
wasm-debug = "build --target wasm32-unknown-unknown"
unit-test = "test --lib --features backtraces"
integration-test = "test --test integration"

View File

@ -1,34 +0,0 @@
[package]
name = "wormhole"
version = "0.1.0"
authors = ["Yuriy Savchenko <yuriy.savchenko@gmail.com>"]
edition = "2018"
description = "Wormhole contract"
[lib]
crate-type = ["cdylib", "rlib"]
[features]
backtraces = ["cosmwasm-std/backtraces"]
# use library feature to disable all init/handle/query exports
library = []
[dependencies]
cosmwasm-std = { version = "0.16.0" }
cosmwasm-storage = { version = "0.16.0" }
schemars = "0.8.1"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
#cw20 = "0.8.0"
#cw20-base = { version = "0.8.0", features = ["library"] }
#cw20-wrapped = { path = "../cw20-wrapped", features = ["library"] }
thiserror = { version = "1.0.20" }
k256 = { version = "0.9.4", default-features = false, features = ["ecdsa"] }
getrandom = { version = "0.2", features = ["custom"] }
sha3 = { version = "0.9.1", default-features = false }
generic-array = { version = "0.14.4" }
hex = "0.4.2"
lazy_static = "1.4.0"
[dev-dependencies]
cosmwasm-vm = { version = "0.16.0", default-features = false }
serde_json = "1.0"

View File

@ -1,73 +0,0 @@
use cosmwasm_std::{
CanonicalAddr,
StdError,
StdResult,
};
pub trait ByteUtils {
fn get_u8(&self, index: usize) -> u8;
fn get_u16(&self, index: usize) -> u16;
fn get_u32(&self, index: usize) -> u32;
fn get_u64(&self, index: usize) -> u64;
fn get_u128_be(&self, index: usize) -> u128;
/// High 128 then low 128
fn get_u256(&self, index: usize) -> (u128, u128);
fn get_address(&self, index: usize) -> CanonicalAddr;
fn get_bytes32(&self, index: usize) -> &[u8];
}
impl ByteUtils for &[u8] {
fn get_u8(&self, index: usize) -> u8 {
self[index]
}
fn get_u16(&self, index: usize) -> u16 {
let mut bytes: [u8; 16 / 8] = [0; 16 / 8];
bytes.copy_from_slice(&self[index..index + 2]);
u16::from_be_bytes(bytes)
}
fn get_u32(&self, index: usize) -> u32 {
let mut bytes: [u8; 32 / 8] = [0; 32 / 8];
bytes.copy_from_slice(&self[index..index + 4]);
u32::from_be_bytes(bytes)
}
fn get_u64(&self, index: usize) -> u64 {
let mut bytes: [u8; 64 / 8] = [0; 64 / 8];
bytes.copy_from_slice(&self[index..index + 8]);
u64::from_be_bytes(bytes)
}
fn get_u128_be(&self, index: usize) -> u128 {
let mut bytes: [u8; 128 / 8] = [0; 128 / 8];
bytes.copy_from_slice(&self[index..index + 128 / 8]);
u128::from_be_bytes(bytes)
}
fn get_u256(&self, index: usize) -> (u128, u128) {
(self.get_u128_be(index), self.get_u128_be(index + 128 / 8))
}
fn get_address(&self, index: usize) -> CanonicalAddr {
// 32 bytes are reserved for addresses, but only the last 20 bytes are taken by the actual address
CanonicalAddr::from(&self[index + 32 - 20..index + 32])
}
fn get_bytes32(&self, index: usize) -> &[u8] {
&self[index..index + 32]
}
}
pub fn extend_address_to_32(addr: &CanonicalAddr) -> Vec<u8> {
let mut result: Vec<u8> = vec![0; 12];
result.extend(addr.as_slice());
result
}
pub fn extend_string_to_32(s: &str) -> Vec<u8> {
let bytes = s.as_bytes();
let len = usize::min(32, bytes.len());
let result = vec![0; 32 - len];
[bytes[..len].to_vec(), result].concat()
}
pub fn get_string_from_32(v: &Vec<u8>) -> StdResult<String> {
let s = String::from_utf8(v.clone())
.or_else(|_| Err(StdError::generic_err("could not parse string")))?;
Ok(s.chars().filter(|c| c != &'\0').collect())
}

View File

@ -1,418 +0,0 @@
#[allow(unused_imports)]
use cosmwasm_std::{
entry_point,
has_coins,
to_binary,
BankMsg,
Binary,
Coin,
CosmosMsg,
Deps,
DepsMut,
Env,
MessageInfo,
Response,
StdError,
StdResult,
Storage,
WasmMsg,
};
use crate::{
byte_utils::{
extend_address_to_32,
ByteUtils,
},
error::ContractError,
msg::{
ExecuteMsg,
GetAddressHexResponse,
GetStateResponse,
GuardianSetInfoResponse,
InstantiateMsg,
MigrateMsg,
QueryMsg,
},
state::{
config,
config_read,
guardian_set_get,
guardian_set_set,
sequence_read,
sequence_set,
vaa_archive_add,
vaa_archive_check,
ConfigInfo,
ContractUpgrade,
GovernancePacket,
GuardianAddress,
GuardianSetInfo,
GuardianSetUpgrade,
ParsedVAA,
SetFee,
TransferFee,
},
};
use k256::{
ecdsa::{
recoverable::{
Id as RecoverableId,
Signature as RecoverableSignature,
},
Signature,
VerifyingKey,
},
EncodedPoint,
};
use sha3::{
Digest,
Keccak256,
};
use generic_array::GenericArray;
use std::convert::TryFrom;
type HumanAddr = String;
// Chain ID of Terra
const CHAIN_ID: u16 = 3;
// Lock assets fee amount and denomination
const FEE_AMOUNT: u128 = 0;
pub const FEE_DENOMINATION: &str = "uluna";
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult<Response> {
Ok(Response::default())
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
deps: DepsMut,
_env: Env,
_info: MessageInfo,
msg: InstantiateMsg,
) -> StdResult<Response> {
// Save general wormhole info
let state = ConfigInfo {
gov_chain: msg.gov_chain,
gov_address: msg.gov_address.as_slice().to_vec(),
guardian_set_index: 0,
guardian_set_expirity: msg.guardian_set_expirity,
fee: Coin::new(FEE_AMOUNT, FEE_DENOMINATION), // 0.01 Luna (or 10000 uluna) fee by default
};
config(deps.storage).save(&state)?;
// Add initial guardian set to storage
guardian_set_set(
deps.storage,
state.guardian_set_index,
&msg.initial_guardian_set,
)?;
Ok(Response::default())
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult<Response> {
match msg {
ExecuteMsg::PostMessage { message, nonce } => {
handle_post_message(deps, env, info, &message.as_slice(), nonce)
}
ExecuteMsg::SubmitVAA { vaa } => handle_submit_vaa(deps, env, info, vaa.as_slice()),
}
}
/// Process VAA message signed by quardians
fn handle_submit_vaa(
deps: DepsMut,
env: Env,
_info: MessageInfo,
data: &[u8],
) -> StdResult<Response> {
let state = config_read(deps.storage).load()?;
let vaa = parse_and_verify_vaa(deps.storage, data, env.block.time.seconds())?;
vaa_archive_add(deps.storage, vaa.hash.as_slice())?;
if state.gov_chain == vaa.emitter_chain && state.gov_address == vaa.emitter_address {
if state.guardian_set_index != vaa.guardian_set_index {
return Err(StdError::generic_err(
"governance VAAs must be signed by the current guardian set",
));
}
return handle_governance_payload(deps, env, &vaa.payload);
}
ContractError::InvalidVAAAction.std_err()
}
fn handle_governance_payload(deps: DepsMut, env: Env, data: &Vec<u8>) -> StdResult<Response> {
let gov_packet = GovernancePacket::deserialize(&data)?;
let module = String::from_utf8(gov_packet.module).unwrap();
let module: String = module.chars().filter(|c| c != &'\0').collect();
if module != "Core" {
return Err(StdError::generic_err("this is not a valid module"));
}
if gov_packet.chain != 0 && gov_packet.chain != CHAIN_ID {
return Err(StdError::generic_err(
"the governance VAA is for another chain",
));
}
match gov_packet.action {
1u8 => vaa_update_contract(deps, env, &gov_packet.payload),
2u8 => vaa_update_guardian_set(deps, env, &gov_packet.payload),
3u8 => handle_set_fee(deps, env, &gov_packet.payload),
4u8 => handle_transfer_fee(deps, env, &gov_packet.payload),
_ => ContractError::InvalidVAAAction.std_err(),
}
}
/// Parses raw VAA data into a struct and verifies whether it contains sufficient signatures of an
/// active guardian set i.e. is valid according to Wormhole consensus rules
fn parse_and_verify_vaa(
storage: &dyn Storage,
data: &[u8],
block_time: u64,
) -> StdResult<ParsedVAA> {
let vaa = ParsedVAA::deserialize(data)?;
if vaa.version != 1 {
return ContractError::InvalidVersion.std_err();
}
// Check if VAA with this hash was already accepted
if vaa_archive_check(storage, vaa.hash.as_slice()) {
return ContractError::VaaAlreadyExecuted.std_err();
}
// Load and check guardian set
let guardian_set = guardian_set_get(storage, vaa.guardian_set_index);
let guardian_set: GuardianSetInfo =
guardian_set.or_else(|_| ContractError::InvalidGuardianSetIndex.std_err())?;
if guardian_set.expiration_time != 0 && guardian_set.expiration_time < block_time {
return ContractError::GuardianSetExpired.std_err();
}
if (vaa.len_signers as usize) < guardian_set.quorum() {
return ContractError::NoQuorum.std_err();
}
// Verify guardian signatures
let mut last_index: i32 = -1;
let mut pos = ParsedVAA::HEADER_LEN;
for _ in 0..vaa.len_signers {
if pos + ParsedVAA::SIGNATURE_LEN > data.len() {
return ContractError::InvalidVAA.std_err();
}
let index = data.get_u8(pos) as i32;
if index <= last_index {
return ContractError::WrongGuardianIndexOrder.std_err();
}
last_index = index;
let signature = Signature::try_from(
&data[pos + ParsedVAA::SIG_DATA_POS
..pos + ParsedVAA::SIG_DATA_POS + ParsedVAA::SIG_DATA_LEN],
)
.or_else(|_| ContractError::CannotDecodeSignature.std_err())?;
let id = RecoverableId::new(data.get_u8(pos + ParsedVAA::SIG_RECOVERY_POS))
.or_else(|_| ContractError::CannotDecodeSignature.std_err())?;
let recoverable_signature = RecoverableSignature::new(&signature, id)
.or_else(|_| ContractError::CannotDecodeSignature.std_err())?;
let verify_key = recoverable_signature
.recover_verify_key_from_digest_bytes(GenericArray::from_slice(vaa.hash.as_slice()))
.or_else(|_| ContractError::CannotRecoverKey.std_err())?;
let index = index as usize;
if index >= guardian_set.addresses.len() {
return ContractError::TooManySignatures.std_err();
}
if !keys_equal(&verify_key, &guardian_set.addresses[index]) {
return ContractError::GuardianSignatureError.std_err();
}
pos += ParsedVAA::SIGNATURE_LEN;
}
Ok(vaa)
}
fn vaa_update_guardian_set(deps: DepsMut, env: Env, data: &Vec<u8>) -> StdResult<Response> {
/* Payload format
0 uint32 new_index
4 uint8 len(keys)
5 [][20]uint8 guardian addresses
*/
let mut state = config_read(deps.storage).load()?;
let GuardianSetUpgrade {
new_guardian_set_index,
new_guardian_set,
} = GuardianSetUpgrade::deserialize(&data)?;
if new_guardian_set_index != state.guardian_set_index + 1 {
return ContractError::GuardianSetIndexIncreaseError.std_err();
}
let old_guardian_set_index = state.guardian_set_index;
state.guardian_set_index = new_guardian_set_index;
guardian_set_set(deps.storage, state.guardian_set_index, &new_guardian_set)?;
config(deps.storage).save(&state)?;
let mut old_guardian_set = guardian_set_get(deps.storage, old_guardian_set_index)?;
old_guardian_set.expiration_time = env.block.time.seconds() + state.guardian_set_expirity;
guardian_set_set(deps.storage, old_guardian_set_index, &old_guardian_set)?;
Ok(Response::new()
.add_attribute("action", "guardian_set_change")
.add_attribute("old", old_guardian_set_index.to_string())
.add_attribute("new", state.guardian_set_index.to_string()))
}
fn vaa_update_contract(_deps: DepsMut, env: Env, data: &Vec<u8>) -> StdResult<Response> {
/* Payload format
0 [][32]uint8 new_contract
*/
let ContractUpgrade { new_contract } = ContractUpgrade::deserialize(&data)?;
Ok(Response::new()
.add_message(CosmosMsg::Wasm(WasmMsg::Migrate {
contract_addr: env.contract.address.to_string(),
new_code_id: new_contract,
msg: to_binary(&MigrateMsg {})?,
}))
.add_attribute("action", "contract_upgrade"))
}
pub fn handle_set_fee(deps: DepsMut, _env: Env, data: &Vec<u8>) -> StdResult<Response> {
let set_fee_msg = SetFee::deserialize(&data)?;
// Save new fees
let mut state = config_read(deps.storage).load()?;
state.fee = set_fee_msg.fee;
config(deps.storage).save(&state)?;
Ok(Response::new()
.add_attribute("action", "fee_change")
.add_attribute("new_fee.amount", state.fee.amount.to_string())
.add_attribute("new_fee.denom", state.fee.denom.to_string()))
}
pub fn handle_transfer_fee(deps: DepsMut, _env: Env, data: &Vec<u8>) -> StdResult<Response> {
let transfer_msg = TransferFee::deserialize(&data)?;
Ok(Response::new().add_message(CosmosMsg::Bank(BankMsg::Send {
to_address: deps.api.addr_humanize(&transfer_msg.recipient)?.to_string(),
amount: vec![transfer_msg.amount],
})))
}
fn handle_post_message(
deps: DepsMut,
env: Env,
info: MessageInfo,
message: &[u8],
nonce: u32,
) -> StdResult<Response> {
let state = config_read(deps.storage).load()?;
let fee = state.fee;
// Check fee
if fee.amount.u128() > 0 && !has_coins(info.funds.as_ref(), &fee) {
return ContractError::FeeTooLow.std_err();
}
let emitter = extend_address_to_32(&deps.api.addr_canonicalize(&info.sender.as_str())?);
let sequence = sequence_read(deps.storage, emitter.as_slice());
sequence_set(deps.storage, emitter.as_slice(), sequence + 1)?;
Ok(Response::new()
.add_attribute("message.message", hex::encode(message))
.add_attribute("message.sender", hex::encode(emitter))
.add_attribute("message.chain_id", CHAIN_ID.to_string())
.add_attribute("message.nonce", nonce.to_string())
.add_attribute("message.sequence", sequence.to_string())
.add_attribute("message.block_time", env.block.time.seconds().to_string()))
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {
QueryMsg::GuardianSetInfo {} => to_binary(&query_guardian_set_info(deps)?),
QueryMsg::VerifyVAA { vaa, block_time } => to_binary(&query_parse_and_verify_vaa(
deps,
&vaa.as_slice(),
block_time,
)?),
QueryMsg::GetState {} => to_binary(&query_state(deps)?),
QueryMsg::QueryAddressHex { address } => to_binary(&query_address_hex(deps, &address)?),
}
}
pub fn query_guardian_set_info(deps: Deps) -> StdResult<GuardianSetInfoResponse> {
let state = config_read(deps.storage).load()?;
let guardian_set = guardian_set_get(deps.storage, state.guardian_set_index)?;
let res = GuardianSetInfoResponse {
guardian_set_index: state.guardian_set_index,
addresses: guardian_set.addresses,
};
Ok(res)
}
pub fn query_parse_and_verify_vaa(
deps: Deps,
data: &[u8],
block_time: u64,
) -> StdResult<ParsedVAA> {
parse_and_verify_vaa(deps.storage, data, block_time)
}
// returns the hex of the 32 byte address we use for some address on this chain
pub fn query_address_hex(deps: Deps, address: &HumanAddr) -> StdResult<GetAddressHexResponse> {
Ok(GetAddressHexResponse {
hex: hex::encode(extend_address_to_32(&deps.api.addr_canonicalize(&address)?)),
})
}
pub fn query_state(deps: Deps) -> StdResult<GetStateResponse> {
let state = config_read(deps.storage).load()?;
let res = GetStateResponse { fee: state.fee };
Ok(res)
}
fn keys_equal(a: &VerifyingKey, b: &GuardianAddress) -> bool {
let mut hasher = Keccak256::new();
let point: EncodedPoint = EncodedPoint::from(a);
let point = point.decompress();
if bool::from(point.is_none()) {
return false;
}
let point = point.unwrap();
hasher.update(&point.as_bytes()[1..]);
let a = &hasher.finalize()[12..];
let b = &b.bytes;
if a.len() != b.len() {
return false;
}
for (ai, bi) in a.iter().zip(b.as_slice().iter()) {
if ai != bi {
return false;
}
}
true
}

View File

@ -1,113 +0,0 @@
use cosmwasm_std::StdError;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ContractError {
/// Invalid VAA version
#[error("InvalidVersion")]
InvalidVersion,
/// Guardian set with this index does not exist
#[error("InvalidGuardianSetIndex")]
InvalidGuardianSetIndex,
/// Guardian set expiration date is zero or in the past
#[error("GuardianSetExpired")]
GuardianSetExpired,
/// Not enough signers on the VAA
#[error("NoQuorum")]
NoQuorum,
/// Wrong guardian index order, order must be ascending
#[error("WrongGuardianIndexOrder")]
WrongGuardianIndexOrder,
/// Some problem with signature decoding from bytes
#[error("CannotDecodeSignature")]
CannotDecodeSignature,
/// Some problem with public key recovery from the signature
#[error("CannotRecoverKey")]
CannotRecoverKey,
/// Recovered pubkey from signature does not match guardian address
#[error("GuardianSignatureError")]
GuardianSignatureError,
/// VAA action code not recognized
#[error("InvalidVAAAction")]
InvalidVAAAction,
/// VAA guardian set is not current
#[error("NotCurrentGuardianSet")]
NotCurrentGuardianSet,
/// Only 128-bit amounts are supported
#[error("AmountTooHigh")]
AmountTooHigh,
/// Amount should be higher than zero
#[error("AmountTooLow")]
AmountTooLow,
/// Source and target chain ids must be different
#[error("SameSourceAndTarget")]
SameSourceAndTarget,
/// Target chain id must be the same as the current CHAIN_ID
#[error("WrongTargetChain")]
WrongTargetChain,
/// Wrapped asset init hook sent twice for the same asset id
#[error("AssetAlreadyRegistered")]
AssetAlreadyRegistered,
/// Guardian set must increase in steps of 1
#[error("GuardianSetIndexIncreaseError")]
GuardianSetIndexIncreaseError,
/// VAA was already executed
#[error("VaaAlreadyExecuted")]
VaaAlreadyExecuted,
/// Message sender not permitted to execute this operation
#[error("PermissionDenied")]
PermissionDenied,
/// Could not decode target address from canonical to human-readable form
#[error("WrongTargetAddressFormat")]
WrongTargetAddressFormat,
/// More signatures than active guardians found
#[error("TooManySignatures")]
TooManySignatures,
/// Wrapped asset not found in the registry
#[error("AssetNotFound")]
AssetNotFound,
/// Generic error when there is a problem with VAA structure
#[error("InvalidVAA")]
InvalidVAA,
/// Thrown when fee is enabled for the action, but was not sent with the transaction
#[error("FeeTooLow")]
FeeTooLow,
/// Registering asset outside of the wormhole
#[error("RegistrationForbidden")]
RegistrationForbidden,
}
impl ContractError {
pub fn std(&self) -> StdError {
StdError::GenericErr {
msg: format!("{}", self),
}
}
pub fn std_err<T>(&self) -> Result<T, StdError> {
Err(self.std())
}
}

View File

@ -1,7 +0,0 @@
pub mod byte_utils;
pub mod contract;
pub mod error;
pub mod msg;
pub mod state;
pub use crate::error::ContractError;

View File

@ -1,71 +0,0 @@
use cosmwasm_std::{
Binary,
Coin,
};
use schemars::JsonSchema;
use serde::{
Deserialize,
Serialize,
};
use crate::state::{
GuardianAddress,
GuardianSetInfo,
};
type HumanAddr = String;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InstantiateMsg {
pub gov_chain: u16,
pub gov_address: Binary,
pub initial_guardian_set: GuardianSetInfo,
pub guardian_set_expirity: u64,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
SubmitVAA { vaa: Binary },
PostMessage { message: Binary, nonce: u32 },
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct MigrateMsg {
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
GuardianSetInfo {},
VerifyVAA { vaa: Binary, block_time: u64 },
GetState {},
QueryAddressHex { address: HumanAddr },
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct GuardianSetInfoResponse {
pub guardian_set_index: u32, // Current guardian set index
pub addresses: Vec<GuardianAddress>, // List of querdian addresses
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct WrappedRegistryResponse {
pub address: HumanAddr,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct GetStateResponse {
pub fee: Coin,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct GetAddressHexResponse {
pub hex: String,
}

View File

@ -1,459 +0,0 @@
use schemars::{
JsonSchema,
};
use serde::{
Deserialize,
Serialize,
};
use cosmwasm_std::{
Binary,
CanonicalAddr,
Coin,
StdResult,
Storage,
Uint128,
};
use cosmwasm_storage::{
bucket,
bucket_read,
singleton,
singleton_read,
Bucket,
ReadonlyBucket,
ReadonlySingleton,
Singleton,
};
use crate::{
byte_utils::ByteUtils,
error::ContractError,
};
use sha3::{
Digest,
Keccak256,
};
type HumanAddr = String;
pub static CONFIG_KEY: &[u8] = b"config";
pub static GUARDIAN_SET_KEY: &[u8] = b"guardian_set";
pub static SEQUENCE_KEY: &[u8] = b"sequence";
pub static WRAPPED_ASSET_KEY: &[u8] = b"wrapped_asset";
pub static WRAPPED_ASSET_ADDRESS_KEY: &[u8] = b"wrapped_asset_address";
// Guardian set information
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct ConfigInfo {
// Current active guardian set
pub guardian_set_index: u32,
// Period for which a guardian set stays active after it has been replaced
pub guardian_set_expirity: u64,
// governance contract details
pub gov_chain: u16,
pub gov_address: Vec<u8>,
// Message sending fee
pub fee: Coin,
}
// Validator Action Approval(VAA) data
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct ParsedVAA {
pub version: u8,
pub guardian_set_index: u32,
pub timestamp: u32,
pub nonce: u32,
pub len_signers: u8,
pub emitter_chain: u16,
pub emitter_address: Vec<u8>,
pub sequence: u64,
pub consistency_level: u8,
pub payload: Vec<u8>,
pub hash: Vec<u8>,
}
impl ParsedVAA {
/* VAA format:
header (length 6):
0 uint8 version (0x01)
1 uint32 guardian set index
5 uint8 len signatures
per signature (length 66):
0 uint8 index of the signer (in guardian keys)
1 [65]uint8 signature
body:
0 uint32 timestamp (unix in seconds)
4 uint32 nonce
8 uint16 emitter_chain
10 [32]uint8 emitter_address
42 uint64 sequence
50 uint8 consistency_level
51 []uint8 payload
*/
pub const HEADER_LEN: usize = 6;
pub const SIGNATURE_LEN: usize = 66;
pub const GUARDIAN_SET_INDEX_POS: usize = 1;
pub const LEN_SIGNER_POS: usize = 5;
pub const VAA_NONCE_POS: usize = 4;
pub const VAA_EMITTER_CHAIN_POS: usize = 8;
pub const VAA_EMITTER_ADDRESS_POS: usize = 10;
pub const VAA_SEQUENCE_POS: usize = 42;
pub const VAA_CONSISTENCY_LEVEL_POS: usize = 50;
pub const VAA_PAYLOAD_POS: usize = 51;
// Signature data offsets in the signature block
pub const SIG_DATA_POS: usize = 1;
// Signature length minus recovery id at the end
pub const SIG_DATA_LEN: usize = 64;
// Recovery byte is last after the main signature
pub const SIG_RECOVERY_POS: usize = Self::SIG_DATA_POS + Self::SIG_DATA_LEN;
pub fn deserialize(data: &[u8]) -> StdResult<Self> {
let version = data.get_u8(0);
// Load 4 bytes starting from index 1
let guardian_set_index: u32 = data.get_u32(Self::GUARDIAN_SET_INDEX_POS);
let len_signers = data.get_u8(Self::LEN_SIGNER_POS) as usize;
let body_offset: usize = Self::HEADER_LEN + Self::SIGNATURE_LEN * len_signers as usize;
// Hash the body
if body_offset >= data.len() {
return ContractError::InvalidVAA.std_err();
}
let body = &data[body_offset..];
let mut hasher = Keccak256::new();
hasher.update(body);
let hash = hasher.finalize().to_vec();
// Rehash the hash
let mut hasher = Keccak256::new();
hasher.update(hash);
let hash = hasher.finalize().to_vec();
// Signatures valid, apply VAA
if body_offset + Self::VAA_PAYLOAD_POS > data.len() {
return ContractError::InvalidVAA.std_err();
}
let timestamp = data.get_u32(body_offset);
let nonce = data.get_u32(body_offset + Self::VAA_NONCE_POS);
let emitter_chain = data.get_u16(body_offset + Self::VAA_EMITTER_CHAIN_POS);
let emitter_address = data
.get_bytes32(body_offset + Self::VAA_EMITTER_ADDRESS_POS)
.to_vec();
let sequence = data.get_u64(body_offset + Self::VAA_SEQUENCE_POS);
let consistency_level = data.get_u8(body_offset + Self::VAA_CONSISTENCY_LEVEL_POS);
let payload = data[body_offset + Self::VAA_PAYLOAD_POS..].to_vec();
Ok(ParsedVAA {
version,
guardian_set_index,
timestamp,
nonce,
len_signers: len_signers as u8,
emitter_chain,
emitter_address,
sequence,
consistency_level,
payload,
hash,
})
}
}
// Guardian address
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct GuardianAddress {
pub bytes: Binary, // 20-byte addresses
}
use crate::contract::FEE_DENOMINATION;
#[cfg(test)]
use hex;
#[cfg(test)]
impl GuardianAddress {
pub fn from(string: &str) -> GuardianAddress {
GuardianAddress {
bytes: hex::decode(string).expect("Decoding failed").into(),
}
}
}
// Guardian set information
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct GuardianSetInfo {
pub addresses: Vec<GuardianAddress>,
// List of guardian addresses
pub expiration_time: u64, // Guardian set expiration time
}
impl GuardianSetInfo {
pub fn quorum(&self) -> usize {
// allow quorum of 0 for testing purposes...
if self.addresses.len() == 0 {
return 0;
}
((self.addresses.len() * 10 / 3) * 2) / 10 + 1
}
}
// Wormhole contract generic information
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct WormholeInfo {
// Period for which a guardian set stays active after it has been replaced
pub guardian_set_expirity: u64,
}
pub fn config(storage: &mut dyn Storage) -> Singleton<ConfigInfo> {
singleton(storage, CONFIG_KEY)
}
pub fn config_read(storage: &dyn Storage) -> ReadonlySingleton<ConfigInfo> {
singleton_read(storage, CONFIG_KEY)
}
pub fn guardian_set_set(
storage: &mut dyn Storage,
index: u32,
data: &GuardianSetInfo,
) -> StdResult<()> {
bucket(storage, GUARDIAN_SET_KEY).save(&index.to_be_bytes(), data)
}
pub fn guardian_set_get(storage: &dyn Storage, index: u32) -> StdResult<GuardianSetInfo> {
bucket_read(storage, GUARDIAN_SET_KEY).load(&index.to_be_bytes())
}
pub fn sequence_set(storage: &mut dyn Storage, emitter: &[u8], sequence: u64) -> StdResult<()> {
bucket(storage, SEQUENCE_KEY).save(emitter, &sequence)
}
pub fn sequence_read(storage: &dyn Storage, emitter: &[u8]) -> u64 {
bucket_read(storage, SEQUENCE_KEY)
.load(&emitter)
.or::<u64>(Ok(0))
.unwrap()
}
pub fn vaa_archive_add(storage: &mut dyn Storage, hash: &[u8]) -> StdResult<()> {
bucket(storage, GUARDIAN_SET_KEY).save(hash, &true)
}
pub fn vaa_archive_check(storage: &dyn Storage, hash: &[u8]) -> bool {
bucket_read(storage, GUARDIAN_SET_KEY)
.load(&hash)
.or::<bool>(Ok(false))
.unwrap()
}
pub fn wrapped_asset(storage: &mut dyn Storage) -> Bucket<HumanAddr> {
bucket(storage, WRAPPED_ASSET_KEY)
}
pub fn wrapped_asset_read(storage: &dyn Storage) -> ReadonlyBucket<HumanAddr> {
bucket_read(storage, WRAPPED_ASSET_KEY)
}
pub fn wrapped_asset_address(storage: &mut dyn Storage) -> Bucket<Vec<u8>> {
bucket(storage, WRAPPED_ASSET_ADDRESS_KEY)
}
pub fn wrapped_asset_address_read(storage: &dyn Storage) -> ReadonlyBucket<Vec<u8>> {
bucket_read(storage, WRAPPED_ASSET_ADDRESS_KEY)
}
pub struct GovernancePacket {
pub module: Vec<u8>,
pub action: u8,
pub chain: u16,
pub payload: Vec<u8>,
}
impl GovernancePacket {
pub fn deserialize(data: &Vec<u8>) -> StdResult<Self> {
let data = data.as_slice();
let module = data.get_bytes32(0).to_vec();
let action = data.get_u8(32);
let chain = data.get_u16(33);
let payload = data[35..].to_vec();
Ok(GovernancePacket {
module,
action,
chain,
payload,
})
}
}
// action 1
pub struct ContractUpgrade {
pub new_contract: u64,
}
// action 2
pub struct GuardianSetUpgrade {
pub new_guardian_set_index: u32,
pub new_guardian_set: GuardianSetInfo,
}
impl ContractUpgrade {
pub fn deserialize(data: &Vec<u8>) -> StdResult<Self> {
let data = data.as_slice();
let new_contract = data.get_u64(24);
Ok(ContractUpgrade {
new_contract,
})
}
}
impl GuardianSetUpgrade {
pub fn deserialize(data: &Vec<u8>) -> StdResult<Self> {
const ADDRESS_LEN: usize = 20;
let data = data.as_slice();
let new_guardian_set_index = data.get_u32(0);
let n_guardians = data.get_u8(4);
let mut addresses = vec![];
for i in 0..n_guardians {
let pos = 5 + (i as usize) * ADDRESS_LEN;
if pos + ADDRESS_LEN > data.len() {
return ContractError::InvalidVAA.std_err();
}
addresses.push(GuardianAddress {
bytes: data[pos..pos + ADDRESS_LEN].to_vec().into(),
});
}
let new_guardian_set = GuardianSetInfo {
addresses,
expiration_time: 0,
};
return Ok(GuardianSetUpgrade {
new_guardian_set_index,
new_guardian_set,
});
}
}
// action 3
pub struct SetFee {
pub fee: Coin,
}
impl SetFee {
pub fn deserialize(data: &Vec<u8>) -> StdResult<Self> {
let data = data.as_slice();
let (_, amount) = data.get_u256(0);
let fee = Coin {
denom: String::from(FEE_DENOMINATION),
amount: Uint128::new(amount),
};
Ok(SetFee { fee })
}
}
// action 4
pub struct TransferFee {
pub amount: Coin,
pub recipient: CanonicalAddr,
}
impl TransferFee {
pub fn deserialize(data: &Vec<u8>) -> StdResult<Self> {
let data = data.as_slice();
let recipient = data.get_address(0);
let (_, amount) = data.get_u256(32);
let amount = Coin {
denom: String::from(FEE_DENOMINATION),
amount: Uint128::new(amount),
};
Ok(TransferFee { amount, recipient })
}
}
#[cfg(test)]
mod tests {
use super::*;
fn build_guardian_set(length: usize) -> GuardianSetInfo {
let mut addresses: Vec<GuardianAddress> = Vec::with_capacity(length);
for _ in 0..length {
addresses.push(GuardianAddress {
bytes: vec![].into(),
});
}
GuardianSetInfo {
addresses,
expiration_time: 0,
}
}
#[test]
fn quardian_set_quorum() {
assert_eq!(build_guardian_set(1).quorum(), 1);
assert_eq!(build_guardian_set(2).quorum(), 2);
assert_eq!(build_guardian_set(3).quorum(), 3);
assert_eq!(build_guardian_set(4).quorum(), 3);
assert_eq!(build_guardian_set(5).quorum(), 4);
assert_eq!(build_guardian_set(6).quorum(), 5);
assert_eq!(build_guardian_set(7).quorum(), 5);
assert_eq!(build_guardian_set(8).quorum(), 6);
assert_eq!(build_guardian_set(9).quorum(), 7);
assert_eq!(build_guardian_set(10).quorum(), 7);
assert_eq!(build_guardian_set(11).quorum(), 8);
assert_eq!(build_guardian_set(12).quorum(), 9);
assert_eq!(build_guardian_set(20).quorum(), 14);
assert_eq!(build_guardian_set(25).quorum(), 17);
assert_eq!(build_guardian_set(100).quorum(), 67);
}
#[test]
fn test_deserialize() {
let x = hex::decode("080000000901007bfa71192f886ab6819fa4862e34b4d178962958d9b2e3d9437338c9e5fde1443b809d2886eaa69e0f0158ea517675d96243c9209c3fe1d94d5b19866654c6980000000b150000000500020001020304000000000000000000000000000000000000000000000000000000000000000000000a0261626364").unwrap();
let v = ParsedVAA::deserialize(x.as_slice()).unwrap();
assert_eq!(
v,
ParsedVAA {
version: 8,
guardian_set_index: 9,
timestamp: 2837,
nonce: 5,
len_signers: 1,
emitter_chain: 2,
emitter_address: vec![
0, 1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0
],
sequence: 10,
consistency_level: 2,
payload: vec![97, 98, 99, 100],
hash: vec![
195, 10, 19, 96, 8, 61, 218, 69, 160, 238, 165, 142, 105, 119, 139, 121, 212,
73, 238, 179, 13, 80, 245, 224, 75, 110, 163, 8, 185, 132, 55, 34
]
}
);
}
}

View File

@ -1,28 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# generated
/contracts
/src/contract-addresses
/src/ethers-contracts

View File

@ -1,70 +0,0 @@
# Getting Started with Create React App
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `npm start`
Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.\
You will also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you cant go back!**
If you arent satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
### Code Splitting
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
### Analyzing the Bundle Size
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
### Making a Progressive Web App
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
### Advanced Configuration
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
### Deployment
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
### `npm run build` fails to minify
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)

View File

@ -1,28 +0,0 @@
const { addBeforeLoader, loaderByName } = require("@craco/craco");
module.exports = {
webpack: {
configure: (webpackConfig) => {
const wasmExtensionRegExp = /\.wasm$/;
webpackConfig.resolve.extensions.push(".wasm");
webpackConfig.module.rules.forEach((rule) => {
(rule.oneOf || []).forEach((oneOf) => {
if (oneOf.loader && oneOf.loader.indexOf("file-loader") >= 0) {
oneOf.exclude.push(wasmExtensionRegExp);
}
});
});
const wasmLoader = {
test: /\.wasm$/,
exclude: /node_modules/,
loaders: ["wasm-loader"],
};
addBeforeLoader(webpackConfig, loaderByName("file-loader"), wasmLoader);
return webpackConfig;
},
},
};

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More