├── src ├── utils │ └── check.ts ├── global.d.ts ├── index.ts ├── backend │ ├── index.ts │ ├── querier.ts │ ├── backendApi.ts │ └── storage.ts ├── types.ts ├── environment.ts ├── helpers │ └── byte-array.ts └── memory.ts ├── .node_version ├── .npmignore ├── testdata ├── v1.1 │ ├── burner.wasm │ ├── floaty.wasm │ ├── queue.wasm │ ├── cyberpunk.wasm │ ├── hackatom.wasm │ ├── reflect.wasm │ ├── staking.wasm │ ├── ibc_reflect.wasm │ ├── crypto_verify.wasm │ └── ibc_reflect_send.wasm └── v1.0 │ ├── hackatom.wasm │ ├── cosmwasm_vm_test.wasm │ └── cw_machine-aarch64.wasm ├── .gitignore ├── contracts ├── cw-vm-test │ ├── src │ │ ├── lib.rs │ │ ├── state.rs │ │ ├── error.rs │ │ ├── msg.rs │ │ └── contract.rs │ ├── .cargo │ │ └── config │ ├── .gitpod.yml │ ├── .editorconfig │ ├── .gitignore │ ├── schema │ │ ├── count_response.json │ │ ├── instantiate_msg.json │ │ ├── query_msg.json │ │ ├── execute_msg.json │ │ └── state.json │ ├── rustfmt.toml │ ├── .gitpod.Dockerfile │ ├── NOTICE │ ├── examples │ │ └── schema.rs │ ├── Cargo.toml │ ├── .circleci │ │ └── config.yml │ ├── .github │ │ └── workflows │ │ │ └── Basic.yml │ ├── Importing.md │ ├── Developing.md │ ├── README.md │ ├── Publishing.md │ └── LICENSE └── cosmwasm-vm-test │ ├── .cargo │ └── config │ ├── src │ ├── lib.rs │ ├── state.rs │ ├── error.rs │ ├── helpers.rs │ ├── msg.rs │ ├── integration_tests.rs │ └── contract.rs │ ├── .gitpod.yml │ ├── .editorconfig │ ├── .gitignore │ ├── schema │ ├── instantiate_msg.json │ ├── get_count_response.json │ ├── query_msg.json │ ├── execute_msg.json │ └── state.json │ ├── rustfmt.toml │ ├── .gitpod.Dockerfile │ ├── NOTICE │ ├── examples │ └── schema.rs │ ├── Cargo.toml │ ├── .circleci │ └── config.yml │ ├── .github │ └── workflows │ │ └── Basic.yml │ ├── Importing.md │ ├── README.md │ ├── Developing.md │ ├── Publishing.md │ └── LICENSE ├── jest.config.js ├── .gitpod.yml ├── .eslintrc.js ├── LICENSE ├── tsconfig.json ├── .github └── workflows │ ├── main.yml │ └── publish.yaml ├── test ├── sample.test.ts ├── common │ ├── test-vm.ts │ └── test-data.ts ├── integration │ ├── cyberpunk.test.ts │ ├── burner.test.ts │ ├── queue.test.ts │ ├── hackatom.test.ts │ └── crypto-verify.test.ts └── vm.test.ts ├── webpack.config.js ├── package.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md └── README.md /src/utils/check.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.node_version: -------------------------------------------------------------------------------- 1 | 16.10.0 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | src 3 | -------------------------------------------------------------------------------- /testdata/v1.1/burner.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperweb-io/cosmwasm-vm-js/HEAD/testdata/v1.1/burner.wasm -------------------------------------------------------------------------------- /testdata/v1.1/floaty.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperweb-io/cosmwasm-vm-js/HEAD/testdata/v1.1/floaty.wasm -------------------------------------------------------------------------------- /testdata/v1.1/queue.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperweb-io/cosmwasm-vm-js/HEAD/testdata/v1.1/queue.wasm -------------------------------------------------------------------------------- /testdata/v1.0/hackatom.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperweb-io/cosmwasm-vm-js/HEAD/testdata/v1.0/hackatom.wasm -------------------------------------------------------------------------------- /testdata/v1.1/cyberpunk.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperweb-io/cosmwasm-vm-js/HEAD/testdata/v1.1/cyberpunk.wasm -------------------------------------------------------------------------------- /testdata/v1.1/hackatom.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperweb-io/cosmwasm-vm-js/HEAD/testdata/v1.1/hackatom.wasm -------------------------------------------------------------------------------- /testdata/v1.1/reflect.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperweb-io/cosmwasm-vm-js/HEAD/testdata/v1.1/reflect.wasm -------------------------------------------------------------------------------- /testdata/v1.1/staking.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperweb-io/cosmwasm-vm-js/HEAD/testdata/v1.1/staking.wasm -------------------------------------------------------------------------------- /testdata/v1.1/ibc_reflect.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperweb-io/cosmwasm-vm-js/HEAD/testdata/v1.1/ibc_reflect.wasm -------------------------------------------------------------------------------- /testdata/v1.1/crypto_verify.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperweb-io/cosmwasm-vm-js/HEAD/testdata/v1.1/crypto_verify.wasm -------------------------------------------------------------------------------- /testdata/v1.0/cosmwasm_vm_test.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperweb-io/cosmwasm-vm-js/HEAD/testdata/v1.0/cosmwasm_vm_test.wasm -------------------------------------------------------------------------------- /testdata/v1.1/ibc_reflect_send.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperweb-io/cosmwasm-vm-js/HEAD/testdata/v1.1/ibc_reflect_send.wasm -------------------------------------------------------------------------------- /testdata/v1.0/cw_machine-aarch64.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperweb-io/cosmwasm-vm-js/HEAD/testdata/v1.0/cw_machine-aarch64.wasm -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | dist 5 | .nova/ 6 | .env 7 | target/ 8 | .vscode/ 9 | .idea/ 10 | yarn.lock 11 | -------------------------------------------------------------------------------- /contracts/cw-vm-test/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod contract; 2 | mod error; 3 | pub mod msg; 4 | pub mod state; 5 | 6 | pub use crate::error::ContractError; 7 | -------------------------------------------------------------------------------- /contracts/cw-vm-test/.cargo/config: -------------------------------------------------------------------------------- 1 | [alias] 2 | wasm = "build --release --target wasm32-unknown-unknown" 3 | unit-test = "test --lib" 4 | schema = "run --example schema" 5 | -------------------------------------------------------------------------------- /contracts/cosmwasm-vm-test/.cargo/config: -------------------------------------------------------------------------------- 1 | [alias] 2 | wasm = "build --release --target wasm32-unknown-unknown" 3 | unit-test = "test --lib" 4 | schema = "run --example schema" 5 | -------------------------------------------------------------------------------- /contracts/cosmwasm-vm-test/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod contract; 2 | mod error; 3 | pub mod helpers; 4 | pub mod integration_tests; 5 | pub mod msg; 6 | pub mod state; 7 | 8 | pub use crate::error::ContractError; 9 | -------------------------------------------------------------------------------- /contracts/cw-vm-test/.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: cosmwasm/cw-gitpod-base:v0.16 2 | 3 | vscode: 4 | extensions: 5 | - rust-lang.rust 6 | 7 | tasks: 8 | - name: Dependencies & Build 9 | init: | 10 | cargo build 11 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | transform: { 5 | '^.+\\.ts?$': 'ts-jest', 6 | }, 7 | transformIgnorePatterns: ['/node_modules/'], 8 | }; -------------------------------------------------------------------------------- /contracts/cosmwasm-vm-test/src/state.rs: -------------------------------------------------------------------------------- 1 | use schemars::JsonSchema; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use cosmwasm_std::Addr; 5 | use cw_storage_plus::Item; 6 | 7 | pub const BUFFER: Item = Item::new("buffer"); 8 | -------------------------------------------------------------------------------- /contracts/cosmwasm-vm-test/.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: cosmwasm/cw-gitpod-base:v0.16 2 | 3 | vscode: 4 | extensions: 5 | - rust-lang.rust 6 | 7 | tasks: 8 | - name: Dependencies & Build 9 | init: | 10 | cargo build 11 | -------------------------------------------------------------------------------- /contracts/cw-vm-test/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.rs] 11 | indent_size = 4 12 | -------------------------------------------------------------------------------- /contracts/cosmwasm-vm-test/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.rs] 11 | indent_size = 4 12 | -------------------------------------------------------------------------------- /src/global.d.ts: -------------------------------------------------------------------------------- 1 | import { eddsa as ellipticEddsa } from "elliptic"; 2 | 3 | declare global { 4 | var _eddsa: ellipticEddsa; // we use a global to prevent serialization issues for the calling class 5 | function eddsa(): ellipticEddsa; 6 | } 7 | 8 | export {}; 9 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { eddsa } from 'elliptic'; 2 | 3 | export * from './memory'; 4 | export * from './backend'; 5 | export * from './instance'; 6 | export * from './environment'; 7 | 8 | global.eddsa = () => global._eddsa || (global._eddsa = new eddsa('ed25519')); 9 | -------------------------------------------------------------------------------- /contracts/cw-vm-test/.gitignore: -------------------------------------------------------------------------------- 1 | # Build results 2 | /target 3 | 4 | # Cargo+Git helper file (https://github.com/rust-lang/cargo/blob/0.44.1/src/cargo/sources/git/utils.rs#L320-L327) 5 | .cargo-ok 6 | 7 | # Text file backups 8 | **/*.rs.bk 9 | 10 | # macOS 11 | .DS_Store 12 | 13 | # IDEs 14 | *.iml 15 | .idea 16 | -------------------------------------------------------------------------------- /contracts/cosmwasm-vm-test/.gitignore: -------------------------------------------------------------------------------- 1 | # Build results 2 | /target 3 | 4 | # Cargo+Git helper file (https://github.com/rust-lang/cargo/blob/0.44.1/src/cargo/sources/git/utils.rs#L320-L327) 5 | .cargo-ok 6 | 7 | # Text file backups 8 | **/*.rs.bk 9 | 10 | # macOS 11 | .DS_Store 12 | 13 | # IDEs 14 | *.iml 15 | .idea 16 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | # This configuration file was automatically generated by Gitpod. 2 | # Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file) 3 | # and commit this file to your remote git repository to share the goodness with others. 4 | 5 | tasks: 6 | - init: npm install && npm run build 7 | command: npm test 8 | -------------------------------------------------------------------------------- /contracts/cw-vm-test/schema/count_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "CountResponse", 4 | "type": "object", 5 | "required": [ 6 | "count" 7 | ], 8 | "properties": { 9 | "count": { 10 | "type": "integer", 11 | "format": "int32" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /contracts/cw-vm-test/schema/instantiate_msg.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "InstantiateMsg", 4 | "type": "object", 5 | "required": [ 6 | "count" 7 | ], 8 | "properties": { 9 | "count": { 10 | "type": "integer", 11 | "format": "int32" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /contracts/cosmwasm-vm-test/schema/instantiate_msg.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "InstantiateMsg", 4 | "type": "object", 5 | "required": [ 6 | "count" 7 | ], 8 | "properties": { 9 | "count": { 10 | "type": "integer", 11 | "format": "int32" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /contracts/cosmwasm-vm-test/schema/get_count_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "GetCountResponse", 4 | "type": "object", 5 | "required": [ 6 | "count" 7 | ], 8 | "properties": { 9 | "count": { 10 | "type": "integer", 11 | "format": "int32" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /contracts/cw-vm-test/src/state.rs: -------------------------------------------------------------------------------- 1 | use schemars::JsonSchema; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use cosmwasm_std::Addr; 5 | use cw_storage_plus::Item; 6 | 7 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 8 | pub struct State { 9 | pub count: i32, 10 | pub owner: Addr, 11 | } 12 | 13 | pub const STATE: Item = Item::new("state"); 14 | -------------------------------------------------------------------------------- /contracts/cw-vm-test/src/error.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::StdError; 2 | use thiserror::Error; 3 | 4 | #[derive(Error, Debug)] 5 | pub enum ContractError { 6 | #[error("{0}")] 7 | Std(#[from] StdError), 8 | 9 | #[error("Unauthorized")] 10 | Unauthorized {}, 11 | // Add any other custom errors you like here. 12 | // Look at https://docs.rs/thiserror/1.0.21/thiserror/ for details. 13 | } 14 | -------------------------------------------------------------------------------- /src/backend/index.ts: -------------------------------------------------------------------------------- 1 | import * as BackendApi from './backendApi'; 2 | import * as Querier from './querier'; 3 | import * as Storage from './storage'; 4 | 5 | export * from './backendApi'; 6 | export * from './querier'; 7 | export * from './storage'; 8 | 9 | export interface IBackend { 10 | backend_api: BackendApi.IBackendApi; 11 | querier: Querier.IQuerier; 12 | storage: Storage.IIterStorage; 13 | } 14 | -------------------------------------------------------------------------------- /contracts/cw-vm-test/schema/query_msg.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "QueryMsg", 4 | "anyOf": [ 5 | { 6 | "type": "object", 7 | "required": [ 8 | "get_count" 9 | ], 10 | "properties": { 11 | "get_count": { 12 | "type": "object" 13 | } 14 | }, 15 | "additionalProperties": false 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /contracts/cosmwasm-vm-test/schema/query_msg.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "QueryMsg", 4 | "anyOf": [ 5 | { 6 | "type": "object", 7 | "required": [ 8 | "get_count" 9 | ], 10 | "properties": { 11 | "get_count": { 12 | "type": "object" 13 | } 14 | }, 15 | "additionalProperties": false 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /contracts/cw-vm-test/rustfmt.toml: -------------------------------------------------------------------------------- 1 | # stable 2 | newline_style = "unix" 3 | hard_tabs = false 4 | tab_spaces = 4 5 | 6 | # unstable... should we require `rustup run nightly cargo fmt` ? 7 | # or just update the style guide when they are stable? 8 | #fn_single_line = true 9 | #format_code_in_doc_comments = true 10 | #overflow_delimited_expr = true 11 | #reorder_impl_items = true 12 | #struct_field_align_threshold = 20 13 | #struct_lit_single_line = true 14 | #report_todo = "Always" 15 | 16 | -------------------------------------------------------------------------------- /contracts/cosmwasm-vm-test/rustfmt.toml: -------------------------------------------------------------------------------- 1 | # stable 2 | newline_style = "unix" 3 | hard_tabs = false 4 | tab_spaces = 4 5 | 6 | # unstable... should we require `rustup run nightly cargo fmt` ? 7 | # or just update the style guide when they are stable? 8 | #fn_single_line = true 9 | #format_code_in_doc_comments = true 10 | #overflow_delimited_expr = true 11 | #reorder_impl_items = true 12 | #struct_field_align_threshold = 20 13 | #struct_lit_single_line = true 14 | #report_todo = "Always" 15 | 16 | -------------------------------------------------------------------------------- /contracts/cosmwasm-vm-test/src/error.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::StdError; 2 | use thiserror::Error; 3 | 4 | #[derive(Error, Debug)] 5 | pub enum ContractError { 6 | #[error("{0}")] 7 | Std(#[from] StdError), 8 | 9 | #[error("Unauthorized")] 10 | Unauthorized {}, 11 | 12 | #[error("Custom Error val: {val:?}")] 13 | CustomError { val: String }, 14 | // Add any other custom errors you like here. 15 | // Look at https://docs.rs/thiserror/1.0.21/thiserror/ for details. 16 | } 17 | -------------------------------------------------------------------------------- /contracts/cw-vm-test/.gitpod.Dockerfile: -------------------------------------------------------------------------------- 1 | ### wasmd ### 2 | FROM cosmwasm/wasmd:v0.18.0 as wasmd 3 | 4 | ### rust-optimizer ### 5 | FROM cosmwasm/rust-optimizer:0.11.5 as rust-optimizer 6 | 7 | FROM gitpod/workspace-full:latest 8 | 9 | COPY --from=wasmd /usr/bin/wasmd /usr/local/bin/wasmd 10 | COPY --from=wasmd /opt/* /opt/ 11 | 12 | RUN sudo apt-get update \ 13 | && sudo apt-get install -y jq \ 14 | && sudo rm -rf /var/lib/apt/lists/* 15 | 16 | RUN rustup update stable \ 17 | && rustup target add wasm32-unknown-unknown 18 | -------------------------------------------------------------------------------- /contracts/cosmwasm-vm-test/.gitpod.Dockerfile: -------------------------------------------------------------------------------- 1 | ### wasmd ### 2 | FROM cosmwasm/wasmd:v0.18.0 as wasmd 3 | 4 | ### rust-optimizer ### 5 | FROM cosmwasm/rust-optimizer:0.11.5 as rust-optimizer 6 | 7 | FROM gitpod/workspace-full:latest 8 | 9 | COPY --from=wasmd /usr/bin/wasmd /usr/local/bin/wasmd 10 | COPY --from=wasmd /opt/* /opt/ 11 | 12 | RUN sudo apt-get update \ 13 | && sudo apt-get install -y jq \ 14 | && sudo rm -rf /var/lib/apt/lists/* 15 | 16 | RUN rustup update stable \ 17 | && rustup target add wasm32-unknown-unknown 18 | -------------------------------------------------------------------------------- /contracts/cosmwasm-vm-test/NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2022 william 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /contracts/cw-vm-test/NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2022 William Chen 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/eslint-recommended" 10 | ], 11 | "globals": { 12 | "Atomics": "readonly", 13 | "SharedArrayBuffer": "readonly" 14 | }, 15 | "parser": "@typescript-eslint/parser", 16 | "parserOptions": { 17 | "ecmaVersion": 2018, 18 | "sourceType": "module" 19 | }, 20 | "plugins": [ 21 | "@typescript-eslint" 22 | ], 23 | "rules": { 24 | "no-unused-vars": "off", 25 | "@typescript-eslint/no-unused-vars": ["error"] 26 | } 27 | }; -------------------------------------------------------------------------------- /contracts/cw-vm-test/examples/schema.rs: -------------------------------------------------------------------------------- 1 | use std::env::current_dir; 2 | use std::fs::create_dir_all; 3 | 4 | use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; 5 | 6 | use cosmwasm_vm_test::msg::{CountResponse, ExecuteMsg, InstantiateMsg, QueryMsg}; 7 | use cosmwasm_vm_test::state::State; 8 | 9 | fn main() { 10 | let mut out_dir = current_dir().unwrap(); 11 | out_dir.push("schema"); 12 | create_dir_all(&out_dir).unwrap(); 13 | remove_schemas(&out_dir).unwrap(); 14 | 15 | export_schema(&schema_for!(InstantiateMsg), &out_dir); 16 | export_schema(&schema_for!(ExecuteMsg), &out_dir); 17 | export_schema(&schema_for!(QueryMsg), &out_dir); 18 | export_schema(&schema_for!(State), &out_dir); 19 | export_schema(&schema_for!(CountResponse), &out_dir); 20 | } 21 | -------------------------------------------------------------------------------- /contracts/cosmwasm-vm-test/examples/schema.rs: -------------------------------------------------------------------------------- 1 | use std::env::current_dir; 2 | use std::fs::create_dir_all; 3 | 4 | use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; 5 | 6 | use cosmwasm_vm_test::msg::{ExecuteMsg, GetCountResponse, InstantiateMsg, QueryMsg}; 7 | use cosmwasm_vm_test::state::State; 8 | 9 | fn main() { 10 | let mut out_dir = current_dir().unwrap(); 11 | out_dir.push("schema"); 12 | create_dir_all(&out_dir).unwrap(); 13 | remove_schemas(&out_dir).unwrap(); 14 | 15 | export_schema(&schema_for!(InstantiateMsg), &out_dir); 16 | export_schema(&schema_for!(ExecuteMsg), &out_dir); 17 | export_schema(&schema_for!(QueryMsg), &out_dir); 18 | export_schema(&schema_for!(State), &out_dir); 19 | export_schema(&schema_for!(GetCountResponse), &out_dir); 20 | } 21 | -------------------------------------------------------------------------------- /contracts/cw-vm-test/schema/execute_msg.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "ExecuteMsg", 4 | "anyOf": [ 5 | { 6 | "type": "object", 7 | "required": [ 8 | "increment" 9 | ], 10 | "properties": { 11 | "increment": { 12 | "type": "object" 13 | } 14 | }, 15 | "additionalProperties": false 16 | }, 17 | { 18 | "type": "object", 19 | "required": [ 20 | "reset" 21 | ], 22 | "properties": { 23 | "reset": { 24 | "type": "object", 25 | "required": [ 26 | "count" 27 | ], 28 | "properties": { 29 | "count": { 30 | "type": "integer", 31 | "format": "int32" 32 | } 33 | } 34 | } 35 | }, 36 | "additionalProperties": false 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /contracts/cosmwasm-vm-test/schema/execute_msg.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "ExecuteMsg", 4 | "anyOf": [ 5 | { 6 | "type": "object", 7 | "required": [ 8 | "increment" 9 | ], 10 | "properties": { 11 | "increment": { 12 | "type": "object" 13 | } 14 | }, 15 | "additionalProperties": false 16 | }, 17 | { 18 | "type": "object", 19 | "required": [ 20 | "reset" 21 | ], 22 | "properties": { 23 | "reset": { 24 | "type": "object", 25 | "required": [ 26 | "count" 27 | ], 28 | "properties": { 29 | "count": { 30 | "type": "integer", 31 | "format": "int32" 32 | } 33 | } 34 | } 35 | }, 36 | "additionalProperties": false 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | 2 | export type Address = string; 3 | 4 | /** Port of [Env (Rust)](https://docs.rs/cosmwasm-std/1.1.4/cosmwasm_std/struct.Env.html) */ 5 | export type Env = 6 | | { 7 | block: BlockInfo; 8 | contract: ContractInfo; 9 | } 10 | | { 11 | block: BlockInfo; 12 | transaction: TransactionInfo | null; 13 | contract: ContractInfo; 14 | } 15 | 16 | export type BlockInfo = { 17 | height: number | string; 18 | time: number | string; 19 | chain_id: string; 20 | } 21 | 22 | export type TransactionInfo = { 23 | index: number | string; 24 | } 25 | 26 | export type ContractInfo = { 27 | address: Address; 28 | } 29 | 30 | /** By example of [@cosmjs/amino](https://cosmos.github.io/cosmjs/latest/amino/interfaces/Coin.html) */ 31 | export type Coin = { 32 | denom: string; 33 | amount: string; 34 | } 35 | 36 | /** Port of [MessageInfo (Rust)](https://docs.rs/cosmwasm-std/1.1.4/cosmwasm_std/struct.MessageInfo.html) */ 37 | export type MessageInfo = { 38 | sender: Address; 39 | funds: Coin[]; 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Terran One, inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /contracts/cw-vm-test/src/msg.rs: -------------------------------------------------------------------------------- 1 | use schemars::JsonSchema; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 5 | pub struct InstantiateMsg { 6 | pub count: i32, 7 | } 8 | 9 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 10 | #[serde(rename_all = "snake_case")] 11 | pub enum ExecuteMsg { 12 | Increment {}, 13 | Reset { count: i32 }, 14 | AddrValidate { str: String }, 15 | AddrHumanize { str: String }, 16 | AddrCanonicalize { str: String }, 17 | Secp256k1Verify { hash: String, signature: String, public_keystr: String }, 18 | Debug { message: String }, 19 | DbWrite { key: String, value: String }, 20 | DbRead { key: String }, 21 | } 22 | 23 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 24 | #[serde(rename_all = "snake_case")] 25 | pub enum QueryMsg { 26 | // GetCount returns the current count as a json-encoded number 27 | GetCount {}, 28 | } 29 | 30 | // We define a custom struct for each query response 31 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 32 | pub struct CountResponse { 33 | pub count: i32, 34 | } 35 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "src", 4 | "types" 5 | ], 6 | "exclude": [ 7 | "/node_modules/", 8 | "./src/**/*.spec.ts" 9 | ], 10 | "compilerOptions": { 11 | "allowSyntheticDefaultImports": true, 12 | "alwaysStrict": true, 13 | "baseUrl": "./", 14 | "declaration": true, 15 | "esModuleInterop": true, 16 | "lib": [ 17 | "es2015", 18 | "es2016", 19 | "es2017", 20 | "dom" 21 | ], 22 | "module": "commonjs", 23 | "moduleResolution": "node", 24 | "noFallthroughCasesInSwitch": true, 25 | "noImplicitAny": true, 26 | "noImplicitReturns": true, 27 | "noImplicitThis": true, 28 | "noUnusedLocals": false, 29 | "noUnusedParameters": false, 30 | "downlevelIteration": true, 31 | "outDir": "dist", 32 | "rootDir": "src", 33 | "skipLibCheck": true, 34 | "sourceMap": true, 35 | "strict": true, 36 | "strictFunctionTypes": true, 37 | "strictNullChecks": true, 38 | "strictPropertyInitialization": true, 39 | "target": "es6", 40 | "paths": { 41 | "*": [ 42 | "src/*" 43 | ] 44 | } 45 | }, 46 | "typeAcquisition": { 47 | "include": [ 48 | "jest" 49 | ] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /contracts/cw-vm-test/schema/state.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "State", 4 | "type": "object", 5 | "required": [ 6 | "count", 7 | "owner" 8 | ], 9 | "properties": { 10 | "count": { 11 | "type": "integer", 12 | "format": "int32" 13 | }, 14 | "owner": { 15 | "$ref": "#/definitions/Addr" 16 | } 17 | }, 18 | "definitions": { 19 | "Addr": { 20 | "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", 21 | "type": "string" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /contracts/cosmwasm-vm-test/schema/state.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "State", 4 | "type": "object", 5 | "required": [ 6 | "count", 7 | "owner" 8 | ], 9 | "properties": { 10 | "count": { 11 | "type": "integer", 12 | "format": "int32" 13 | }, 14 | "owner": { 15 | "$ref": "#/definitions/Addr" 16 | } 17 | }, 18 | "definitions": { 19 | "Addr": { 20 | "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", 21 | "type": "string" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push] 3 | jobs: 4 | build: 5 | name: Build and test on Node ${{ matrix.node }} and ${{ matrix.os }} 6 | 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | node: ['18.x'] 11 | os: [ubuntu-latest] 12 | 13 | steps: 14 | - name: Checkout repo 15 | uses: actions/checkout@v2 16 | 17 | - name: Use Node ${{ matrix.node }} 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: ${{ matrix.node }} 21 | 22 | - name: Install deps and build (with cache) 23 | uses: bahmutov/npm-install@v1 24 | 25 | - name: Test 26 | run: npm test --ci --coverage --maxWorkers=2 27 | 28 | - name: Build 29 | run: npm run build 30 | 31 | - name: Notify on failure 32 | if: failure() 33 | uses: slackapi/slack-github-action@v1.22.0 34 | env: 35 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 36 | SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK 37 | with: 38 | payload: | 39 | { 40 | "attachments": [ 41 | { 42 | "text": ":boom: CI failed for ", 43 | "color": "danger" 44 | } 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /src/environment.ts: -------------------------------------------------------------------------------- 1 | import { IBackendApi, IIterStorage, IQuerier, IStorage } from './backend'; 2 | 3 | export interface IEnvironment { 4 | call_function(name: string, args: object[]): object; 5 | } 6 | 7 | export interface GasState { 8 | gas_limit: number; 9 | externally_used_gas: number; 10 | } 11 | 12 | export interface ContextData { 13 | gas_state: GasState; 14 | storage: IStorage; 15 | storage_readonly: boolean; 16 | wasmer_instance: any; 17 | } 18 | 19 | export class Environment { 20 | public storage: IIterStorage; 21 | public querier: IQuerier; 22 | public backendApi: IBackendApi; 23 | public data: ContextData; 24 | 25 | constructor( 26 | storage: IIterStorage, 27 | querier: IQuerier, 28 | backendApi: IBackendApi, 29 | data: ContextData 30 | ) { 31 | this.storage = storage; 32 | this.querier = querier; 33 | this.backendApi = backendApi; 34 | this.data = data; 35 | this.call_function = this.call_function.bind(this); 36 | } 37 | 38 | call_function(name: string, args: object[] = []): object { 39 | if (name.length === 0) { 40 | throw new Error('Empty function name'); 41 | } 42 | 43 | if (args.length === 0) { 44 | console.log('No arguments passed'); 45 | } 46 | 47 | return {}; 48 | } 49 | 50 | public is_storage_readonly(): boolean { 51 | return this.data.storage_readonly; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/helpers/byte-array.ts: -------------------------------------------------------------------------------- 1 | import { fromHex } from '@cosmjs/encoding'; 2 | 3 | /** 4 | * Compares two byte arrays using the same logic as strcmp() 5 | * 6 | * @returns {number} bytes1 < bytes2 --> -1; bytes1 == bytes2 --> 0; bytes1 > bytes2 --> 1 7 | */ 8 | export function compare( 9 | bytes1: Uint8Array, 10 | bytes2: Uint8Array 11 | ): number { 12 | const length = Math.max(bytes1.length, bytes2.length); 13 | for (let i = 0; i < length; i++) { 14 | if (bytes1.length < i) return -1; 15 | if (bytes2.length < i) return 1; 16 | 17 | if (bytes1[i] < bytes2[i]) return -1; 18 | if (bytes1[i] > bytes2[i]) return 1; 19 | } 20 | 21 | return 0; 22 | } 23 | 24 | export function toNumber(bigEndianByteArray: Uint8Array | number[]) { 25 | let value = 0; 26 | for (let i = 0; i < bigEndianByteArray.length; i++) { 27 | value = (value * 256) + bigEndianByteArray[i]; 28 | } 29 | return value; 30 | } 31 | 32 | export function toByteArray(number: number, fixedLength?: number | undefined): Uint8Array { 33 | let hex = number.toString(16); 34 | if (hex.length % 2 === 1) { 35 | hex = `0${hex}`; 36 | } 37 | 38 | const bytesOriginal = fromHex(hex); 39 | 40 | if (!fixedLength) { 41 | return new Uint8Array([...bytesOriginal]); 42 | } 43 | 44 | let bytesFixedLength = [...bytesOriginal]; 45 | for (let i = 0; i < fixedLength - bytesOriginal.length; i++) { 46 | bytesFixedLength = [0, ...bytesFixedLength]; 47 | } 48 | 49 | return new Uint8Array(bytesFixedLength); 50 | } 51 | -------------------------------------------------------------------------------- /contracts/cosmwasm-vm-test/src/helpers.rs: -------------------------------------------------------------------------------- 1 | use schemars::JsonSchema; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use cosmwasm_std::{ 5 | to_binary, Addr, CosmosMsg, CustomQuery, Querier, QuerierWrapper, StdResult, WasmMsg, WasmQuery, 6 | }; 7 | 8 | use crate::msg::{ExecuteMsg, GetCountResponse, QueryMsg}; 9 | 10 | /// CwTemplateContract is a wrapper around Addr that provides a lot of helpers 11 | /// for working with this. 12 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 13 | pub struct CwTemplateContract(pub Addr); 14 | 15 | impl CwTemplateContract { 16 | pub fn addr(&self) -> Addr { 17 | self.0.clone() 18 | } 19 | 20 | pub fn call>(&self, msg: T) -> StdResult { 21 | let msg = to_binary(&msg.into())?; 22 | Ok(WasmMsg::Execute { 23 | contract_addr: self.addr().into(), 24 | msg, 25 | funds: vec![], 26 | } 27 | .into()) 28 | } 29 | 30 | /// Get Count 31 | pub fn count(&self, querier: &Q) -> StdResult 32 | where 33 | Q: Querier, 34 | T: Into, 35 | CQ: CustomQuery, 36 | { 37 | let msg = QueryMsg::GetCount {}; 38 | let query = WasmQuery::Smart { 39 | contract_addr: self.addr().into(), 40 | msg: to_binary(&msg)?, 41 | } 42 | .into(); 43 | let res: GetCountResponse = QuerierWrapper::::new(querier).query(&query)?; 44 | Ok(res) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/backend/querier.ts: -------------------------------------------------------------------------------- 1 | export interface IQuerier { 2 | query_raw(request: Uint8Array, gas_limit: number /* Uint64 */): Uint8Array; 3 | } 4 | 5 | /** Basic implementation of `IQuerier` with standardized `query_raw` 6 | * which delegates to a new, abstract `handleQuery` method. 7 | */ 8 | export abstract class QuerierBase implements IQuerier { 9 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 10 | query_raw(request: Uint8Array, gas_limit: number): Uint8Array { 11 | const queryRequest = parseQuery(request); 12 | 13 | // TODO: make room for error 14 | // The Ok(Ok(x)) represents SystemResult> 15 | 16 | return objectToUint8Array({ ok: { ok: objectToBase64(this.handleQuery(queryRequest)) }}); 17 | } 18 | 19 | /** Handle a specific JSON query message. */ 20 | abstract handleQuery(queryRequest: any): any; 21 | } 22 | 23 | /** Basic implementation which does not actually implement `handleQuery`. Intended for testing. */ 24 | export class BasicQuerier extends QuerierBase { 25 | handleQuery(queryRequest: any): any { 26 | throw new Error(`Unimplemented - subclass BasicQuerier and provide handleQuery() implementation.`) 27 | } 28 | } 29 | 30 | 31 | function parseQuery(bytes: Uint8Array): any { 32 | const query = JSON.parse(new TextDecoder().decode(bytes)); 33 | return query; 34 | } 35 | 36 | function objectToBase64(obj: object): string { 37 | return Buffer.from(JSON.stringify(obj)).toString('base64'); 38 | } 39 | 40 | function objectToUint8Array(obj: object): Uint8Array { 41 | return new TextEncoder().encode(JSON.stringify(obj)); 42 | } 43 | -------------------------------------------------------------------------------- /contracts/cosmwasm-vm-test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cosmwasm-vm-test" 3 | version = "0.1.0" 4 | authors = ["william"] 5 | edition = "2018" 6 | 7 | exclude = [ 8 | # 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. 9 | "contract.wasm", 10 | "hash.txt", 11 | ] 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [lib] 16 | crate-type = ["cdylib", "rlib"] 17 | 18 | [profile.release] 19 | opt-level = 3 20 | debug = false 21 | rpath = false 22 | lto = true 23 | debug-assertions = false 24 | codegen-units = 1 25 | panic = 'abort' 26 | incremental = false 27 | overflow-checks = true 28 | 29 | [features] 30 | # for more explicit tests, cargo test --features=backtraces 31 | backtraces = ["cosmwasm-std/backtraces"] 32 | # use library feature to disable all instantiate/execute/query exports 33 | library = [] 34 | 35 | [package.metadata.scripts] 36 | optimize = """docker run --rm -v "$(pwd)":/code \ 37 | --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ 38 | --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ 39 | cosmwasm/rust-optimizer:0.12.6 40 | """ 41 | 42 | [dependencies] 43 | cosmwasm-std = "1.0.0" 44 | cosmwasm-storage = "1.0.0" 45 | cw-storage-plus = "0.13.2" 46 | cw2 = "0.13.2" 47 | schemars = "0.8.8" 48 | serde = { version = "1.0.137", default-features = false, features = ["derive"] } 49 | thiserror = { version = "1.0.31" } 50 | 51 | [dev-dependencies] 52 | cosmwasm-schema = "1.0.0" 53 | cw-multi-test = "0.13.2" 54 | -------------------------------------------------------------------------------- /contracts/cw-vm-test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cosmwasm-vm-test" 3 | version = "0.1.0" 4 | authors = ["William Chen "] 5 | edition = "2018" 6 | 7 | exclude = [ 8 | # 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. 9 | "contract.wasm", 10 | "hash.txt", 11 | ] 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [lib] 16 | crate-type = ["cdylib", "rlib"] 17 | 18 | [profile.release] 19 | opt-level = 3 20 | debug = false 21 | rpath = false 22 | lto = true 23 | debug-assertions = false 24 | codegen-units = 1 25 | panic = 'abort' 26 | incremental = false 27 | overflow-checks = true 28 | 29 | [features] 30 | # for more explicit tests, cargo test --features=backtraces 31 | backtraces = ["cosmwasm-std/backtraces"] 32 | # use library feature to disable all instantiate/execute/query exports 33 | library = [] 34 | 35 | [package.metadata.scripts] 36 | optimize = """docker run --rm -v "$(pwd)":/code \ 37 | --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ 38 | --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ 39 | cosmwasm/rust-optimizer:0.12.6 40 | """ 41 | 42 | [dependencies] 43 | cosmwasm-std = { version = "0.16.2" } 44 | cosmwasm-storage = { version = "0.16.0" } 45 | cw-storage-plus = "0.8.0" 46 | cw2 = "0.8.1" 47 | schemars = "0.8.3" 48 | serde = { version = "1.0.127", default-features = false, features = ["derive"] } 49 | thiserror = { version = "1.0.26" } 50 | hex = "0.4" 51 | 52 | [dev-dependencies] 53 | cosmwasm-schema = { version = "0.16.0" } 54 | -------------------------------------------------------------------------------- /contracts/cw-vm-test/.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | executors: 4 | builder: 5 | docker: 6 | - image: buildpack-deps:trusty 7 | 8 | jobs: 9 | docker-image: 10 | executor: builder 11 | steps: 12 | - checkout 13 | - setup_remote_docker 14 | docker_layer_caching: true 15 | - run: 16 | name: Build Docker artifact 17 | command: docker build --pull -t "cosmwasm/cw-gitpod-base:${CIRCLE_SHA1}" . 18 | - run: 19 | name: Push application Docker image to docker hub 20 | command: | 21 | if [ "${CIRCLE_BRANCH}" = "master" ]; then 22 | docker tag "cosmwasm/cw-gitpod-base:${CIRCLE_SHA1}" cosmwasm/cw-gitpod-base:latest 23 | docker login --password-stdin -u "$DOCKER_USER" \<<<"$DOCKER_PASS" 24 | docker push cosmwasm/cw-gitpod-base:latest 25 | docker logout 26 | fi 27 | 28 | docker-tagged: 29 | executor: builder 30 | steps: 31 | - checkout 32 | - setup_remote_docker 33 | docker_layer_caching: true 34 | - run: 35 | name: Push application Docker image to docker hub 36 | command: | 37 | docker tag "cosmwasm/cw-gitpod-base:${CIRCLE_SHA1}" "cosmwasm/cw-gitpod-base:${CIRCLE_TAG}" 38 | docker login --password-stdin -u "$DOCKER_USER" \<<<"$DOCKER_PASS" 39 | docker push 40 | docker logout 41 | 42 | workflows: 43 | version: 2 44 | test-suite: 45 | jobs: 46 | # this is now a slow process... let's only run on master 47 | - docker-image: 48 | filters: 49 | branches: 50 | only: 51 | - master 52 | - docker-tagged: 53 | filters: 54 | tags: 55 | only: 56 | - /^v.*/ 57 | branches: 58 | ignore: 59 | - /.*/ 60 | requires: 61 | - docker-image 62 | -------------------------------------------------------------------------------- /contracts/cosmwasm-vm-test/.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | executors: 4 | builder: 5 | docker: 6 | - image: buildpack-deps:trusty 7 | 8 | jobs: 9 | docker-image: 10 | executor: builder 11 | steps: 12 | - checkout 13 | - setup_remote_docker 14 | docker_layer_caching: true 15 | - run: 16 | name: Build Docker artifact 17 | command: docker build --pull -t "cosmwasm/cw-gitpod-base:${CIRCLE_SHA1}" . 18 | - run: 19 | name: Push application Docker image to docker hub 20 | command: | 21 | if [ "${CIRCLE_BRANCH}" = "master" ]; then 22 | docker tag "cosmwasm/cw-gitpod-base:${CIRCLE_SHA1}" cosmwasm/cw-gitpod-base:latest 23 | docker login --password-stdin -u "$DOCKER_USER" \<<<"$DOCKER_PASS" 24 | docker push cosmwasm/cw-gitpod-base:latest 25 | docker logout 26 | fi 27 | 28 | docker-tagged: 29 | executor: builder 30 | steps: 31 | - checkout 32 | - setup_remote_docker 33 | docker_layer_caching: true 34 | - run: 35 | name: Push application Docker image to docker hub 36 | command: | 37 | docker tag "cosmwasm/cw-gitpod-base:${CIRCLE_SHA1}" "cosmwasm/cw-gitpod-base:${CIRCLE_TAG}" 38 | docker login --password-stdin -u "$DOCKER_USER" \<<<"$DOCKER_PASS" 39 | docker push 40 | docker logout 41 | 42 | workflows: 43 | version: 2 44 | test-suite: 45 | jobs: 46 | # this is now a slow process... let's only run on master 47 | - docker-image: 48 | filters: 49 | branches: 50 | only: 51 | - master 52 | - docker-tagged: 53 | filters: 54 | tags: 55 | only: 56 | - /^v.*/ 57 | branches: 58 | ignore: 59 | - /.*/ 60 | requires: 61 | - docker-image 62 | -------------------------------------------------------------------------------- /contracts/cw-vm-test/.github/workflows/Basic.yml: -------------------------------------------------------------------------------- 1 | # Based on https://github.com/actions-rs/example/blob/master/.github/workflows/quickstart.yml 2 | 3 | on: [push, pull_request] 4 | 5 | name: Basic 6 | 7 | jobs: 8 | 9 | test: 10 | name: Test Suite 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout sources 14 | uses: actions/checkout@v2 15 | 16 | - name: Install stable toolchain 17 | uses: actions-rs/toolchain@v1 18 | with: 19 | profile: minimal 20 | toolchain: 1.51.0 21 | target: wasm32-unknown-unknown 22 | override: true 23 | 24 | - name: Run unit tests 25 | uses: actions-rs/cargo@v1 26 | with: 27 | command: unit-test 28 | args: --locked 29 | env: 30 | RUST_BACKTRACE: 1 31 | 32 | - name: Compile WASM contract 33 | uses: actions-rs/cargo@v1 34 | with: 35 | command: wasm 36 | args: --locked 37 | env: 38 | RUSTFLAGS: "-C link-arg=-s" 39 | 40 | lints: 41 | name: Lints 42 | runs-on: ubuntu-latest 43 | steps: 44 | - name: Checkout sources 45 | uses: actions/checkout@v2 46 | 47 | - name: Install stable toolchain 48 | uses: actions-rs/toolchain@v1 49 | with: 50 | profile: minimal 51 | toolchain: 1.51.0 52 | override: true 53 | components: rustfmt, clippy 54 | 55 | - name: Run cargo fmt 56 | uses: actions-rs/cargo@v1 57 | with: 58 | command: fmt 59 | args: --all -- --check 60 | 61 | - name: Run cargo clippy 62 | uses: actions-rs/cargo@v1 63 | with: 64 | command: clippy 65 | args: -- -D warnings 66 | 67 | # TODO: we should check 68 | # CHANGES_IN_REPO=$(git status --porcelain) 69 | # after this, but I don't know how 70 | - name: Generate Schema 71 | uses: actions-rs/cargo@v1 72 | with: 73 | command: schema 74 | args: --locked 75 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publish package to npm 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | 11 | permissions: 12 | contents: write 13 | 14 | steps: 15 | - name: Checkout repo 16 | uses: actions/checkout@v3 17 | 18 | - uses: actions/setup-node@v3 19 | name: Set up node 20 | with: 21 | node-version: '18.x' 22 | registry-url: 'https://registry.npmjs.org' # set up .npmrc file to publish to npm 23 | 24 | - name: Install dependencies 25 | run: npm install 26 | 27 | - name: Build 28 | run: npm run build 29 | 30 | - name: Publish 31 | run: npm publish 32 | env: 33 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 34 | 35 | - name: Notify on success 36 | if: success() 37 | uses: slackapi/slack-github-action@v1.22.0 38 | env: 39 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 40 | SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK 41 | with: 42 | payload: | 43 | { 44 | "attachments": [ 45 | { 46 | "text": ":shipit: A new version of has been published", 47 | "color": "good" 48 | } 49 | ] 50 | } 51 | 52 | - name: Notify on failure 53 | if: failure() 54 | uses: slackapi/slack-github-action@v1.22.0 55 | env: 56 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 57 | SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK 58 | with: 59 | payload: | 60 | { 61 | "attachments": [ 62 | { 63 | "text": ":boom: Failed to publish ", 64 | "color": "danger" 65 | } 66 | ] 67 | } 68 | -------------------------------------------------------------------------------- /contracts/cosmwasm-vm-test/.github/workflows/Basic.yml: -------------------------------------------------------------------------------- 1 | # Based on https://github.com/actions-rs/example/blob/master/.github/workflows/quickstart.yml 2 | 3 | on: [push, pull_request] 4 | 5 | name: Basic 6 | 7 | jobs: 8 | 9 | test: 10 | name: Test Suite 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout sources 14 | uses: actions/checkout@v2 15 | 16 | - name: Install stable toolchain 17 | uses: actions-rs/toolchain@v1 18 | with: 19 | profile: minimal 20 | toolchain: 1.58.1 21 | target: wasm32-unknown-unknown 22 | override: true 23 | 24 | - name: Run unit tests 25 | uses: actions-rs/cargo@v1 26 | with: 27 | command: unit-test 28 | args: --locked 29 | env: 30 | RUST_BACKTRACE: 1 31 | 32 | - name: Compile WASM contract 33 | uses: actions-rs/cargo@v1 34 | with: 35 | command: wasm 36 | args: --locked 37 | env: 38 | RUSTFLAGS: "-C link-arg=-s" 39 | 40 | lints: 41 | name: Lints 42 | runs-on: ubuntu-latest 43 | steps: 44 | - name: Checkout sources 45 | uses: actions/checkout@v2 46 | 47 | - name: Install stable toolchain 48 | uses: actions-rs/toolchain@v1 49 | with: 50 | profile: minimal 51 | toolchain: 1.58.1 52 | override: true 53 | components: rustfmt, clippy 54 | 55 | - name: Run cargo fmt 56 | uses: actions-rs/cargo@v1 57 | with: 58 | command: fmt 59 | args: --all -- --check 60 | 61 | - name: Run cargo clippy 62 | uses: actions-rs/cargo@v1 63 | with: 64 | command: clippy 65 | args: -- -D warnings 66 | 67 | - name: Generate Schema 68 | uses: actions-rs/cargo@v1 69 | with: 70 | command: schema 71 | args: --locked 72 | 73 | - name: Schema Changes 74 | # fails if any changes not committed 75 | run: git diff --exit-code schema 76 | -------------------------------------------------------------------------------- /test/sample.test.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from "fs"; 2 | import { VMInstance } from '../src/instance'; 3 | import { 4 | BasicBackendApi, 5 | BasicKVIterStorage, 6 | BasicQuerier, 7 | IBackend, 8 | } from '../src/backend'; 9 | 10 | const wasmBytecode = readFileSync('testdata/v1.0/cosmwasm_vm_test.wasm'); 11 | const backend: IBackend = { 12 | backend_api: new BasicBackendApi('terra'), 13 | storage: new BasicKVIterStorage(), 14 | querier: new BasicQuerier(), 15 | }; 16 | 17 | const vm = new VMInstance(backend); 18 | const mockEnv = { 19 | block: { 20 | height: 1337, 21 | time: '2000000000', 22 | chain_id: 'columbus-5', 23 | }, 24 | contract: { 25 | address: 'terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76', 26 | }, 27 | }; 28 | 29 | const mockInfo = { 30 | sender: 'terra1337xewwfv3jdjuz8e0nea9vd8dpugc0k2dcyt3', 31 | funds: [], 32 | }; 33 | 34 | describe('CosmWasmVM', () => { 35 | it('instantiates', async () => { 36 | await vm.build(wasmBytecode); 37 | 38 | const region = vm.instantiate(mockEnv, mockInfo, { count: 20 }); 39 | console.log(region.json); 40 | console.log(vm.backend); 41 | const actual = { 42 | ok: { 43 | attributes: [ 44 | { key: 'method', value: 'instantiate' }, 45 | { 46 | key: 'owner', 47 | value: 'terra1337xewwfv3jdjuz8e0nea9vd8dpugc0k2dcyt3', 48 | }, 49 | { key: 'count', value: '20' }, 50 | ], 51 | data: null, 52 | events: [], 53 | messages: [], 54 | }, 55 | }; 56 | expect(region.json).toEqual(actual); 57 | }); 58 | 59 | it('execute', async () => { 60 | await vm.build(wasmBytecode); 61 | 62 | let region = vm.instantiate(mockEnv, mockInfo, { count: 20 }); 63 | region = vm.execute(mockEnv, mockInfo, { increment: {} }); 64 | console.log(region.json); 65 | console.log(vm.backend); 66 | const actual = { 67 | ok: { 68 | attributes: [{ key: 'method', value: 'try_increment' }], 69 | data: null, 70 | events: [], 71 | messages: [], 72 | }, 73 | }; 74 | expect(region.json).toEqual(actual); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); 3 | const TerserPlugin = require('terser-webpack-plugin'); 4 | 5 | // const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 6 | 7 | const commonConfig = { 8 | mode: 'production', 9 | entry: './src/index.ts', 10 | devtool: 'source-map', 11 | output: { 12 | globalObject: 'this', 13 | }, 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.tsx?$/, 18 | use: 'ts-loader', 19 | exclude: /node_modules/, 20 | }, 21 | ], 22 | }, 23 | resolve: { 24 | extensions: ['.tsx', '.ts', '.js'], 25 | plugins: [new TsconfigPathsPlugin()], 26 | }, 27 | plugins: [ 28 | new webpack.IgnorePlugin({ 29 | resourceRegExp: 30 | /wordlists\/(french|spanish|italian|korean|chinese_simplified|chinese_traditional|japanese)\.json$/, 31 | }), 32 | ], 33 | }; 34 | 35 | const webConfig = { 36 | ...commonConfig, 37 | target: 'web', 38 | output: { 39 | filename: 'bundle.js', 40 | libraryTarget: 'umd', 41 | library: 'cosmwasm-vm-js', 42 | }, 43 | resolve: { 44 | ...commonConfig.resolve, 45 | fallback: { 46 | stream: require.resolve('stream-browserify'), 47 | buffer: require.resolve('buffer'), 48 | assert: require.resolve('assert'), 49 | 'process/browser': require.resolve('process'), 50 | util: require.resolve('util'), 51 | }, 52 | }, 53 | plugins: [ 54 | ...commonConfig.plugins, 55 | new webpack.ProvidePlugin({ 56 | Buffer: ['buffer', 'Buffer'], 57 | }), 58 | new webpack.ProvidePlugin({ 59 | process: 'process/browser', 60 | }), 61 | // new BundleAnalyzerPlugin(), 62 | ], 63 | optimization: { 64 | minimize: true, 65 | minimizer: [ 66 | new TerserPlugin({ 67 | terserOptions: { 68 | keep_classnames: true, 69 | }, 70 | }), 71 | ], 72 | }, 73 | }; 74 | 75 | const nodeConfig = { 76 | ...commonConfig, 77 | target: 'node', 78 | output: { 79 | libraryTarget: 'commonjs', 80 | filename: 'bundle.node.js', 81 | }, 82 | }; 83 | 84 | module.exports = [webConfig, nodeConfig]; 85 | -------------------------------------------------------------------------------- /test/common/test-vm.ts: -------------------------------------------------------------------------------- 1 | import { fromAscii, fromBase64 } from '@cosmjs/encoding'; 2 | import { readFileSync } from 'fs'; 3 | import { VMInstance, IBackend, BasicBackendApi, BasicKVIterStorage, BasicQuerier, Region } from "../../src"; 4 | import { KEY1, VALUE1, KEY2, VALUE2 } from './test-data'; 5 | 6 | export function wrapResult(res: any) { 7 | if (res instanceof Region) 8 | res = res.json; 9 | 10 | if (typeof res !== 'object') 11 | throw new Error('StdResult is not an object'); 12 | 13 | const isOk = !!res.ok; 14 | return { 15 | isOk, 16 | isErr: !isOk, 17 | unwrap: () => res.ok, 18 | unwrap_err: () => res.err, 19 | }; 20 | } 21 | 22 | export const createVM = async (): Promise => { 23 | const wasmByteCode = readFileSync('./testdata/v1.0/hackatom.wasm'); 24 | const backend: IBackend = { 25 | backend_api: new BasicBackendApi('terra'), 26 | storage: new BasicKVIterStorage(), 27 | querier: new BasicQuerier(), 28 | }; 29 | 30 | const vm = new VMInstance(backend); 31 | vm.backend.storage.set(KEY1, VALUE1); 32 | vm.backend.storage.set(KEY2, VALUE2); 33 | 34 | await vm.build(wasmByteCode); 35 | return vm; 36 | }; 37 | 38 | export const writeData = (vm: VMInstance, data: Uint8Array): Region => { 39 | return vm.allocate_bytes(data); 40 | }; 41 | 42 | export const writeObject = (vm: VMInstance, data: [Uint8Array]): Region => { 43 | return vm.allocate_json(data); 44 | }; 45 | 46 | export function parseBase64Response(data: string): any { 47 | let bytes: Uint8Array; 48 | try { 49 | bytes = fromBase64(data); 50 | } catch (_) { 51 | throw new Error(`Data value is not base64-encoded: ${JSON.stringify(data)}`) 52 | } 53 | 54 | let str: string; 55 | try { 56 | str = fromAscii(bytes); 57 | } catch (_) { 58 | throw new Error(`Data value is not ASCII encoded: ${JSON.stringify(bytes)}`) 59 | } 60 | 61 | try { 62 | return JSON.parse(str); 63 | } catch (_) { 64 | throw new Error(`Data value is not valid JSON: ${str}`) 65 | } 66 | } 67 | 68 | export function expectResponseToBeOk(region: Region) { 69 | try { 70 | expect((region.json as { ok: string }).ok).toBeDefined(); 71 | } catch (_) { 72 | throw new Error(`Expected response to be ok; instead got: ${JSON.stringify(region.json)}`); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /contracts/cosmwasm-vm-test/src/msg.rs: -------------------------------------------------------------------------------- 1 | use schemars::JsonSchema; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 5 | pub struct InstantiateMsg { 6 | } 7 | 8 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 9 | #[serde(rename_all = "snake_case")] 10 | pub enum ExecuteMsg { 11 | TestDbWrite {}, 12 | TestDbRemove {}, 13 | TestDbScan {}, 14 | TestDbNext {}, 15 | TestDebugExecute {}, 16 | } 17 | 18 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 19 | #[serde(rename_all = "snake_case")] 20 | pub enum QueryMsg { 21 | TestDbRead {}, 22 | TestAddrHumanize {}, 23 | TestAddrCanonicalize {}, 24 | TestAddrValidate {}, 25 | TestSecp256k1Verify {}, 26 | TestSecp256k1RecoverPubkey {}, 27 | TestEd25519Verify {}, 28 | TestEd25519BatchVerify {}, 29 | TestDebugQuery {}, 30 | TestQueryChain {}, 31 | } 32 | 33 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 34 | pub struct TestDbReadResponse { 35 | } 36 | 37 | // We define a custom struct for each query response 38 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 39 | pub struct TestAddrHumanizeResponse { 40 | } 41 | 42 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 43 | pub struct TestAddrCanonicalizeResponse { 44 | } 45 | 46 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 47 | pub struct TestAddrValidateResponse { 48 | } 49 | 50 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 51 | pub struct TestSecp256k1VerifyResponse { 52 | } 53 | 54 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 55 | pub struct TestSecp256k1RecoverPubkeyResponse { 56 | } 57 | 58 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 59 | pub struct TestEd25519VerifyResponse { 60 | } 61 | 62 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 63 | pub struct TestEd25519BatchVerifyResponse { 64 | } 65 | 66 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 67 | pub struct TestDebugQueryResponse { 68 | } 69 | 70 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 71 | pub struct TestQueryChainResponse { 72 | } 73 | -------------------------------------------------------------------------------- /contracts/cosmwasm-vm-test/src/integration_tests.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use crate::helpers::CwTemplateContract; 4 | use crate::msg::InstantiateMsg; 5 | use cosmwasm_std::{Addr, Coin, Empty, Uint128}; 6 | use cw_multi_test::{App, AppBuilder, Contract, ContractWrapper, Executor}; 7 | 8 | pub fn contract_template() -> Box> { 9 | let contract = ContractWrapper::new( 10 | crate::contract::execute, 11 | crate::contract::instantiate, 12 | crate::contract::query, 13 | ); 14 | Box::new(contract) 15 | } 16 | 17 | const USER: &str = "USER"; 18 | const ADMIN: &str = "ADMIN"; 19 | const NATIVE_DENOM: &str = "denom"; 20 | 21 | fn mock_app() -> App { 22 | AppBuilder::new().build(|router, _, storage| { 23 | router 24 | .bank 25 | .init_balance( 26 | storage, 27 | &Addr::unchecked(USER), 28 | vec![Coin { 29 | denom: NATIVE_DENOM.to_string(), 30 | amount: Uint128::new(1), 31 | }], 32 | ) 33 | .unwrap(); 34 | }) 35 | } 36 | 37 | fn proper_instantiate() -> (App, CwTemplateContract) { 38 | let mut app = mock_app(); 39 | let cw_template_id = app.store_code(contract_template()); 40 | 41 | let msg = InstantiateMsg { count: 1i32 }; 42 | let cw_template_contract_addr = app 43 | .instantiate_contract( 44 | cw_template_id, 45 | Addr::unchecked(ADMIN), 46 | &msg, 47 | &[], 48 | "test", 49 | None, 50 | ) 51 | .unwrap(); 52 | 53 | let cw_template_contract = CwTemplateContract(cw_template_contract_addr); 54 | 55 | (app, cw_template_contract) 56 | } 57 | 58 | mod count { 59 | use super::*; 60 | use crate::msg::ExecuteMsg; 61 | 62 | #[test] 63 | fn count() { 64 | let (mut app, cw_template_contract) = proper_instantiate(); 65 | 66 | let msg = ExecuteMsg::Increment {}; 67 | let cosmos_msg = cw_template_contract.call(msg).unwrap(); 68 | app.execute(Addr::unchecked(USER), cosmos_msg).unwrap(); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@terran-one/cosmwasm-vm-js", 3 | "version": "0.2.16", 4 | "license": "MIT", 5 | "author": "TerranOne", 6 | "main": "dist/index.js", 7 | "types": "dist/index.d.ts", 8 | "type": "commonjs", 9 | "files": [ 10 | "dist" 11 | ], 12 | "publishConfig": { 13 | "registry": "https://registry.npmjs.org" 14 | }, 15 | "scripts": { 16 | "build": "tsc --module commonjs && webpack --mode production", 17 | "size": "size-limit", 18 | "test": "dts test", 19 | "lint": "dts lint", 20 | "//degit:contracts": "cd contracts && npx degit CosmWasm/cosmwasm/contracts/hackatom#0.16 hackatom", 21 | "prepublishOnly": "npm run build" 22 | }, 23 | "husky": { 24 | "hooks": { 25 | "pre-commit": "dts lint --fix" 26 | } 27 | }, 28 | "prettier": { 29 | "printWidth": 80, 30 | "semi": true, 31 | "singleQuote": true, 32 | "trailingComma": "es5" 33 | }, 34 | "engines": { 35 | "node": ">=12" 36 | }, 37 | "size-limit": [ 38 | { 39 | "path": "dist/cw-vm.ts.cjs.production.min.js", 40 | "limit": "10 KB" 41 | }, 42 | { 43 | "path": "dist/cw-vm.ts.esm.js", 44 | "limit": "10 KB" 45 | } 46 | ], 47 | "peerDependencies": { 48 | "@babel/plugin-syntax-flow": "7.18.6", 49 | "@babel/plugin-transform-react-jsx": "7.19.0", 50 | "@babel/plugin-syntax-jsx": "7.18.6" 51 | }, 52 | "devDependencies": { 53 | "@babel/preset-typescript": "^7.16.7", 54 | "@size-limit/preset-small-lib": "^7.0.8", 55 | "@tsconfig/recommended": "^1.0.1", 56 | "@types/jest": "^27.5.2", 57 | "dts-cli": "^1.5.1", 58 | "husky": "^7.0.4", 59 | "majestic": "^1.8.1", 60 | "size-limit": "^7.0.8", 61 | "stream-browserify": "^3.0.0", 62 | "terser-webpack-plugin": "^5.3.6", 63 | "ts-jest": "^27.1.4", 64 | "ts-loader": "^9.3.1", 65 | "tsconfig-paths-webpack-plugin": "^4.0.0", 66 | "tslib": "^2.4.0", 67 | "typescript": "^4.6.3", 68 | "webpack": "^5.74.0", 69 | "webpack-cli": "^4.10.0" 70 | }, 71 | "dependencies": { 72 | "@cosmjs/crypto": "^0.28.4", 73 | "@cosmjs/encoding": "^0.28.4", 74 | "@polkadot/util": "^10.1.11", 75 | "@polkadot/util-crypto": "^10.1.11", 76 | "@types/elliptic": "^6.4.14", 77 | "@types/secp256k1": "^4.0.3", 78 | "bech32": "^2.0.0", 79 | "elliptic": "^6.5.4", 80 | "immutable": "^4.1.0", 81 | "process": "^0.11.10", 82 | "secp256k1": "^4.0.3", 83 | "synchronized-promise": "^0.3.1", 84 | "util": "^0.12.4" 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/backend/backendApi.ts: -------------------------------------------------------------------------------- 1 | import { fromBech32, normalizeBech32 } from '@cosmjs/encoding'; 2 | import { bech32 } from 'bech32'; 3 | 4 | export interface IGasInfo { 5 | cost: number; 6 | externally_used: number; 7 | 8 | with_cost(cost: number): IGasInfo; 9 | 10 | with_externally_used(externally_used: number): IGasInfo; 11 | 12 | free(): IGasInfo; 13 | } 14 | 15 | export class GasInfo implements IGasInfo { 16 | constructor(public cost: number, public externally_used: number) {} 17 | 18 | with_cost(cost: number): IGasInfo { 19 | return new GasInfo(cost, 0); 20 | } 21 | 22 | with_externally_used(externally_used: number): IGasInfo { 23 | return new GasInfo(0, externally_used); 24 | } 25 | 26 | free(): IGasInfo { 27 | return new GasInfo(0, 0); 28 | } 29 | } 30 | 31 | export interface IBackendApi { 32 | bech32_prefix: string; 33 | canonical_address(human: string): Uint8Array; 34 | human_address(canonical: Uint8Array): string; 35 | } 36 | 37 | export class BasicBackendApi implements IBackendApi { 38 | // public GAS_COST_CANONICALIZE = 55; 39 | public CANONICAL_LENGTH = 54; 40 | public EXCESS_PADDING = 6; 41 | constructor(public bech32_prefix: string = 'terra') {} 42 | 43 | public canonical_address(human: string): Uint8Array { 44 | if (human.length === 0) { 45 | throw new Error('Empty human address'); 46 | } 47 | 48 | const normalized = normalizeBech32(human); 49 | 50 | if (normalized.length < 3) { 51 | throw new Error(`canonical_address: Address too short: ${normalized}`); 52 | } 53 | 54 | if (normalized.length > this.CANONICAL_LENGTH) { 55 | throw new Error(`canonical_address: Address too long: ${normalized}`); 56 | } 57 | 58 | return fromBech32(normalized).data; 59 | } 60 | 61 | public human_address(canonical: Uint8Array): string { 62 | if (canonical.length === 0) { 63 | throw new Error('human_address: Empty canonical address'); 64 | } 65 | 66 | if (canonical.length !== this.CANONICAL_LENGTH) { 67 | throw new Error( 68 | `human_address: canonical address length not correct: ${canonical.length}` 69 | ); 70 | } 71 | 72 | // Remove excess padding, otherwise bech32.encode will throw "Exceeds length limit" 73 | // error when normalized is greater than 48 in length. 74 | const normalized = 75 | canonical.length - this.EXCESS_PADDING >= 48 76 | ? canonical.slice(0, this.CANONICAL_LENGTH - this.EXCESS_PADDING) 77 | : canonical; 78 | return bech32.encode(this.bech32_prefix, bech32.toWords(normalized)); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /test/integration/cyberpunk.test.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs'; 2 | import { VMInstance } from "../../src/instance"; 3 | import { 4 | BasicBackendApi, 5 | BasicKVIterStorage, 6 | BasicQuerier, 7 | IBackend, 8 | } from '../../src/backend'; 9 | import type { Env, MessageInfo } from '../../src/types'; 10 | import { parseBase64Response, wrapResult } from '../common/test-vm'; 11 | 12 | const wasmBytecode = readFileSync('testdata/v1.1/cyberpunk.wasm'); 13 | const backend: IBackend = { 14 | backend_api: new BasicBackendApi('terra'), 15 | storage: new BasicKVIterStorage(), 16 | querier: new BasicQuerier(), 17 | }; 18 | 19 | const creator = 'terra1337xewwfv3jdjuz8e0nea9vd8dpugc0k2dcyt3'; 20 | 21 | const mockEnv: Env = { 22 | block: { 23 | height: 1337, 24 | time: '2000000000', 25 | chain_id: 'columbus-5', 26 | }, 27 | contract: { address: 'terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76' }, 28 | transaction: null, 29 | }; 30 | 31 | const mockInfo: MessageInfo = { 32 | sender: creator, 33 | funds: [] 34 | }; 35 | 36 | let vm: VMInstance; 37 | describe('cyberpunk', () => { 38 | beforeEach(async () => { 39 | vm = new VMInstance(backend); 40 | await vm.build(wasmBytecode); 41 | }); 42 | 43 | // port of https://github.com/CosmWasm/cosmwasm/blob/f6a0485088f1084379a5655bcc2956526290c09f/contracts/cyberpunk/tests/integration.rs#L30 44 | it.skip('execute_argon2', async () => { // gas limit not implemented 45 | // Arrange 46 | vm = new VMInstance(backend, 100_000_000_000_000); // TODO: implement gas limit on VM 47 | const initRes = vm.instantiate(mockEnv, mockInfo, {}).json as any; 48 | expect(initRes.messages.length).toStrictEqual(0); 49 | 50 | const gasBefore = vm.remainingGas; 51 | 52 | // Act 53 | const executeRes = vm.execute( 54 | mockEnv, 55 | mockInfo, 56 | { 57 | mem_cost: 256, 58 | time_cost: 5, 59 | } 60 | ).json; 61 | 62 | // Assert 63 | // TODO 64 | }); 65 | 66 | it('test_env', async () => { 67 | // Arrange 68 | const initRes = wrapResult(vm.instantiate(mockEnv, mockInfo, {})).unwrap(); 69 | expect(initRes.messages.length).toStrictEqual(0); 70 | 71 | // Act 1 72 | const res = wrapResult(vm.execute(mockEnv, mockInfo, { mirror_env: {} })).unwrap(); 73 | 74 | // Assert 1 75 | expect(res.data).toBeDefined(); 76 | let receivedEnv = parseBase64Response(res.data); 77 | expect(receivedEnv).toEqual(mockEnv); 78 | 79 | // Act 2 80 | const data = wrapResult(vm.query(mockEnv, { mirror_env: {} })).unwrap(); 81 | receivedEnv = parseBase64Response(data); 82 | expect(receivedEnv).toEqual(mockEnv); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /test/common/test-data.ts: -------------------------------------------------------------------------------- 1 | import { fromHex, toAscii } from '@cosmjs/encoding'; 2 | 3 | // Constants from https://github.com/cosmwasm/cosmwasm/blob/5e04c3c1aa7e278626196de43aa18e9bedbc6000/packages/vm/src/imports.rs#L499 4 | 5 | // In Rust, b"XXX" is the same as creating a bytestring of the ASCII-encoded string "XXX". 6 | export const KEY1 = toAscii('ant'); 7 | export const VALUE1 = toAscii('insect'); 8 | export const KEY2 = toAscii('tree'); 9 | export const VALUE2 = toAscii('plant'); 10 | 11 | export const ECDSA_HASH_HEX = fromHex( 12 | '5ae8317d34d1e595e3fa7247db80c0af4320cce1116de187f8f7e2e099c0d8d0' 13 | ); 14 | export const ECDSA_SIG_HEX = fromHex( 15 | '207082eb2c3dfa0b454e0906051270ba4074ac93760ba9e7110cd9471475111151eb0dbbc9920e72146fb564f99d039802bf6ef2561446eb126ef364d21ee9c4' 16 | ); 17 | export const ECDSA_PUBKEY_HEX = fromHex( 18 | '04051c1ee2190ecfb174bfe4f90763f2b4ff7517b70a2aec1876ebcfd644c4633fb03f3cfbd94b1f376e34592d9d41ccaf640bb751b00a1fadeb0c01157769eb73' 19 | ); 20 | 21 | export const EDDSA_MSG_HEX = fromHex(''); 22 | export const EDDSA_SIG_HEX = fromHex( 23 | 'e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b' 24 | ); 25 | export const EDDSA_PUBKEY_HEX = fromHex( 26 | 'd75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a' 27 | ); 28 | 29 | export const SECP256K1_MSG_HEX = fromHex( 30 | '5ae8317d34d1e595e3fa7247db80c0af4320cce1116de187f8f7e2e099c0d8d0' 31 | ); 32 | export const SECP256K1_SIG_HEX = fromHex( 33 | '45c0b7f8c09a9e1f1cea0c25785594427b6bf8f9f878a8af0b1abbb48e16d0920d8becd0c220f67c51217eecfd7184ef0732481c843857e6bc7fc095c4f6b788' 34 | ); 35 | export const RECOVER_PARAM = 1; 36 | export const SECP256K1_PUBKEY_HEX = fromHex( 37 | '044a071e8a6e10aada2b8cf39fa3b5fb3400b04e99ea8ae64ceea1a977dbeaf5d5f8c8fbd10b71ab14cd561f7df8eb6da50f8a8d81ba564342244d26d1d4211595' 38 | ); 39 | 40 | export const ED25519_MSG_HEX = fromHex('72'); 41 | export const ED25519_SIG_HEX = fromHex( 42 | '92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00' 43 | ); 44 | export const ED25519_PUBKEY_HEX = fromHex( 45 | '3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c' 46 | ); 47 | 48 | export const SECP256K1_MESSAGE_HEX = fromHex('5c868fedb8026979ebd26f1ba07c27eedf4ff6d10443505a96ecaf21ba8c4f0937b3cd23ffdc3dd429d4cd1905fb8dbcceeff1350020e18b58d2ba70887baa3a9b783ad30d3fbf210331cdd7df8d77defa398cdacdfc2e359c7ba4cae46bb74401deb417f8b912a1aa966aeeba9c39c7dd22479ae2b30719dca2f2206c5eb4b7'); 49 | export const ETHEREUM_MESSAGE = 'connect all the things'; 50 | export const ETHEREUM_SIGNATURE_HEX = fromHex('dada130255a447ecf434a2df9193e6fbba663e4546c35c075cd6eea21d8c7cb1714b9b65a4f7f604ff6aad55fba73f8c36514a512bbbba03709b37069194f8a41b'); 51 | export const ETHEREUM_SIGNER_ADDRESS = '0x12890D2cce102216644c59daE5baed380d84830c'; 52 | export const ED25519_MESSAGE_HEX = fromHex('af82'); 53 | export const ED25519_SIGNATURE_HEX = fromHex('6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a'); 54 | export const ED25519_PUBLIC_KEY_HEX = fromHex('fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025'); 55 | export const ED25519_MESSAGE2_HEX = fromHex('72'); 56 | export const ED25519_SIGNATURE2_HEX = fromHex('92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00'); 57 | export const ED25519_PUBLIC_KEY2_HEX = fromHex('3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c'); 58 | 59 | -------------------------------------------------------------------------------- /contracts/cw-vm-test/Importing.md: -------------------------------------------------------------------------------- 1 | # Importing 2 | 3 | In [Publishing](./Publishing.md), we discussed how you can publish your contract to the world. 4 | This looks at the flip-side, how can you use someone else's contract (which is the same 5 | question as how they will use your contract). Let's go through the various stages. 6 | 7 | ## Verifying Artifacts 8 | 9 | Before using remote code, you most certainly want to verify it is honest. 10 | 11 | The simplest audit of the repo is to simply check that the artifacts in the repo 12 | are correct. This involves recompiling the claimed source with the claimed builder 13 | and validating that the locally compiled code (hash) matches the code hash that was 14 | uploaded. This will verify that the source code is the correct preimage. Which allows 15 | one to audit the original (Rust) source code, rather than looking at wasm bytecode. 16 | 17 | We have a script to do this automatic verification steps that can 18 | easily be run by many individuals. Please check out 19 | [`cosmwasm-verify`](https://github.com/CosmWasm/cosmwasm-verify/blob/master/README.md) 20 | to see a simple shell script that does all these steps and easily allows you to verify 21 | any uploaded contract. 22 | 23 | ## Reviewing 24 | 25 | Once you have done the quick programatic checks, it is good to give at least a quick 26 | look through the code. A glance at `examples/schema.rs` to make sure it is outputing 27 | all relevant structs from `contract.rs`, and also ensure `src/lib.rs` is just the 28 | default wrapper (nothing funny going on there). After this point, we can dive into 29 | the contract code itself. Check the flows for the execute methods, any invariants and 30 | permission checks that should be there, and a reasonable data storage format. 31 | 32 | You can dig into the contract as far as you want, but it is important to make sure there 33 | are no obvious backdoors at least. 34 | 35 | ## Decentralized Verification 36 | 37 | It's not very practical to do a deep code review on every dependency you want to use, 38 | which is a big reason for the popularity of code audits in the blockchain world. We trust 39 | some experts review in lieu of doing the work ourselves. But wouldn't it be nice to do this 40 | in a decentralized manner and peer-review each other's contracts? Bringing in deeper domain 41 | knowledge and saving fees. 42 | 43 | Luckily, there is an amazing project called [crev](https://github.com/crev-dev/cargo-crev/blob/master/cargo-crev/README.md) 44 | that provides `A cryptographically verifiable code review system for the cargo (Rust) package manager`. 45 | 46 | I highly recommend that CosmWasm contract developers get set up with this. At minimum, we 47 | can all add a review on a package that programmatically checked out that the json schemas 48 | and wasm bytecode do match the code, and publish our claim, so we don't all rely on some 49 | central server to say it validated this. As we go on, we can add deeper reviews on standard 50 | packages. 51 | 52 | If you want to use `cargo-crev`, please follow their 53 | [getting started guide](https://github.com/crev-dev/cargo-crev/blob/master/cargo-crev/src/doc/getting_started.md) 54 | and once you have made your own *proof repository* with at least one *trust proof*, 55 | please make a PR to the [`cawesome-wasm`]() repo with a link to your repo and 56 | some public name or pseudonym that people know you by. This allows people who trust you 57 | to also reuse your proofs. 58 | 59 | There is a [standard list of proof repos](https://github.com/crev-dev/cargo-crev/wiki/List-of-Proof-Repositories) 60 | with some strong rust developers in there. This may cover dependencies like `serde` and `snafu` 61 | but will not hit any CosmWasm-related modules, so we look to bootstrap a very focused 62 | review community. 63 | -------------------------------------------------------------------------------- /contracts/cosmwasm-vm-test/Importing.md: -------------------------------------------------------------------------------- 1 | # Importing 2 | 3 | In [Publishing](./Publishing.md), we discussed how you can publish your contract to the world. 4 | This looks at the flip-side, how can you use someone else's contract (which is the same 5 | question as how they will use your contract). Let's go through the various stages. 6 | 7 | ## Verifying Artifacts 8 | 9 | Before using remote code, you most certainly want to verify it is honest. 10 | 11 | The simplest audit of the repo is to simply check that the artifacts in the repo 12 | are correct. This involves recompiling the claimed source with the claimed builder 13 | and validating that the locally compiled code (hash) matches the code hash that was 14 | uploaded. This will verify that the source code is the correct preimage. Which allows 15 | one to audit the original (Rust) source code, rather than looking at wasm bytecode. 16 | 17 | We have a script to do this automatic verification steps that can 18 | easily be run by many individuals. Please check out 19 | [`cosmwasm-verify`](https://github.com/CosmWasm/cosmwasm-verify/blob/master/README.md) 20 | to see a simple shell script that does all these steps and easily allows you to verify 21 | any uploaded contract. 22 | 23 | ## Reviewing 24 | 25 | Once you have done the quick programatic checks, it is good to give at least a quick 26 | look through the code. A glance at `examples/schema.rs` to make sure it is outputing 27 | all relevant structs from `contract.rs`, and also ensure `src/lib.rs` is just the 28 | default wrapper (nothing funny going on there). After this point, we can dive into 29 | the contract code itself. Check the flows for the execute methods, any invariants and 30 | permission checks that should be there, and a reasonable data storage format. 31 | 32 | You can dig into the contract as far as you want, but it is important to make sure there 33 | are no obvious backdoors at least. 34 | 35 | ## Decentralized Verification 36 | 37 | It's not very practical to do a deep code review on every dependency you want to use, 38 | which is a big reason for the popularity of code audits in the blockchain world. We trust 39 | some experts review in lieu of doing the work ourselves. But wouldn't it be nice to do this 40 | in a decentralized manner and peer-review each other's contracts? Bringing in deeper domain 41 | knowledge and saving fees. 42 | 43 | Luckily, there is an amazing project called [crev](https://github.com/crev-dev/cargo-crev/blob/master/cargo-crev/README.md) 44 | that provides `A cryptographically verifiable code review system for the cargo (Rust) package manager`. 45 | 46 | I highly recommend that CosmWasm contract developers get set up with this. At minimum, we 47 | can all add a review on a package that programmatically checked out that the json schemas 48 | and wasm bytecode do match the code, and publish our claim, so we don't all rely on some 49 | central server to say it validated this. As we go on, we can add deeper reviews on standard 50 | packages. 51 | 52 | If you want to use `cargo-crev`, please follow their 53 | [getting started guide](https://github.com/crev-dev/cargo-crev/blob/master/cargo-crev/src/doc/getting_started.md) 54 | and once you have made your own *proof repository* with at least one *trust proof*, 55 | please make a PR to the [`cawesome-wasm`]() repo with a link to your repo and 56 | some public name or pseudonym that people know you by. This allows people who trust you 57 | to also reuse your proofs. 58 | 59 | There is a [standard list of proof repos](https://github.com/crev-dev/cargo-crev/wiki/List-of-Proof-Repositories) 60 | with some strong rust developers in there. This may cover dependencies like `serde` and `snafu` 61 | but will not hit any CosmWasm-related modules, so we look to bootstrap a very focused 62 | review community. 63 | -------------------------------------------------------------------------------- /test/vm.test.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs'; 2 | import { BasicKVIterStorage, VMInstance } from '../src'; 3 | import { BasicBackendApi, BasicQuerier, IBackend } from '../src/backend'; 4 | import { writeData } from './common/test-vm'; 5 | import * as testData from './common/test-data'; 6 | 7 | const wasmByteCode = readFileSync('testdata/v1.0/cosmwasm_vm_test.wasm'); 8 | const cwMachineBytecode = readFileSync('testdata/v1.0/cw_machine-aarch64.wasm'); 9 | const backend: IBackend = { 10 | backend_api: new BasicBackendApi('terra'), 11 | storage: new BasicKVIterStorage(), 12 | querier: new BasicQuerier(), 13 | }; 14 | 15 | const vm = new VMInstance(backend); 16 | const mockEnv = { 17 | block: { 18 | height: 1337, 19 | time: '2000000000', 20 | chain_id: 'columbus-5', 21 | }, 22 | contract: { 23 | address: 'terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76', 24 | }, 25 | }; 26 | 27 | const mockInfo = { 28 | sender: 'terra1337xewwfv3jdjuz8e0nea9vd8dpugc0k2dcyt3', 29 | funds: [], 30 | }; 31 | 32 | describe('CosmWasmVM', () => { 33 | it('instantiates', async () => { 34 | await vm.build(wasmByteCode); 35 | 36 | const region = vm.instantiate(mockEnv, mockInfo, { count: 20 }); 37 | const actual = { 38 | ok: { 39 | attributes: [ 40 | { key: 'method', value: 'instantiate' }, 41 | { 42 | key: 'owner', 43 | value: 'terra1337xewwfv3jdjuz8e0nea9vd8dpugc0k2dcyt3', 44 | }, 45 | { key: 'count', value: '20' }, 46 | ], 47 | data: null, 48 | events: [], 49 | messages: [], 50 | }, 51 | }; 52 | expect(region.json).toEqual(actual); 53 | }); 54 | 55 | it('execute', async () => { 56 | await vm.build(wasmByteCode); 57 | 58 | let region = vm.instantiate(mockEnv, mockInfo, { count: 20 }); 59 | region = vm.execute(mockEnv, mockInfo, { increment: {} }); 60 | const actual = { 61 | ok: { 62 | attributes: [{ key: 'method', value: 'try_increment' }], 63 | data: null, 64 | events: [], 65 | messages: [], 66 | }, 67 | }; 68 | expect(region.json).toEqual(actual); 69 | }); 70 | 71 | it('reply', async () => { 72 | await vm.build(cwMachineBytecode); 73 | 74 | let region = vm.instantiate(mockEnv, mockInfo, {}); 75 | region = vm.reply(mockEnv, { 76 | id: 1, 77 | result: { 78 | ok: { 79 | events: [{ type: 'wasm', attributes: [{ key: 'k', value: 'v' }] }], 80 | data: null, 81 | }, 82 | }, 83 | }); 84 | expect('ok' in region.json).toBeTruthy(); 85 | 86 | region = vm.reply(mockEnv, { 87 | id: 2, 88 | result: { 89 | ok: { 90 | events: [{ type: 'wasm', attributes: [{ key: 'k', value: 'v' }] }], 91 | data: null, 92 | }, 93 | }, 94 | }); 95 | expect('error' in region.json).toBeTruthy(); 96 | }); 97 | 98 | it('serializes', async () => { 99 | // Arrange 100 | await vm.build(wasmByteCode); 101 | vm.instantiate(mockEnv, mockInfo, { count: 20 }); 102 | 103 | // Act 104 | const json = JSON.stringify(vm); 105 | 106 | // Assert 107 | expect(json).toBeDefined(); 108 | }); 109 | 110 | it('serializes after edda usage', async () => { 111 | // Arrange 112 | await vm.build(wasmByteCode); 113 | vm.instantiate(mockEnv, mockInfo, { count: 20 }); 114 | 115 | const hashPtr = writeData(vm, testData.EDDSA_MSG_HEX); 116 | const sigPtr = writeData(vm, testData.EDDSA_SIG_HEX); 117 | const pubkeyPtr = writeData(vm, testData.EDDSA_PUBKEY_HEX); 118 | vm.do_ed25519_verify(hashPtr, sigPtr, pubkeyPtr); 119 | 120 | // Act 121 | const json = JSON.stringify(vm); 122 | 123 | // Assert 124 | expect(json).toBeDefined(); 125 | }); 126 | }); 127 | -------------------------------------------------------------------------------- /test/integration/burner.test.ts: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////// 2 | // Burner is an example contract for migration (introduced in CW 0.9). 3 | // It cannot be instantiated, but an existing contract can be migrated 4 | // to the Burner to permanently burn the contract and perform basic 5 | // cleanup. 6 | // ----- 7 | // Rust Sources: https://github.com/CosmWasm/cosmwasm/tree/main/contracts/burner 8 | import { readFileSync } from 'fs'; 9 | import { VMInstance } from "../../src/instance"; 10 | import { 11 | BasicBackendApi, 12 | BasicKVIterStorage, 13 | BasicQuerier, 14 | IBackend, 15 | Order, 16 | } from '../../src/backend'; 17 | import { toAscii } from '@cosmjs/encoding'; 18 | import { Env, MessageInfo } from '../../src/types'; 19 | 20 | class MockQuerier extends BasicQuerier { 21 | handleQuery(request: any): any { 22 | return { amount: [{ denom: 'earth', amount: '1000' }] } 23 | } 24 | } 25 | 26 | const wasmBytecode = readFileSync('testdata/v1.1/burner.wasm'); 27 | const backend: IBackend = { 28 | backend_api: new BasicBackendApi('terra'), 29 | storage: new BasicKVIterStorage(), 30 | querier: new MockQuerier(), 31 | }; 32 | 33 | const creator = 'terra1337xewwfv3jdjuz8e0nea9vd8dpugc0k2dcyt3'; 34 | const payout = 'terra163u9pnx5sucsk537zpn82fzxjgdp44xehfdy4x'; 35 | 36 | const mockEnv: Env = { 37 | block: { 38 | height: 1337, 39 | time: '2000000000', 40 | chain_id: 'columbus-5', 41 | }, 42 | contract: { address: 'terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76' } 43 | }; 44 | 45 | let vm: VMInstance; 46 | describe('burner', () => { 47 | beforeEach(async () => { 48 | vm = new VMInstance(backend); 49 | await vm.build(wasmBytecode); 50 | }); 51 | 52 | // port of https://github.com/CosmWasm/cosmwasm/blob/f6a0485088f1084379a5655bcc2956526290c09f/contracts/burner/tests/integration.rs#L32 53 | it('instantiate_fails', async () => { 54 | // Arrange 55 | const mockInfo: MessageInfo = { 56 | sender: creator, 57 | funds: [ 58 | { denom: 'earth', amount: '1000' }, 59 | ], 60 | } 61 | 62 | // Act 63 | const instantiateResponse = vm.instantiate(mockEnv, mockInfo, {}); 64 | 65 | // Assert 66 | expect(instantiateResponse.json).toEqual({ 67 | error: 'Generic error: You can only use this contract for migrations', 68 | }); 69 | }); 70 | 71 | // port of https://github.com/CosmWasm/cosmwasm/blob/f6a0485088f1084379a5655bcc2956526290c09f/contracts/burner/tests/integration.rs#L47 72 | // TODO: querier not yet implemented 73 | // test verifies two things: 74 | // 1) remaining coins in storage (123456 gold) are sent to payout address 75 | // 2) storage is purged 76 | it('migrate_cleans_up_data', async () => { 77 | // Arrange 78 | // TODO: VM instance w/ coin data & Bank module 79 | // const vm = new VMInstance(backend, [{ denom: 'gold', amount: '123456' }]); 80 | const storage = vm.backend.storage; 81 | 82 | storage.set(toAscii('foo'), toAscii('bar')); 83 | storage.set(toAscii('key2'), toAscii('data2')); 84 | storage.set(toAscii('key3'), toAscii('cool stuff')); 85 | 86 | // TODO: support scan(null, null, Order) 87 | let iterId = storage.scan(null, null, Order.Ascending); 88 | let cnt = storage.all(iterId); 89 | expect(cnt.length).toStrictEqual(3); 90 | 91 | const migrateMsg = { payout }; 92 | 93 | // Act 94 | const res = vm.migrate(mockEnv, migrateMsg).json as any; 95 | 96 | // Assert 97 | expect(res.ok.messages.length).toStrictEqual(1); 98 | expect(res.ok.messages[0]).toBeDefined(); 99 | // TODO: msg is SubMsg w/ BankMsg::Send to payout of all coins in contract 100 | iterId = storage.scan(null, null, Order.Ascending); 101 | cnt = storage.all(iterId); 102 | expect(cnt.length).toStrictEqual(0); 103 | }); 104 | }); 105 | -------------------------------------------------------------------------------- /src/memory.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Wrapper class for the Region data structure, which describes a region of 3 | * WebAssembly's linear memory that has been allocated by the VM. 4 | * 5 | * Note that this class is passed a pointer to the data structure, and the 6 | * Region's members (offset: u32, capacity: u32, length: u32) are read from 7 | * that pointer as they are laid out in the data structure. 8 | */ 9 | export class Region { 10 | /** 11 | * The region's data structure laid out in memory. 12 | */ 13 | public region_info: Uint32Array; 14 | 15 | /** 16 | * @param memory The WebAssembly.Memory object that this region is associated 17 | * @param ptr The offset of the region's data structure in memory 18 | */ 19 | constructor(public memory: WebAssembly.Memory, public ptr: number) { 20 | this.region_info = new Uint32Array(memory.buffer, ptr, 3); 21 | } 22 | 23 | public get offset(): number { 24 | return this.region_info[0]; 25 | } 26 | 27 | public set offset(val: number) { 28 | this.region_info[0] = val; 29 | } 30 | 31 | public set capacity(val: number) { 32 | this.region_info[1] = val; 33 | } 34 | 35 | public get capacity(): number { 36 | return this.region_info[1]; 37 | } 38 | 39 | public set length(val: number) { 40 | this.region_info[2] = val; 41 | } 42 | 43 | public get length(): number { 44 | return this.region_info[2]; 45 | } 46 | 47 | /** 48 | * Get a byte-slice of the region's data. 49 | */ 50 | public get data(): Uint8Array { 51 | return this.read(); 52 | } 53 | 54 | /** 55 | * Get a byte-slice of the entire writable region. 56 | */ 57 | public get slice(): Uint8Array { 58 | return new Uint8Array(this.memory.buffer, this.offset, this.capacity); 59 | } 60 | 61 | /** 62 | * Get a base64-encoded string of the region's data. 63 | */ 64 | public get b64(): string { 65 | return this.read_b64(); 66 | } 67 | 68 | /** 69 | * Get a string view of the region's data. 70 | */ 71 | public get str(): string { 72 | return this.read_str(); 73 | } 74 | 75 | /** 76 | * Parse the object of the region's data as JSON. 77 | */ 78 | public get json(): object { 79 | return this.read_json(); 80 | } 81 | 82 | /** 83 | * Write a byte-slice to the region. 84 | * @param bytes The bytes to write to the region 85 | */ 86 | public write(bytes: Uint8Array): void { 87 | this.slice.set(bytes); 88 | this.length = bytes.length; 89 | } 90 | 91 | /** 92 | * Write bytes encoded as base64 to the region. 93 | * @param b64 bytes encoded as base64 94 | */ 95 | public write_b64(b64: string): void { 96 | this.write(Buffer.from(b64, 'base64')); 97 | } 98 | 99 | /** 100 | * Write a string to the region. 101 | * @param str The string to write to the region 102 | */ 103 | public write_str(str: string): void { 104 | this.write(new TextEncoder().encode(str)); 105 | } 106 | 107 | /** 108 | * Write a JSON object to the region as a string. 109 | * @param obj The object to write to the region 110 | */ 111 | public write_json(obj: object): void { 112 | this.write_str(JSON.stringify(obj)); 113 | } 114 | 115 | /** 116 | * Reads the region's data as a Uint8Array. 117 | * @returns The byte-slice of the region's data. 118 | */ 119 | public read(): Uint8Array { 120 | return new Uint8Array(this.memory.buffer, this.offset, this.length); 121 | } 122 | 123 | public read_b64(): string { 124 | return Buffer.from(this.read()).toString('base64'); 125 | } 126 | 127 | /** 128 | * Reads the region's data as a String. 129 | * @returns The region's data as a string. 130 | */ 131 | public read_str(): string { 132 | return new TextDecoder().decode(this.read()); 133 | } 134 | 135 | /** 136 | * Parse the region's data as JSON. 137 | * @returns The region's data as a JSON object. 138 | */ 139 | public read_json(): object { 140 | return JSON.parse(this.read_str()); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /contracts/cosmwasm-vm-test/README.md: -------------------------------------------------------------------------------- 1 | # CosmWasm Starter Pack 2 | 3 | This is a template to build smart contracts in Rust to run inside a 4 | [Cosmos SDK](https://github.com/cosmos/cosmos-sdk) module on all chains that enable it. 5 | To understand the framework better, please read the overview in the 6 | [cosmwasm repo](https://github.com/CosmWasm/cosmwasm/blob/master/README.md), 7 | and dig into the [cosmwasm docs](https://www.cosmwasm.com). 8 | This assumes you understand the theory and just want to get coding. 9 | 10 | ## Creating a new repo from template 11 | 12 | Assuming you have a recent version of rust and cargo (v1.58.1+) installed 13 | (via [rustup](https://rustup.rs/)), 14 | then the following should get you a new repo to start a contract: 15 | 16 | Install [cargo-generate](https://github.com/ashleygwilliams/cargo-generate) and cargo-run-script. 17 | Unless you did that before, run this line now: 18 | 19 | ```sh 20 | cargo install cargo-generate --features vendored-openssl 21 | cargo install cargo-run-script 22 | ``` 23 | 24 | Now, use it to create your new contract. 25 | Go to the folder in which you want to place it and run: 26 | 27 | 28 | **Latest: 1.0.0** 29 | 30 | ```sh 31 | cargo generate --git https://github.com/CosmWasm/cw-template.git --name PROJECT_NAME 32 | ```` 33 | 34 | For cloning minimal code repo: 35 | 36 | ```sh 37 | cargo generate --git https://github.com/CosmWasm/cw-template.git --branch 1.0-minimal --name PROJECT_NAME 38 | ``` 39 | 40 | **Older Version** 41 | 42 | Pass version as branch flag: 43 | 44 | ```sh 45 | cargo generate --git https://github.com/CosmWasm/cw-template.git --branch --name PROJECT_NAME 46 | ```` 47 | 48 | Example: 49 | 50 | ```sh 51 | cargo generate --git https://github.com/CosmWasm/cw-template.git --branch 0.16 --name PROJECT_NAME 52 | ``` 53 | 54 | You will now have a new folder called `PROJECT_NAME` (I hope you changed that to something else) 55 | containing a simple working contract and build system that you can customize. 56 | 57 | ## Create a Repo 58 | 59 | After generating, you have a initialized local git repo, but no commits, and no remote. 60 | Go to a server (eg. github) and create a new upstream repo (called `YOUR-GIT-URL` below). 61 | Then run the following: 62 | 63 | ```sh 64 | # this is needed to create a valid Cargo.lock file (see below) 65 | cargo check 66 | git branch -M main 67 | git add . 68 | git commit -m 'Initial Commit' 69 | git remote add origin YOUR-GIT-URL 70 | git push -u origin main 71 | ``` 72 | 73 | ## CI Support 74 | 75 | We have template configurations for both [GitHub Actions](.github/workflows/Basic.yml) 76 | and [Circle CI](.circleci/config.yml) in the generated project, so you can 77 | get up and running with CI right away. 78 | 79 | One note is that the CI runs all `cargo` commands 80 | with `--locked` to ensure it uses the exact same versions as you have locally. This also means 81 | you must have an up-to-date `Cargo.lock` file, which is not auto-generated. 82 | The first time you set up the project (or after adding any dep), you should ensure the 83 | `Cargo.lock` file is updated, so the CI will test properly. This can be done simply by 84 | running `cargo check` or `cargo unit-test`. 85 | 86 | ## Using your project 87 | 88 | Once you have your custom repo, you should check out [Developing](./Developing.md) to explain 89 | more on how to run tests and develop code. Or go through the 90 | [online tutorial](https://docs.cosmwasm.com/) to get a better feel 91 | of how to develop. 92 | 93 | [Publishing](./Publishing.md) contains useful information on how to publish your contract 94 | to the world, once you are ready to deploy it on a running blockchain. And 95 | [Importing](./Importing.md) contains information about pulling in other contracts or crates 96 | that have been published. 97 | 98 | Please replace this README file with information about your specific project. You can keep 99 | the `Developing.md` and `Publishing.md` files as useful referenced, but please set some 100 | proper description in the README. 101 | -------------------------------------------------------------------------------- /contracts/cw-vm-test/Developing.md: -------------------------------------------------------------------------------- 1 | # Developing 2 | 3 | If you have recently created a contract with this template, you probably could use some 4 | help on how to build and test the contract, as well as prepare it for production. This 5 | file attempts to provide a brief overview, assuming you have installed a recent 6 | version of Rust already (eg. 1.51.0+). 7 | 8 | ## Prerequisites 9 | 10 | Before starting, make sure you have [rustup](https://rustup.rs/) along with a 11 | recent `rustc` and `cargo` version installed. Currently, we are testing on 1.51.0+. 12 | 13 | And you need to have the `wasm32-unknown-unknown` target installed as well. 14 | 15 | You can check that via: 16 | 17 | ```sh 18 | rustc --version 19 | cargo --version 20 | rustup target list --installed 21 | # if wasm32 is not listed above, run this 22 | rustup target add wasm32-unknown-unknown 23 | ``` 24 | 25 | ## Compiling and running tests 26 | 27 | Now that you created your custom contract, make sure you can compile and run it before 28 | making any changes. Go into the repository and do: 29 | 30 | ```sh 31 | # this will produce a wasm build in ./target/wasm32-unknown-unknown/release/YOUR_NAME_HERE.wasm 32 | cargo wasm 33 | 34 | # this runs unit tests with helpful backtraces 35 | RUST_BACKTRACE=1 cargo unit-test 36 | 37 | # auto-generate json schema 38 | cargo schema 39 | ``` 40 | 41 | ### Understanding the tests 42 | 43 | The main code is in `src/contract.rs` and the unit tests there run in pure rust, 44 | which makes them very quick to execute and give nice output on failures, especially 45 | if you do `RUST_BACKTRACE=1 cargo unit-test`. 46 | 47 | We consider testing critical for anything on a blockchain, and recommend to always keep 48 | the tests up to date. 49 | 50 | ## Generating JSON Schema 51 | 52 | While the Wasm calls (`instantiate`, `execute`, `query`) accept JSON, this is not enough 53 | information to use it. We need to expose the schema for the expected messages to the 54 | clients. You can generate this schema by calling `cargo schema`, which will output 55 | 4 files in `./schema`, corresponding to the 3 message types the contract accepts, 56 | as well as the internal `State`. 57 | 58 | These files are in standard json-schema format, which should be usable by various 59 | client side tools, either to auto-generate codecs, or just to validate incoming 60 | json wrt. the defined schema. 61 | 62 | ## Preparing the Wasm bytecode for production 63 | 64 | Before we upload it to a chain, we need to ensure the smallest output size possible, 65 | as this will be included in the body of a transaction. We also want to have a 66 | reproducible build process, so third parties can verify that the uploaded Wasm 67 | code did indeed come from the claimed rust code. 68 | 69 | To solve both these issues, we have produced `rust-optimizer`, a docker image to 70 | produce an extremely small build output in a consistent manner. The suggest way 71 | to run it is this: 72 | 73 | ```sh 74 | docker run --rm -v "$(pwd)":/code \ 75 | --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ 76 | --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ 77 | cosmwasm/rust-optimizer:0.11.4 78 | ``` 79 | 80 | We must mount the contract code to `/code`. You can use a absolute path instead 81 | of `$(pwd)` if you don't want to `cd` to the directory first. The other two 82 | volumes are nice for speedup. Mounting `/code/target` in particular is useful 83 | to avoid docker overwriting your local dev files with root permissions. 84 | Note the `/code/target` cache is unique for each contract being compiled to limit 85 | interference, while the registry cache is global. 86 | 87 | This is rather slow compared to local compilations, especially the first compile 88 | of a given contract. The use of the two volume caches is very useful to speed up 89 | following compiles of the same contract. 90 | 91 | This produces an `artifacts` directory with a `PROJECT_NAME.wasm`, as well as 92 | `checksums.txt`, containing the Sha256 hash of the wasm file. 93 | The wasm file is compiled deterministically (anyone else running the same 94 | docker on the same git commit should get the identical file with the same Sha256 hash). 95 | It is also stripped and minimized for upload to a blockchain (we will also 96 | gzip it in the uploading process to make it even smaller). 97 | -------------------------------------------------------------------------------- /contracts/cw-vm-test/README.md: -------------------------------------------------------------------------------- 1 | # CosmWasm Starter Pack 2 | 3 | This is a template to build smart contracts in Rust to run inside a 4 | [Cosmos SDK](https://github.com/cosmos/cosmos-sdk) module on all chains that enable it. 5 | To understand the framework better, please read the overview in the 6 | [cosmwasm repo](https://github.com/CosmWasm/cosmwasm/blob/master/README.md), 7 | and dig into the [cosmwasm docs](https://www.cosmwasm.com). 8 | This assumes you understand the theory and just want to get coding. 9 | 10 | ## Creating a new repo from template 11 | 12 | Assuming you have a recent version of rust and cargo (v1.51.0+) installed 13 | (via [rustup](https://rustup.rs/)), 14 | then the following should get you a new repo to start a contract: 15 | 16 | 17 | Install [cargo-generate](https://github.com/ashleygwilliams/cargo-generate) and cargo-run-script. 18 | Unless you did that before, run this line now: 19 | 20 | ```sh 21 | cargo install cargo-generate cargo-run-script --features vendored-openssl 22 | ``` 23 | 24 | Now, use it to create your new contract. 25 | Go to the folder in which you want to place it and run: 26 | 27 | 28 | **Latest: 0.16** 29 | 30 | ```sh 31 | cargo generate --git https://github.com/CosmWasm/cw-template.git --name PROJECT_NAME 32 | ```` 33 | 34 | **Older Version** 35 | 36 | Pass version as branch flag: 37 | 38 | ```sh 39 | cargo generate --git https://github.com/CosmWasm/cw-template.git --branch --name PROJECT_NAME 40 | ```` 41 | 42 | Example: 43 | 44 | ```sh 45 | cargo generate --git https://github.com/CosmWasm/cw-template.git --branch 0.14 --name PROJECT_NAME 46 | ``` 47 | 48 | You will now have a new folder called `PROJECT_NAME` (I hope you changed that to something else) 49 | containing a simple working contract and build system that you can customize. 50 | 51 | ## Create a Repo 52 | 53 | After generating, you have a initialized local git repo, but no commits, and no remote. 54 | Go to a server (eg. github) and create a new upstream repo (called `YOUR-GIT-URL` below). 55 | Then run the following: 56 | 57 | ```sh 58 | # this is needed to create a valid Cargo.lock file (see below) 59 | cargo check 60 | git branch -M main 61 | git add . 62 | git commit -m 'Initial Commit' 63 | git remote add origin YOUR-GIT-URL 64 | git push -u origin master 65 | ``` 66 | 67 | ## CI Support 68 | 69 | We have template configurations for both [GitHub Actions](.github/workflows/Basic.yml) 70 | and [Circle CI](.circleci/config.yml) in the generated project, so you can 71 | get up and running with CI right away. 72 | 73 | One note is that the CI runs all `cargo` commands 74 | with `--locked` to ensure it uses the exact same versions as you have locally. This also means 75 | you must have an up-to-date `Cargo.lock` file, which is not auto-generated. 76 | The first time you set up the project (or after adding any dep), you should ensure the 77 | `Cargo.lock` file is updated, so the CI will test properly. This can be done simply by 78 | running `cargo check` or `cargo unit-test`. 79 | 80 | ## Using your project 81 | 82 | Once you have your custom repo, you should check out [Developing](./Developing.md) to explain 83 | more on how to run tests and develop code. Or go through the 84 | [online tutorial](https://docs.cosmwasm.com/) to get a better feel 85 | of how to develop. 86 | 87 | [Publishing](./Publishing.md) contains useful information on how to publish your contract 88 | to the world, once you are ready to deploy it on a running blockchain. And 89 | [Importing](./Importing.md) contains information about pulling in other contracts or crates 90 | that have been published. 91 | 92 | Please replace this README file with information about your specific project. You can keep 93 | the `Developing.md` and `Publishing.md` files as useful referenced, but please set some 94 | proper description in the README. 95 | 96 | ## Gitpod integration 97 | 98 | [Gitpod](https://www.gitpod.io/) container-based development platform will be enabled on your project by default. 99 | 100 | Workspace contains: 101 | - **rust**: for builds 102 | - [wasmd](https://github.com/CosmWasm/wasmd): for local node setup and client 103 | - **jq**: shell JSON manipulation tool 104 | 105 | Follow [Gitpod Getting Started](https://www.gitpod.io/docs/getting-started) and launch your workspace. 106 | 107 | -------------------------------------------------------------------------------- /contracts/cosmwasm-vm-test/Developing.md: -------------------------------------------------------------------------------- 1 | # Developing 2 | 3 | If you have recently created a contract with this template, you probably could use some 4 | help on how to build and test the contract, as well as prepare it for production. This 5 | file attempts to provide a brief overview, assuming you have installed a recent 6 | version of Rust already (eg. 1.58.1+). 7 | 8 | ## Prerequisites 9 | 10 | Before starting, make sure you have [rustup](https://rustup.rs/) along with a 11 | recent `rustc` and `cargo` version installed. Currently, we are testing on 1.58.1+. 12 | 13 | And you need to have the `wasm32-unknown-unknown` target installed as well. 14 | 15 | You can check that via: 16 | 17 | ```sh 18 | rustc --version 19 | cargo --version 20 | rustup target list --installed 21 | # if wasm32 is not listed above, run this 22 | rustup target add wasm32-unknown-unknown 23 | ``` 24 | 25 | ## Compiling and running tests 26 | 27 | Now that you created your custom contract, make sure you can compile and run it before 28 | making any changes. Go into the repository and do: 29 | 30 | ```sh 31 | # this will produce a wasm build in ./target/wasm32-unknown-unknown/release/YOUR_NAME_HERE.wasm 32 | cargo wasm 33 | 34 | # this runs unit tests with helpful backtraces 35 | RUST_BACKTRACE=1 cargo unit-test 36 | 37 | # auto-generate json schema 38 | cargo schema 39 | ``` 40 | 41 | ### Understanding the tests 42 | 43 | The main code is in `src/contract.rs` and the unit tests there run in pure rust, 44 | which makes them very quick to execute and give nice output on failures, especially 45 | if you do `RUST_BACKTRACE=1 cargo unit-test`. 46 | 47 | We consider testing critical for anything on a blockchain, and recommend to always keep 48 | the tests up to date. 49 | 50 | ## Generating JSON Schema 51 | 52 | While the Wasm calls (`instantiate`, `execute`, `query`) accept JSON, this is not enough 53 | information to use it. We need to expose the schema for the expected messages to the 54 | clients. You can generate this schema by calling `cargo schema`, which will output 55 | 4 files in `./schema`, corresponding to the 3 message types the contract accepts, 56 | as well as the internal `State`. 57 | 58 | These files are in standard json-schema format, which should be usable by various 59 | client side tools, either to auto-generate codecs, or just to validate incoming 60 | json wrt. the defined schema. 61 | 62 | ## Preparing the Wasm bytecode for production 63 | 64 | Before we upload it to a chain, we need to ensure the smallest output size possible, 65 | as this will be included in the body of a transaction. We also want to have a 66 | reproducible build process, so third parties can verify that the uploaded Wasm 67 | code did indeed come from the claimed rust code. 68 | 69 | To solve both these issues, we have produced `rust-optimizer`, a docker image to 70 | produce an extremely small build output in a consistent manner. The suggest way 71 | to run it is this: 72 | 73 | ```sh 74 | docker run --rm -v "$(pwd)":/code \ 75 | --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ 76 | --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ 77 | cosmwasm/rust-optimizer:0.12.6 78 | ``` 79 | 80 | Or, If you're on an arm64 machine, you should use a docker image built with arm64. 81 | ```sh 82 | docker run --rm -v "$(pwd)":/code \ 83 | --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ 84 | --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ 85 | cosmwasm/rust-optimizer-arm64:0.12.6 86 | ``` 87 | 88 | We must mount the contract code to `/code`. You can use a absolute path instead 89 | of `$(pwd)` if you don't want to `cd` to the directory first. The other two 90 | volumes are nice for speedup. Mounting `/code/target` in particular is useful 91 | to avoid docker overwriting your local dev files with root permissions. 92 | Note the `/code/target` cache is unique for each contract being compiled to limit 93 | interference, while the registry cache is global. 94 | 95 | This is rather slow compared to local compilations, especially the first compile 96 | of a given contract. The use of the two volume caches is very useful to speed up 97 | following compiles of the same contract. 98 | 99 | This produces an `artifacts` directory with a `PROJECT_NAME.wasm`, as well as 100 | `checksums.txt`, containing the Sha256 hash of the wasm file. 101 | The wasm file is compiled deterministically (anyone else running the same 102 | docker on the same git commit should get the identical file with the same Sha256 hash). 103 | It is also stripped and minimized for upload to a blockchain (we will also 104 | gzip it in the uploading process to make it even smaller). 105 | -------------------------------------------------------------------------------- /src/backend/storage.ts: -------------------------------------------------------------------------------- 1 | import { fromBase64, toBase64 } from '@cosmjs/encoding'; 2 | import { compare, toByteArray, toNumber } from '../helpers/byte-array'; 3 | import Immutable from 'immutable'; 4 | import { MAX_LENGTH_DB_KEY } from '../instance'; 5 | 6 | export interface IStorage { 7 | dict: Immutable.Map; 8 | get(key: Uint8Array): Uint8Array | null; 9 | 10 | set(key: Uint8Array, value: Uint8Array): void; 11 | 12 | remove(key: Uint8Array): void; 13 | 14 | keys(): Iterable; 15 | } 16 | 17 | export class Record { 18 | public key: Uint8Array = Uint8Array.from([]); 19 | public value: Uint8Array = Uint8Array.from([]); 20 | } 21 | 22 | export interface Iter { 23 | data: Array; 24 | position: number; 25 | } 26 | 27 | export enum Order { 28 | Ascending = 1, 29 | Descending = 2, 30 | } 31 | 32 | export interface IIterStorage extends IStorage { 33 | all(iterator_id: Uint8Array): Array; 34 | 35 | scan(start: Uint8Array | null, end: Uint8Array | null, order: Order): Uint8Array; 36 | next(iterator_id: Uint8Array): Record | null; 37 | } 38 | 39 | export class BasicKVStorage implements IStorage { 40 | // TODO: Add binary uint / typed Addr maps for cw-storage-plus compatibility 41 | constructor(public dict: Immutable.Map = Immutable.Map()) {} 42 | 43 | *keys() { 44 | for (const key of this.dict.keys()) { 45 | yield fromBase64(key); 46 | } 47 | } 48 | 49 | get(key: Uint8Array): Uint8Array | null { 50 | const keyStr = toBase64(key); 51 | const value = this.dict.get(keyStr); 52 | if (value === undefined) { 53 | return null; 54 | } 55 | 56 | return fromBase64(value); 57 | } 58 | 59 | set(key: Uint8Array, value: Uint8Array): void { 60 | const keyStr = toBase64(key); 61 | this.dict = this.dict.set(keyStr, toBase64(value)); 62 | } 63 | 64 | remove(key: Uint8Array): void { 65 | if (key.length > MAX_LENGTH_DB_KEY) { 66 | throw new Error( 67 | `Key length ${key.length} exceeds maximum length ${MAX_LENGTH_DB_KEY}.` 68 | ); 69 | } 70 | this.dict = this.dict.remove(toBase64(key)); 71 | } 72 | } 73 | 74 | export class BasicKVIterStorage extends BasicKVStorage implements IIterStorage { 75 | constructor(public dict: Immutable.Map = Immutable.Map(), public iterators: Map = new Map()) { 76 | super(dict); 77 | } 78 | 79 | all(iterator_id: Uint8Array): Array { 80 | const out: Array = []; 81 | let condition = true; 82 | while (condition) { 83 | const record = this.next(iterator_id); 84 | if (record === null) { 85 | condition = false; 86 | } else { 87 | out.push(record); 88 | } 89 | } 90 | return out; 91 | } 92 | 93 | // Get next element of iterator with ID `iterator_id`. 94 | // Creates a region containing both key and value and returns its address. 95 | // Ownership of the result region is transferred to the contract. 96 | // The KV region uses the format value || valuelen || key || keylen, where valuelen and keylen are fixed-size big-endian u32 values. 97 | // An empty key (i.e. KV region ends with \0\0\0\0) means no more element, no matter what the value is. 98 | next(iterator_id: Uint8Array): Record | null { 99 | const iter = this.iterators.get(toNumber(iterator_id)); 100 | if (iter === undefined) { 101 | throw new Error(`Iterator not found.`); 102 | } 103 | const record = iter.data[iter.position]; 104 | if (!record) { 105 | return null; 106 | } 107 | 108 | iter.position += 1; 109 | return record; 110 | } 111 | 112 | scan(start: Uint8Array | null, end: Uint8Array | null, order: Order): Uint8Array { 113 | if (!(order in Order)) { 114 | throw new Error(`Invalid order value ${order}.`); 115 | } 116 | 117 | const newId = this.iterators.size + 1; 118 | 119 | // if start > end, this represents an empty range 120 | if (start?.length && end?.length && compare(start, end) === 1) { 121 | this.iterators.set(newId, { data: [], position: 0 }); 122 | return toByteArray(newId); 123 | } 124 | 125 | let data: Record[] = []; 126 | for (const key of Array.from(this.dict.keys()).sort()) { 127 | if (start?.length && compare(start, fromBase64(key)) === 1) continue; 128 | if (end?.length && compare(fromBase64(key), end) > -1) break; 129 | 130 | data.push({ key: fromBase64(key), value: this.get(fromBase64(key))! }); 131 | } 132 | 133 | if (order === Order.Descending) { 134 | data = data.reverse(); 135 | } 136 | 137 | this.iterators.set(newId, { data, position: 0 }); 138 | return toByteArray(newId); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /contracts/cw-vm-test/Publishing.md: -------------------------------------------------------------------------------- 1 | # Publishing Contracts 2 | 3 | This is an overview of how to publish the contract's source code in this repo. 4 | We use Cargo's default registry [crates.io](https://crates.io/) for publishing contracts written in Rust. 5 | 6 | ## Preparation 7 | 8 | Ensure the `Cargo.toml` file in the repo is properly configured. In particular, you want to 9 | choose a name starting with `cw-`, which will help a lot finding CosmWasm contracts when 10 | searching on crates.io. For the first publication, you will probably want version `0.1.0`. 11 | If you have tested this on a public net already and/or had an audit on the code, 12 | you can start with `1.0.0`, but that should imply some level of stability and confidence. 13 | You will want entries like the following in `Cargo.toml`: 14 | 15 | ```toml 16 | name = "cw-escrow" 17 | version = "0.1.0" 18 | description = "Simple CosmWasm contract for an escrow with arbiter and timeout" 19 | repository = "https://github.com/confio/cosmwasm-examples" 20 | ``` 21 | 22 | You will also want to add a valid [SPDX license statement](https://spdx.org/licenses/), 23 | so others know the rules for using this crate. You can use any license you wish, 24 | even a commercial license, but we recommend choosing one of the following, unless you have 25 | specific requirements. 26 | 27 | * Permissive: [`Apache-2.0`](https://spdx.org/licenses/Apache-2.0.html#licenseText) or [`MIT`](https://spdx.org/licenses/MIT.html#licenseText) 28 | * Copyleft: [`GPL-3.0-or-later`](https://spdx.org/licenses/GPL-3.0-or-later.html#licenseText) or [`AGPL-3.0-or-later`](https://spdx.org/licenses/AGPL-3.0-or-later.html#licenseText) 29 | * Commercial license: `Commercial` (not sure if this works, I cannot find examples) 30 | 31 | It is also helpful to download the LICENSE text (linked to above) and store this 32 | in a LICENSE file in your repo. Now, you have properly configured your crate for use 33 | in a larger ecosystem. 34 | 35 | ### Updating schema 36 | 37 | To allow easy use of the contract, we can publish the schema (`schema/*.json`) together 38 | with the source code. 39 | 40 | ```sh 41 | cargo schema 42 | ``` 43 | 44 | Ensure you check in all the schema files, and make a git commit with the final state. 45 | This commit will be published and should be tagged. Generally, you will want to 46 | tag with the version (eg. `v0.1.0`), but in the `cosmwasm-examples` repo, we have 47 | multiple contracts and label it like `escrow-0.1.0`. Don't forget a 48 | `git push && git push --tags` 49 | 50 | ### Note on build results 51 | 52 | Build results like Wasm bytecode or expected hash don't need to be updated since 53 | the don't belong to the source publication. However, they are excluded from packaging 54 | in `Cargo.toml` which allows you to commit them to your git repository if you like. 55 | 56 | ```toml 57 | exclude = ["artifacts"] 58 | ``` 59 | 60 | A single source code can be built with multiple different optimizers, so 61 | we should not make any strict assumptions on the tooling that will be used. 62 | 63 | ## Publishing 64 | 65 | Now that your package is properly configured and all artifacts are committed, it 66 | is time to share it with the world. 67 | Please refer to the [complete instructions for any questions](https://rurust.github.io/cargo-docs-ru/crates-io.html), 68 | but I will try to give a quick overview of the happy path here. 69 | 70 | ### Registry 71 | 72 | You will need an account on [crates.io](https://crates.io) to publish a rust crate. 73 | If you don't have one already, just click on "Log in with GitHub" in the top-right 74 | to quickly set up a free account. Once inside, click on your username (top-right), 75 | then "Account Settings". On the bottom, there is a section called "API Access". 76 | If you don't have this set up already, create a new token and use `cargo login` 77 | to set it up. This will now authenticate you with the `cargo` cli tool and allow 78 | you to publish. 79 | 80 | ### Uploading 81 | 82 | Once this is set up, make sure you commit the current state you want to publish. 83 | Then try `cargo publish --dry-run`. If that works well, review the files that 84 | will be published via `cargo package --list`. If you are satisfied, you can now 85 | officially publish it via `cargo publish`. 86 | 87 | Congratulations, your package is public to the world. 88 | 89 | ### Sharing 90 | 91 | Once you have published your package, people can now find it by 92 | [searching for "cw-" on crates.io](https://crates.io/search?q=cw). 93 | But that isn't exactly the simplest way. To make things easier and help 94 | keep the ecosystem together, we suggest making a PR to add your package 95 | to the [`cawesome-wasm`](https://github.com/cosmwasm/cawesome-wasm) list. 96 | 97 | ### Organizations 98 | 99 | Many times you are writing a contract not as a solo developer, but rather as 100 | part of an organization. You will want to allow colleagues to upload new 101 | versions of the contract to crates.io when you are on holiday. 102 | [These instructions show how]() you can set up your crate to allow multiple maintainers. 103 | 104 | You can add another owner to the crate by specifying their github user. Note, you will 105 | now both have complete control of the crate, and they can remove you: 106 | 107 | `cargo owner --add ethanfrey` 108 | 109 | You can also add an existing github team inside your organization: 110 | 111 | `cargo owner --add github:confio:developers` 112 | 113 | The team will allow anyone who is currently in the team to publish new versions of the crate. 114 | And this is automatically updated when you make changes on github. However, it will not allow 115 | anyone in the team to add or remove other owners. 116 | -------------------------------------------------------------------------------- /contracts/cosmwasm-vm-test/Publishing.md: -------------------------------------------------------------------------------- 1 | # Publishing Contracts 2 | 3 | This is an overview of how to publish the contract's source code in this repo. 4 | We use Cargo's default registry [crates.io](https://crates.io/) for publishing contracts written in Rust. 5 | 6 | ## Preparation 7 | 8 | Ensure the `Cargo.toml` file in the repo is properly configured. In particular, you want to 9 | choose a name starting with `cw-`, which will help a lot finding CosmWasm contracts when 10 | searching on crates.io. For the first publication, you will probably want version `0.1.0`. 11 | If you have tested this on a public net already and/or had an audit on the code, 12 | you can start with `1.0.0`, but that should imply some level of stability and confidence. 13 | You will want entries like the following in `Cargo.toml`: 14 | 15 | ```toml 16 | name = "cw-escrow" 17 | version = "0.1.0" 18 | description = "Simple CosmWasm contract for an escrow with arbiter and timeout" 19 | repository = "https://github.com/confio/cosmwasm-examples" 20 | ``` 21 | 22 | You will also want to add a valid [SPDX license statement](https://spdx.org/licenses/), 23 | so others know the rules for using this crate. You can use any license you wish, 24 | even a commercial license, but we recommend choosing one of the following, unless you have 25 | specific requirements. 26 | 27 | * Permissive: [`Apache-2.0`](https://spdx.org/licenses/Apache-2.0.html#licenseText) or [`MIT`](https://spdx.org/licenses/MIT.html#licenseText) 28 | * Copyleft: [`GPL-3.0-or-later`](https://spdx.org/licenses/GPL-3.0-or-later.html#licenseText) or [`AGPL-3.0-or-later`](https://spdx.org/licenses/AGPL-3.0-or-later.html#licenseText) 29 | * Commercial license: `Commercial` (not sure if this works, I cannot find examples) 30 | 31 | It is also helpful to download the LICENSE text (linked to above) and store this 32 | in a LICENSE file in your repo. Now, you have properly configured your crate for use 33 | in a larger ecosystem. 34 | 35 | ### Updating schema 36 | 37 | To allow easy use of the contract, we can publish the schema (`schema/*.json`) together 38 | with the source code. 39 | 40 | ```sh 41 | cargo schema 42 | ``` 43 | 44 | Ensure you check in all the schema files, and make a git commit with the final state. 45 | This commit will be published and should be tagged. Generally, you will want to 46 | tag with the version (eg. `v0.1.0`), but in the `cosmwasm-examples` repo, we have 47 | multiple contracts and label it like `escrow-0.1.0`. Don't forget a 48 | `git push && git push --tags` 49 | 50 | ### Note on build results 51 | 52 | Build results like Wasm bytecode or expected hash don't need to be updated since 53 | they don't belong to the source publication. However, they are excluded from packaging 54 | in `Cargo.toml` which allows you to commit them to your git repository if you like. 55 | 56 | ```toml 57 | exclude = ["artifacts"] 58 | ``` 59 | 60 | A single source code can be built with multiple different optimizers, so 61 | we should not make any strict assumptions on the tooling that will be used. 62 | 63 | ## Publishing 64 | 65 | Now that your package is properly configured and all artifacts are committed, it 66 | is time to share it with the world. 67 | Please refer to the [complete instructions for any questions](https://rurust.github.io/cargo-docs-ru/crates-io.html), 68 | but I will try to give a quick overview of the happy path here. 69 | 70 | ### Registry 71 | 72 | You will need an account on [crates.io](https://crates.io) to publish a rust crate. 73 | If you don't have one already, just click on "Log in with GitHub" in the top-right 74 | to quickly set up a free account. Once inside, click on your username (top-right), 75 | then "Account Settings". On the bottom, there is a section called "API Access". 76 | If you don't have this set up already, create a new token and use `cargo login` 77 | to set it up. This will now authenticate you with the `cargo` cli tool and allow 78 | you to publish. 79 | 80 | ### Uploading 81 | 82 | Once this is set up, make sure you commit the current state you want to publish. 83 | Then try `cargo publish --dry-run`. If that works well, review the files that 84 | will be published via `cargo package --list`. If you are satisfied, you can now 85 | officially publish it via `cargo publish`. 86 | 87 | Congratulations, your package is public to the world. 88 | 89 | ### Sharing 90 | 91 | Once you have published your package, people can now find it by 92 | [searching for "cw-" on crates.io](https://crates.io/search?q=cw). 93 | But that isn't exactly the simplest way. To make things easier and help 94 | keep the ecosystem together, we suggest making a PR to add your package 95 | to the [`cawesome-wasm`](https://github.com/cosmwasm/cawesome-wasm) list. 96 | 97 | ### Organizations 98 | 99 | Many times you are writing a contract not as a solo developer, but rather as 100 | part of an organization. You will want to allow colleagues to upload new 101 | versions of the contract to crates.io when you are on holiday. 102 | [These instructions show how]() you can set up your crate to allow multiple maintainers. 103 | 104 | You can add another owner to the crate by specifying their github user. Note, you will 105 | now both have complete control of the crate, and they can remove you: 106 | 107 | `cargo owner --add ethanfrey` 108 | 109 | You can also add an existing github team inside your organization: 110 | 111 | `cargo owner --add github:confio:developers` 112 | 113 | The team will allow anyone who is currently in the team to publish new versions of the crate. 114 | And this is automatically updated when you make changes on github. However, it will not allow 115 | anyone in the team to add or remove other owners. 116 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | [INSERT CONTACT METHOD]. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. 120 | 121 | Community Impact Guidelines were inspired by 122 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 123 | 124 | For answers to common questions about this code of conduct, see the FAQ at 125 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available 126 | at [https://www.contributor-covenant.org/translations][translations]. 127 | 128 | [homepage]: https://www.contributor-covenant.org 129 | [v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html 130 | [Mozilla CoC]: https://github.com/mozilla/diversity 131 | [FAQ]: https://www.contributor-covenant.org/faq 132 | [translations]: https://www.contributor-covenant.org/translations 133 | -------------------------------------------------------------------------------- /test/integration/queue.test.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs'; 2 | import { VMInstance } from "../../src/instance"; 3 | import { 4 | BasicBackendApi, 5 | BasicKVIterStorage, 6 | BasicQuerier, 7 | IBackend, 8 | } from '../../src/backend'; 9 | import { Region } from '../../src/memory'; 10 | import { expectResponseToBeOk, parseBase64Response } from '../common/test-vm'; 11 | 12 | const wasmBytecode = readFileSync('testdata/v1.1/queue.wasm'); 13 | 14 | const creator = 'creator'; 15 | const mockContractAddr = 'cosmos2contract'; 16 | 17 | const mockEnv = { 18 | block: { 19 | height: 12345, 20 | time: '1571797419879305533', 21 | chain_id: 'cosmos-testnet-14002', 22 | }, 23 | contract: { address: mockContractAddr } 24 | }; 25 | 26 | const mockInfo: { sender: string, funds: { amount: string, denom: string }[] } = { 27 | sender: creator, 28 | funds: [] 29 | }; 30 | 31 | let vm: VMInstance; 32 | describe('queue', () => { 33 | beforeEach(async () => { 34 | const backend: IBackend = { 35 | backend_api: new BasicBackendApi('terra'), 36 | storage: new BasicKVIterStorage(), 37 | querier: new BasicQuerier(), 38 | }; 39 | 40 | vm = new VMInstance(backend, 100_000_000_000_000) // TODO: implement gas limit on VM 41 | await vm.build(wasmBytecode); 42 | }); 43 | 44 | it('instantiate_and_query', async () => { 45 | // Arrange 46 | const instantiateResponse = vm.instantiate(mockEnv, mockInfo, {}); 47 | 48 | // Act 49 | const countResponse = vm.query(mockEnv, { count: {} }); 50 | const sumResponse = vm.query(mockEnv, { sum: {} }); 51 | 52 | // Assert 53 | expect((instantiateResponse.json as any).ok.messages.length).toBe(0); 54 | 55 | expectResponseToBeOk(countResponse); 56 | expect(parseBase64OkResponse(countResponse)).toEqual({ count: 0 }); 57 | 58 | expectResponseToBeOk(sumResponse); 59 | expect(parseBase64OkResponse(sumResponse)).toEqual({ sum: 0 }); 60 | }); 61 | 62 | it('push_and_query', () => { 63 | // Arrange 64 | vm.instantiate(mockEnv, mockInfo, {}); 65 | 66 | // Act 67 | vm.execute(mockEnv, mockInfo, { enqueue: { value: 25 } }); 68 | 69 | // Assert 70 | const countResponse = vm.query(mockEnv, { count: {} }); 71 | expect(parseBase64OkResponse(countResponse)).toEqual({ count: 1 }); 72 | 73 | const sumResponse = vm.query(mockEnv, { sum: {} }); 74 | expect(parseBase64OkResponse(sumResponse)).toEqual({ sum: 25 }); 75 | }); 76 | 77 | it('multiple_push', () => { 78 | // Arrange 79 | vm.instantiate(mockEnv, mockInfo, {}); 80 | 81 | // Act 82 | vm.execute(mockEnv, mockInfo, { enqueue: { value: 25 } }); 83 | vm.execute(mockEnv, mockInfo, { enqueue: { value: 35 } }); 84 | vm.execute(mockEnv, mockInfo, { enqueue: { value: 45 } }); 85 | 86 | // Assert 87 | const countResponse = vm.query(mockEnv, { count: {} }); 88 | expect(parseBase64OkResponse(countResponse)).toEqual({ count: 3 }); 89 | 90 | const sumResponse = vm.query(mockEnv, { sum: {} }); 91 | expect(parseBase64OkResponse(sumResponse)).toEqual({ sum: 105 }); 92 | }); 93 | 94 | it('push_and_pop', () => { 95 | // Arrange 96 | vm.instantiate(mockEnv, mockInfo, {}); 97 | vm.execute(mockEnv, mockInfo, { enqueue: { value: 25 } }); 98 | vm.execute(mockEnv, mockInfo, { enqueue: { value: 17 } }); 99 | 100 | // Act 101 | const dequeueResponse = vm.execute(mockEnv, mockInfo, { dequeue: {} }); 102 | 103 | // Assert 104 | expect(parseBase64Response((dequeueResponse.json as any).ok.data)).toEqual({ value: 25 }); 105 | 106 | const countResponse = vm.query(mockEnv, { count: {} }); 107 | expect(parseBase64OkResponse(countResponse)).toEqual({ count: 1 }); 108 | 109 | const sumResponse = vm.query(mockEnv, { sum: {} }); 110 | expect(parseBase64OkResponse(sumResponse)).toEqual({ sum: 17 }); 111 | }); 112 | 113 | it('push_and_reduce', () => { 114 | // Arrange 115 | vm.instantiate(mockEnv, mockInfo, {}); 116 | vm.execute(mockEnv, mockInfo, { enqueue: { value: 40 } }); 117 | vm.execute(mockEnv, mockInfo, { enqueue: { value: 15 } }); 118 | vm.execute(mockEnv, mockInfo, { enqueue: { value: 85 } }); 119 | vm.execute(mockEnv, mockInfo, { enqueue: { value: -10 } }); 120 | 121 | // Act 122 | const reducerResponse = vm.query(mockEnv, { reducer: {} }); 123 | 124 | // Assert 125 | expect(parseBase64OkResponse(reducerResponse).counters).toStrictEqual([[40, 85], [15, 125], [85, 0], [-10, 140]]); 126 | }); 127 | 128 | it('migrate_works', () => { 129 | // Arrange 130 | vm.instantiate(mockEnv, mockInfo, {}); 131 | vm.execute(mockEnv, mockInfo, { enqueue: { value: 25 } }); 132 | vm.execute(mockEnv, mockInfo, { enqueue: { value: 17 } }); 133 | 134 | // Act 135 | const migrateResponse = vm.migrate(mockEnv, {}); 136 | 137 | // Assert 138 | expect((migrateResponse.json as any).ok.messages.length).toEqual(0); 139 | 140 | const countResponse = vm.query(mockEnv, { count: {} }); 141 | expect(parseBase64OkResponse(countResponse)).toEqual({ count: 3 }); 142 | 143 | const sumResponse = vm.query(mockEnv, { sum: {} }); 144 | expect(parseBase64OkResponse(sumResponse)).toEqual({ sum: 303 }); 145 | }); 146 | 147 | it('query_list', () => { 148 | // Arrange 149 | vm.instantiate(mockEnv, mockInfo, {}); 150 | 151 | for (let i = 0; i < 37; i++) { 152 | vm.execute(mockEnv, mockInfo, { enqueue: { value: 40 } }); 153 | } 154 | 155 | for (let i = 0; i < 25; i++) { 156 | vm.execute(mockEnv, mockInfo, { dequeue: {} }); 157 | } 158 | 159 | // Act 160 | const listResponse = vm.query(mockEnv, { list: {} }); 161 | 162 | // Assert 163 | const countResponse = vm.query(mockEnv, { count: {} }); 164 | expect(parseBase64OkResponse(countResponse)).toEqual({ count: 12 }); 165 | 166 | const list = parseBase64OkResponse(listResponse); 167 | expect(list.empty).toStrictEqual([]); 168 | expect(list.early).toStrictEqual([25, 26, 27, 28, 29, 30, 31]); 169 | expect(list.late).toStrictEqual([32, 33, 34, 35, 36]); 170 | }); 171 | 172 | it('query_open_iterators', async () => { 173 | // Arrange 174 | vm.instantiate(mockEnv, mockInfo, {}); 175 | 176 | // Act 177 | const response1 = vm.query(mockEnv, { open_iterators: { count: 1 } }); 178 | const response2 = vm.query(mockEnv, { open_iterators: { count: 2 } }); 179 | const response3 = vm.query(mockEnv, { open_iterators: { count: 321 } }); 180 | 181 | // Assert 182 | expectResponseToBeOk(response1); 183 | expectResponseToBeOk(response2); 184 | expectResponseToBeOk(response3); 185 | }); 186 | }); 187 | 188 | // Helpers 189 | 190 | function parseBase64OkResponse(region: Region): any { 191 | const data = (region.json as { ok: string }).ok; 192 | if (!data) { 193 | throw new Error(`Response indicates an error state: ${JSON.stringify(region.json)}`) 194 | } 195 | 196 | return parseBase64Response(data); 197 | } 198 | -------------------------------------------------------------------------------- /contracts/cosmwasm-vm-test/src/contract.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "library"))] 2 | use cosmwasm_std::entry_point; 3 | use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; 4 | use cw2::set_contract_version; 5 | 6 | use crate::error::ContractError; 7 | use crate::msg::{ExecuteMsg, GetCountResponse, InstantiateMsg, QueryMsg, TestAddrCanonicalizeResponse, TestAddrHumanizeResponse, TestAddrValidateResponse, TestDbReadResponse, TestDebugQueryResponse, TestEd25519BatchVerifyResponse, TestEd25519VerifyResponse, TestQueryChainResponse, TestSecp256k1RecoverPubkeyResponse, TestSecp256k1VerifyResponse}; 8 | use crate::state::{State, STATE}; 9 | 10 | // version info for migration info 11 | const CONTRACT_NAME: &str = "crates.io:cosmwasm-vm-test"; 12 | const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); 13 | 14 | #[cfg_attr(not(feature = "library"), entry_point)] 15 | pub fn instantiate( 16 | deps: DepsMut, 17 | _env: Env, 18 | info: MessageInfo, 19 | msg: InstantiateMsg, 20 | ) -> Result { 21 | Ok(Response::default()) 22 | } 23 | 24 | #[cfg_attr(not(feature = "library"), entry_point)] 25 | pub fn execute( 26 | deps: DepsMut, 27 | _env: Env, 28 | info: MessageInfo, 29 | msg: ExecuteMsg, 30 | ) -> Result { 31 | match msg { 32 | ExecuteMsg::TestDbWrite { key, value } => try_test_db_write(deps, key, value), 33 | ExecuteMsg::TestDbRemove { key } => try_test_db_remove(deps, key), 34 | ExecuteMsg::TestDbScan { start, end, order } => try_test_db_scan(deps, start, end, order), 35 | ExecuteMsg::TestDbNext { iterator_id } => try_test_db_next(deps, iterator_id), 36 | ExecuteMsg::TestDebugExecute { } => try_test_debug_execute(deps), 37 | } 38 | } 39 | 40 | pub fn try_test_db_write(deps: DepsMut) -> Result { 41 | unimplemented!() 42 | } 43 | 44 | pub fn try_test_db_remove(deps: DepsMut) -> Result { 45 | unimplemented!() 46 | } 47 | 48 | 49 | pub fn try_test_db_scan(deps: DepsMut) -> Result { 50 | unimplemented!() 51 | } 52 | 53 | pub fn try_test_db_next(deps: DepsMut) -> Result { 54 | unimplemented!() 55 | } 56 | 57 | pub fn try_test_debug_execute(deps: DepsMut) -> Result { 58 | unimplemented!() 59 | } 60 | 61 | #[cfg_attr(not(feature = "library"), entry_point)] 62 | pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { 63 | match msg { 64 | QueryMsg::TestDbRead { key } => to_binary(&query_test_db_read(deps)?), 65 | QueryMsg::TestAddrHumanize {} => to_binary(&query_test_addr_humanize(deps)?), 66 | QueryMsg::TestAddrCanonicalize {} => to_binary(&query_test_addr_canonicalize(deps)?), 67 | QueryMsg::TestAddrValidate {} => to_binary(&query_test_addr_validate(deps)?), 68 | QueryMsg::TestSecp256k1Verify {} => to_binary(&query_test_secp256k1_verify(deps)?), 69 | QueryMsg::TestSecp256k1RecoverPubkey {} => to_binary(&query_test_secp256k1_recover_pubkey(deps)?), 70 | QueryMsg::TestEd25519Verify {} => to_binary(&query_test_ed25519_verify(deps)?), 71 | QueryMsg::TestEd25519BatchVerify {} => to_binary(&query_test_ed25519_batch_verify(deps)?), 72 | QueryMsg::TestDebugQuery {} => to_binary(&query_test_debug_query(deps)?), 73 | QueryMsg::TestQueryChain {} => to_binary(&query_test_query_chain(deps)?), 74 | } 75 | } 76 | 77 | fn query_test_db_read(deps: Deps) -> StdResult { 78 | unimplemented!() 79 | } 80 | 81 | fn query_test_addr_humanize(deps: Deps) -> StdResult { 82 | unimplemented!() 83 | } 84 | 85 | fn query_test_addr_canonicalize(deps: Deps) -> StdResult { 86 | unimplemented!() 87 | } 88 | 89 | fn query_test_addr_validate(deps: Deps) -> StdResult { 90 | unimplemented!() 91 | } 92 | 93 | fn query_test_secp256k1_verify(deps: Deps) -> StdResult { 94 | unimplemented!() 95 | } 96 | 97 | fn query_test_secp256k1_recover_pubkey(deps: Deps) -> StdResult { 98 | unimplemented!() 99 | } 100 | 101 | fn query_test_ed25519_verify(deps: Deps) -> StdResult { 102 | unimplemented!() 103 | } 104 | 105 | fn query_test_ed25519_batch_verify(deps: Deps) -> StdResult { 106 | unimplemented!() 107 | } 108 | 109 | fn query_test_debug_query(deps: Deps) -> StdResult { 110 | unimplemented!() 111 | } 112 | 113 | fn query_test_query_chain(deps: Deps) -> StdResult { 114 | unimplemented!() 115 | } 116 | 117 | #[cfg(test)] 118 | mod tests { 119 | use super::*; 120 | use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; 121 | use cosmwasm_std::{coins, from_binary}; 122 | 123 | #[test] 124 | fn proper_initialization() { 125 | let mut deps = mock_dependencies(); 126 | 127 | let msg = InstantiateMsg { count: 17 }; 128 | let info = mock_info("creator", &coins(1000, "earth")); 129 | 130 | // we can just call .unwrap() to assert this was a success 131 | let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); 132 | assert_eq!(0, res.messages.len()); 133 | 134 | // it worked, let's query the state 135 | let res = query(deps.as_ref(), mock_env(), QueryMsg::GetCount {}).unwrap(); 136 | let value: GetCountResponse = from_binary(&res).unwrap(); 137 | assert_eq!(17, value.count); 138 | } 139 | 140 | #[test] 141 | fn increment() { 142 | let mut deps = mock_dependencies(); 143 | 144 | let msg = InstantiateMsg { count: 17 }; 145 | let info = mock_info("creator", &coins(2, "token")); 146 | let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); 147 | 148 | // beneficiary can release it 149 | let info = mock_info("anyone", &coins(2, "token")); 150 | let msg = ExecuteMsg::Increment {}; 151 | let _res = execute(deps.as_mut(), mock_env(), info, msg).unwrap(); 152 | 153 | // should increase counter by 1 154 | let res = query(deps.as_ref(), mock_env(), QueryMsg::GetCount {}).unwrap(); 155 | let value: GetCountResponse = from_binary(&res).unwrap(); 156 | assert_eq!(18, value.count); 157 | } 158 | 159 | #[test] 160 | fn reset() { 161 | let mut deps = mock_dependencies(); 162 | 163 | let msg = InstantiateMsg { count: 17 }; 164 | let info = mock_info("creator", &coins(2, "token")); 165 | let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); 166 | 167 | // beneficiary can release it 168 | let unauth_info = mock_info("anyone", &coins(2, "token")); 169 | let msg = ExecuteMsg::Reset { count: 5 }; 170 | let res = execute(deps.as_mut(), mock_env(), unauth_info, msg); 171 | match res { 172 | Err(ContractError::Unauthorized {}) => {} 173 | _ => panic!("Must return unauthorized error"), 174 | } 175 | 176 | // only the original creator can reset the counter 177 | let auth_info = mock_info("creator", &coins(2, "token")); 178 | let msg = ExecuteMsg::Reset { count: 5 }; 179 | let _res = execute(deps.as_mut(), mock_env(), auth_info, msg).unwrap(); 180 | 181 | // should now be 5 182 | let res = query(deps.as_ref(), mock_env(), QueryMsg::GetCount {}).unwrap(); 183 | let value: GetCountResponse = from_binary(&res).unwrap(); 184 | assert_eq!(5, value.count); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to CONTRIBUTING.md 2 | 3 | Firstly, thanks for taking the time to contribute! 4 | 5 | All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help and details about how this project handles them. Please make sure to read the relevant section before making your contribution. It will make life a lot easier for maintainers and ensure a smoother experience for all involved. The community looks forward to your contributions. 6 | 7 | > If you like the project, but just don't have time to contribute, that's fine. There are many other easy ways to support the project and show your appreciation: 8 | > - Star the project 9 | > - Tweet about it 10 | > - Reference this project in your project's README 11 | > - Mention the project at local meetups and tell your friends/colleagues 12 | 13 | ## Table of Contents 14 | 15 | - [Code of Conduct](#code-of-conduct) 16 | - [I Have a Question](#i-have-a-question) 17 | - [I Want To Contribute](#i-want-to-contribute) 18 | - [Reporting Bugs](#reporting-bugs) 19 | - [Suggesting Enhancements](#suggesting-enhancements) 20 | - [Commit Messages](#commit-messages) 21 | - [Attribution](#attribution) 22 | 23 | ## Code of Conduct 24 | 25 | This project and everyone participating in it is governed by the 26 | [Terran One Code of Conduct](https://github.com/Terran-One/cosmwasm-vm-js/blob/master/CODE_OF_CONDUCT.md). 27 | By participating, you are expected to uphold this code. Please report unacceptable behavior 28 | to . 29 | 30 | ## I Have a Question 31 | 32 | Before you ask a question, it is best to search for existing [Issues](https://github.com/Terran-One/cosmwasm-vm-js/issues) that might help you. In case you have found a suitable issue and still need clarification, you can write your question in this issue. It is also advisable to search the internet for answers first. 33 | 34 | If you then still feel the need to ask a question and need clarification, we recommend the following: 35 | 36 | - Open an [Issue](https://github.com/Terran-One/cosmwasm-vm-js/issues/new). 37 | - Provide as much context as you can about what you're running into. 38 | - Provide project and platform versions (nodejs, npm, etc), depending on what seems relevant. 39 | 40 | We will then take care of the issue as soon as possible. 41 | 42 | ## I Want To Contribute 43 | 44 | > ### Legal Notice 45 | > When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project license. 46 | 47 | ### Reporting Bugs 48 | 49 | #### Before Submitting a Bug Report 50 | 51 | A good bug report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible. 52 | 53 | - Make sure that you are using the latest version. 54 | - Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions. If you are looking for support, you might want to check [this section](#i-have-a-question)). 55 | - To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error in the [bug tracker](https://github.com/Terran-One/cosmwasm-vm-js/issues?q=label%3Abug). 56 | - Also make sure to search the internet (including Stack Overflow) to see if users outside of the GitHub community have discussed the issue. 57 | - Collect information about the bug: 58 | - Stack trace (Traceback) 59 | - OS, Platform and Version (Windows, Linux, macOS, x86, ARM) 60 | - Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant. 61 | - Possibly your input and the output 62 | - Can you reliably reproduce the issue? And can you also reproduce it with older versions? 63 | 64 | #### How Do I Submit a Good Bug Report? 65 | 66 | > You must never report security related issues, vulnerabilities or bugs including sensitive information to the issue tracker, or elsewhere in public. Instead sensitive bugs must be sent by email to . 67 | 68 | We use GitHub issues to track bugs and errors. If you run into an issue with the project: 69 | 70 | - Open an [Issue](https://github.com/Terran-One/cosmwasm-vm-js/issues/new). (Since we can't be sure at this point whether it is a bug or not, we ask you not to talk about a bug yet and not to label the issue.) 71 | - Explain the behavior you would expect and the actual behavior. 72 | - Please provide as much context as possible and describe the *reproduction steps* that someone else can follow to recreate the issue on their own. This usually includes your code. For good bug reports you should isolate the problem and create a reduced test case. 73 | - Provide the information you collected in the previous section. 74 | 75 | Once it's filed: 76 | 77 | - The project team will label the issue accordingly. 78 | - A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for those steps and mark the issue as `needs-repro`. Bugs with the `needs-repro` tag will not be addressed until they are reproduced. 79 | - If the team is able to reproduce the issue, it will be marked `needs-fix`, as well as possibly other tags (such as `critical`), and the issue will be left to be implemented by someone. 80 | 81 | ### Suggesting Enhancements 82 | 83 | This section guides you through submitting an enhancement suggestion for CONTRIBUTING.md, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions. 84 | 85 | #### Before Submitting an Enhancement 86 | 87 | - Make sure that you are using the latest version. 88 | - Perform a [search](https://github.com/Terran-One/cosmwasm-vm-js/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one. 89 | - Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on/plugin library. 90 | 91 | #### How Do I Submit a Good Enhancement Suggestion? 92 | 93 | Enhancement suggestions are tracked as [GitHub issues](https://github.com/Terran-One/cosmwasm-vm-js/issues). 94 | 95 | - Use a **clear and descriptive title** for the issue to identify the suggestion. 96 | - Provide a **step-by-step description of the suggested enhancement** in as many details as possible. 97 | - **Describe the current behavior** and **explain which behavior you expected to see instead** and why. At this point you can also tell which alternatives do not work for you. 98 | - You may want to **include screenshots and animated GIFs** which help you demonstrate the steps or point out the part which the suggestion is related to. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) on Linux. 99 | - **Explain why this enhancement would be useful** to most CONTRIBUTING.md users. You may also want to point out the other projects that solved it better and which could serve as inspiration. 100 | 101 | ## Commit Messages 102 | 103 | We use [Conventional Commits](https://github.com/conventional-commits/conventionalcommits.org) for our commit messages. 104 | 105 | ## Attribution 106 | 107 | This guide is based on [CONTRIBUTING.md](https://contributing.md/). 108 | -------------------------------------------------------------------------------- /test/integration/hackatom.test.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs'; 2 | import { VMInstance } from "../../src/instance"; 3 | import { 4 | BasicBackendApi, 5 | BasicKVIterStorage, 6 | BasicQuerier, 7 | } from '../../src/backend'; 8 | import { fromBase64 } from '@cosmjs/encoding'; 9 | import { Region } from '../../src/memory'; 10 | import { expectResponseToBeOk, parseBase64Response } from '../common/test-vm'; 11 | 12 | type HackatomQueryRequest = { 13 | bank: { 14 | all_balances: { 15 | address: string 16 | } 17 | } 18 | } 19 | class HackatomMockQuerier extends BasicQuerier { 20 | private balances: Map = new Map(); 21 | 22 | update_balance(addr: string, balance: { amount: string; denom: string; }[]): { amount: string; denom: string; }[] { 23 | this.balances.set(addr, balance); 24 | return balance; 25 | } 26 | 27 | handleQuery(queryRequest: HackatomQueryRequest): any { 28 | if ('bank' in queryRequest) { 29 | if ('all_balances' in queryRequest.bank) { 30 | const { address } = queryRequest.bank.all_balances; 31 | return { amount: this.balances.get(address) || [] } 32 | } 33 | } 34 | 35 | throw new Error(`unknown query: ${JSON.stringify(queryRequest)}`); 36 | } 37 | } 38 | 39 | const wasmBytecode = readFileSync('testdata/v1.1/hackatom.wasm'); 40 | 41 | const verifier = 'terra1kzsrgcktshvqe9p089lqlkadscqwkezy79t8y9'; 42 | const beneficiary = 'terra1zdpgj8am5nqqvht927k3etljyl6a52kwqup0je'; 43 | const creator = 'terra1337xewwfv3jdjuz8e0nea9vd8dpugc0k2dcyt3'; 44 | const mockContractAddr = 'cosmos2contract'; 45 | 46 | const mockEnv = { 47 | block: { 48 | height: 12345, 49 | time: '1571797419879305533', 50 | chain_id: 'cosmos-testnet-14002', 51 | }, 52 | contract: { address: mockContractAddr } 53 | }; 54 | 55 | const mockInfo: { sender: string, funds: { amount: string, denom: string }[] } = { 56 | sender: creator, 57 | funds: [] 58 | }; 59 | 60 | let vm: VMInstance; 61 | describe('hackatom', () => { 62 | let querier: HackatomMockQuerier; 63 | 64 | beforeEach(async () => { 65 | querier = new HackatomMockQuerier(); 66 | vm = new VMInstance({ 67 | backend_api: new BasicBackendApi('terra'), 68 | storage: new BasicKVIterStorage(), 69 | querier 70 | }); 71 | await vm.build(wasmBytecode); 72 | }); 73 | 74 | 75 | it('proper_initialization', async () => { 76 | // Act 77 | const instantiateResponse = vm.instantiate(mockEnv, mockInfo, { verifier, beneficiary }); 78 | 79 | // Assert 80 | expect(instantiateResponse.json).toEqual({ 81 | ok: { 82 | attributes: [ 83 | { key: 'Let the', value: 'hacking begin' }, 84 | ], 85 | data: null, 86 | events: [], 87 | messages: [], 88 | }, 89 | }); 90 | expectVerifierToBe(verifier); 91 | }); 92 | 93 | it('instantiate_and_query', async () => { 94 | // Arrange 95 | vm.instantiate(mockEnv, mockInfo, { verifier, beneficiary }); 96 | 97 | // Act 98 | const queryResponse = vm.query(mockEnv, { verifier: {} }); 99 | 100 | // Assert 101 | expectResponseToBeOk(queryResponse); 102 | expect(parseBase64OkResponse(queryResponse)).toEqual({ verifier }); 103 | }); 104 | 105 | it('migrate_verifier', async () => { 106 | // Arrange 107 | vm.instantiate(mockEnv, mockInfo, { verifier, beneficiary }); 108 | 109 | // Act 110 | const newVerifier = 'terra1h8ljdmae7lx05kjj79c9ekscwsyjd3yr8wyvdn' 111 | let response = vm.migrate(mockEnv, { verifier: newVerifier }); 112 | 113 | // Assert 114 | expectResponseToBeOk(response); 115 | expect((response.json as { ok: { messages: any[] }}).ok.messages.length).toBe(0); 116 | expectVerifierToBe(newVerifier); 117 | }); 118 | 119 | it.skip('sudo_can_steal_tokens', async () => {}); // sudo not implemented 120 | 121 | it('querier_callbacks_work', async () => { 122 | // Arrange 123 | const richAddress = 'foobar'; 124 | const richBalance = [{ amount: '10000', denom: 'gold' }]; 125 | querier.update_balance(richAddress, richBalance); 126 | 127 | vm.instantiate(mockEnv, mockInfo, { verifier, beneficiary }); 128 | 129 | // Act 130 | const queryResponse = vm.query(mockEnv, { other_balance: { address: richAddress } }); 131 | const queryResponseWrongAddress = vm.query(mockEnv, { other_balance: { address: 'other address' } }); 132 | 133 | // Assert 134 | expectResponseToBeOk(queryResponse); 135 | expect(parseBase64OkResponse(queryResponse).amount).toEqual(richBalance); 136 | 137 | expectResponseToBeOk(queryResponseWrongAddress); 138 | expect(parseBase64OkResponse(queryResponseWrongAddress).amount).toEqual([]); 139 | }); 140 | 141 | it('fails_on_bad_init', async () => { 142 | // Act 143 | const response = vm.instantiate( 144 | mockEnv, 145 | { funds: [{ amount: '1000', denom: 'earth' }] } as any, // invalid info message, missing sender field 146 | { verifier, beneficiary }); 147 | 148 | // Assert 149 | expect((response.json as { error: string }).error.indexOf('Error parsing')).toBe(0); 150 | }); 151 | 152 | it('execute_release_works', async () => { 153 | // Arrange 154 | vm.instantiate(mockEnv, mockInfo, { verifier, beneficiary }); 155 | querier.update_balance(mockContractAddr, [{ amount: '1000', denom: 'earth' }]); 156 | 157 | // Act 158 | const execResponse = vm.execute( 159 | mockEnv, 160 | { sender: verifier, funds: [] }, 161 | { release: {} }); 162 | 163 | // Assert 164 | expectResponseToBeOk(execResponse); 165 | 166 | expect((execResponse.json as any).ok.messages.length).toBe(1); 167 | expect((execResponse.json as any).ok.messages[0].msg.bank.send.to_address).toBe(beneficiary); 168 | expect((execResponse.json as any).ok.messages[0].msg.bank.send.amount).toStrictEqual([{ amount: '1000', denom: 'earth' }]); 169 | 170 | expect((execResponse.json as any).ok.attributes[0]).toStrictEqual({key: 'action', value: 'release'}); 171 | expect((execResponse.json as any).ok.attributes[1]).toStrictEqual({key: 'destination', value: beneficiary}); 172 | 173 | expect(fromBase64((execResponse.json as any).ok.data)[0]).toBe(240); // 0xF0 174 | expect(fromBase64((execResponse.json as any).ok.data)[1]).toBe(11); // 0x0B 175 | expect(fromBase64((execResponse.json as any).ok.data)[2]).toBe(170); // 0xAA 176 | }); 177 | 178 | it('execute_release_fails_for_wrong_sender', async () => { 179 | // Arrange 180 | vm.instantiate(mockEnv, mockInfo, { verifier, beneficiary }); 181 | querier.update_balance(mockContractAddr, [{ amount: '1000', denom: 'earth' }]); 182 | 183 | // Act 184 | const execResponse = vm.execute( 185 | mockEnv, 186 | { sender: beneficiary, funds: [] }, 187 | { release: {} }); 188 | 189 | // Assert 190 | expect((execResponse.json as any).error).toBe('Unauthorized'); 191 | }); 192 | 193 | it('execute_panic', async () => { 194 | // Arrange 195 | vm.instantiate(mockEnv, mockInfo, { verifier, beneficiary }); 196 | 197 | // Act 198 | expect(() => vm.execute(mockEnv, mockInfo, { panic: {} })).toThrow(); 199 | }); 200 | 201 | it('execute_user_errors_in_api_calls', async () => { 202 | // Arrange 203 | vm.instantiate(mockEnv, mockInfo, { verifier, beneficiary }); 204 | 205 | // Act 206 | expect(() => vm.execute(mockEnv, mockInfo, { user_errors_in_api_calls: {} })).toThrow(); 207 | }); 208 | }); 209 | 210 | // Helpers 211 | 212 | function expectVerifierToBe(addr: string) { 213 | const queryResponse = vm.query(mockEnv, { verifier: {} }); 214 | const verifier = parseBase64OkResponse(queryResponse); 215 | expect(verifier).toEqual({ verifier: addr }); 216 | } 217 | 218 | function parseBase64OkResponse(region: Region): any { 219 | const data = (region.json as { ok: string }).ok; 220 | if (!data) { 221 | throw new Error(`Response indicates an error state: ${JSON.stringify(region.json)}`) 222 | } 223 | 224 | return parseBase64Response(data); 225 | } 226 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CosmWasm VM in JavaScript 2 | 3 | This package contains an implementation of the CosmWasm VM that is runnable on Node.js and web browsers that support 4 | WebAssembly (currently only tested on V8 browsers like Google Chrome). 5 | This allows you to run `.wasm` binaries intended for CosmWasm without the need for a backend blockchain or Rust 6 | toolchain, enabling new ways to instrument and test CosmWasm smart contracts. 7 | 8 | **NOTE:** This package is intended to work with contracts built for CosmWasm v1.0. 9 | 10 | **NOTE:** Although great care has been taken to match the behavior of the original CosmWasm VM (powered by Wasmer), 11 | this implementation may not provide identical results and should not be used as a drop-in replacement. Results obtained 12 | should be verified against the original implementation for critical use-cases. 13 | 14 | ## Setup 15 | 16 | Add the `cosmwasm-vm-js` package as a dependency in your `package.json`. 17 | 18 | ```sh 19 | npm install -S @terran-one/cosmwasm-vm-js 20 | ``` 21 | 22 | or 23 | 24 | ```sh 25 | yarn add @terran-one/cosmwasm-vm-js 26 | ``` 27 | 28 | ## Usage 29 | 30 | ```ts 31 | import { readFileSync } from 'fs'; 32 | import { VMInstance } from '@terran-one/cosmwasm-vm-js'; 33 | import { 34 | BasicBackendApi, 35 | BasicKVIterStorage, 36 | BasicQuerier, 37 | IBackend, 38 | } from 'cosmwasm-vm-js/backend'; 39 | 40 | const wasmBytecode = readFileSync('testdata/cosmwasm_vm_test.wasm'); 41 | const backend: IBackend = { 42 | backend_api: new BasicBackendApi('terra'), 43 | storage: new BasicKVIterStorage(), 44 | querier: new BasicQuerier(), 45 | }; 46 | 47 | const vm = new VMInstance(backend); 48 | const mockEnv = { 49 | block: { 50 | height: 1337, 51 | time: '2000000000', 52 | chain_id: 'columbus-5', 53 | }, 54 | contract: { 55 | address: 'terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76', 56 | }, 57 | }; 58 | 59 | const mockInfo = { 60 | sender: 'terra1337xewwfv3jdjuz8e0nea9vd8dpugc0k2dcyt3', 61 | funds: [], 62 | }; 63 | 64 | describe('CosmWasmVM', () => { 65 | it('instantiates', async () => { 66 | await vm.build(wasmBytecode); 67 | 68 | const region = vm.instantiate(mockEnv, mockInfo, { count: 20 }); 69 | console.log(region.json); 70 | console.log(vm.backend); 71 | const actual = { 72 | ok: { 73 | attributes: [ 74 | { key: 'method', value: 'instantiate' }, 75 | { 76 | key: 'owner', 77 | value: 'terra1337xewwfv3jdjuz8e0nea9vd8dpugc0k2dcyt3', 78 | }, 79 | { key: 'count', value: '20' }, 80 | ], 81 | data: null, 82 | events: [], 83 | messages: [], 84 | }, 85 | }; 86 | expect(region.json).toEqual(actual); 87 | }); 88 | 89 | it('execute', async () => { 90 | await vm.build(wasmBytecode); 91 | 92 | let region = vm.instantiate(mockEnv, mockInfo, { count: 20 }); 93 | region = vm.execute(mockEnv, mockInfo, { increment: {} }); 94 | console.log(region.json); 95 | console.log(vm.backend); 96 | const actual = { 97 | ok: { 98 | attributes: [{ key: 'method', value: 'try_increment' }], 99 | data: null, 100 | events: [], 101 | messages: [], 102 | }, 103 | }; 104 | expect(region.json).toEqual(actual); 105 | }); 106 | }); 107 | ``` 108 | 109 | ## How it works 110 | 111 | CosmWasm smart contracts are WebAssembly binaries that export certain function symbols called "entrypoints", such as 112 | the following: 113 | 114 | - `instantiate` 115 | - `execute` 116 | - `query` 117 | - `migrate` 118 | 119 | Users interact and invoke operations on the smart contract by calling the desired entrypoint with arguments. 120 | As these are exposed as WebAssembly functions, we should normally be able to call them directly. 121 | However, CosmWasm contracts carry some implicit requirements that must be met before we can interact with the contract's 122 | functions naturally. 123 | 124 | 1. Contracts expect certain symbols to be provided by the VM host (WASM imports). 125 | 2. Contracts need an environment with storage to which it can read and write data. 126 | 3. Contract entrypoints expect to be called with input arguments prepared and allocated into memory in a certain way. 127 | 4. The response of contract entrypoint invocations should be parsed. 128 | 129 | `cosmwasm-vm-js` provides a VM implementation that addresses all of these requirements and exposes a simulated execution 130 | environment that can be further customized to enable possibilities such as instrumentation, visualization, debugging, 131 | and more. 132 | 133 | ## WASM Imports 134 | 135 | The following WASM imports have been implemented according to `imports.rs` in `cosmwasm-vm`. 136 | 137 | | Import Name | Implemented? | Tested? | Notes | 138 | | -------------------------- |--------------------|--------------------| -------------------------------------------- | 139 | | `db_read` | :white_check_mark: | :white_check_mark: | | 140 | | `db_write` | :white_check_mark: | :white_check_mark: | | 141 | | `db_remove` | :white_check_mark: | :white_check_mark: | | 142 | | `db_scan` | :white_check_mark: | :white_check_mark: | | 143 | | `db_next` | :white_check_mark: | :white_check_mark: | | 144 | | `addr_humanize` | :white_check_mark: | :white_check_mark: | | 145 | | `addr_canonicalize` | :white_check_mark: | :white_check_mark: | | 146 | | `addr_validate` | :white_check_mark: | :white_check_mark: | | 147 | | `secp256k1_verify` | :white_check_mark: | :white_check_mark: | | 148 | | `secp256k1_recover_pubkey` | :white_check_mark: | :white_check_mark: | | 149 | | `ed25519_verify` | :white_check_mark: | :white_check_mark: | | 150 | | `ed25519_batch_verify` | :white_check_mark: | :white_check_mark: | | 151 | | `debug` | :white_check_mark: | :white_check_mark: | Appends to a list of strings instead of printing to console. | 152 | | `query_chain` | :white_check_mark: | :white_check_mark: | | 153 | | `abort` | :white_check_mark: | :white_check_mark: | | 154 | 155 | ## Environment & Storage 156 | 157 | We provide a simple key-value store with bytes keys and bytes values in `BasicKVIterStorage`. 158 | 159 | ### WebAssembly Linear Memory 160 | 161 | A loaded CosmWasm contract module's linear memory is accessible as `WebAssembly.Memory`, which can be read as a 162 | bytearray through 163 | JavaScript's `Uint8Array` data type. 164 | 165 | ### Passing data from JavaScript to WASM 166 | 167 | To invoke entrypoint functions, we need to pass in arguments from JavaScript and load them into WebAssembly linear 168 | memory accessible by the contract. Although we can write directly to `WebAssembly.Memory`, doing this is considered 169 | unsafe as we don't know what we might be touching. 170 | Instead, we must use the contract's `allocate` entrypoint which gives us a pointer to a writeable region of linear 171 | memory which is recognized by the WASM code. 172 | 173 | `cosmwasm-vm-js` also provides the `Region` class, which is an analog of the `Region` type found in `cosmwasm-vm`. 174 | 175 | #### CosmWasm's `Region` type 176 | 177 | ```rust 178 | /// Describes some data allocated in Wasm's linear memory. 179 | /// A pointer to an instance of this can be returned over FFI boundaries. 180 | /// 181 | /// This is the same as `cosmwasm_std::memory::Region` 182 | /// but defined here to allow Wasmer specific implementation. 183 | #[repr(C)] 184 | #[derive(Default, Clone, Copy, Debug)] 185 | struct Region { 186 | /// The beginning of the region expressed as bytes from the beginning of the linear memory 187 | pub offset: u32, 188 | /// The number of bytes available in this region 189 | pub capacity: u32, 190 | /// The number of bytes used in this region 191 | pub length: u32, 192 | } 193 | ``` 194 | 195 | CosmWasm contract entrypoints expect their parameters to be pointers to `Region` structs, which point to the actual data 196 | via `offset`. 197 | 198 | ```text 199 | arg ---> Region ---> argument data 200 | ``` 201 | 202 | # License 203 | 204 | This software is licensed under the [MIT License](https://opensource.org/licenses/MIT). 205 | 206 | Copyright © 2022 Terran One LLC 207 | -------------------------------------------------------------------------------- /contracts/cw-vm-test/src/contract.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "library"))] 2 | use cosmwasm_std::entry_point; 3 | use cosmwasm_std::{ 4 | to_binary, Binary, CanonicalAddr, Deps, DepsMut, Env, MessageInfo, Response, StdResult, 5 | }; 6 | use cw2::set_contract_version; 7 | 8 | use crate::error::ContractError; 9 | use crate::msg::{CountResponse, ExecuteMsg, InstantiateMsg, QueryMsg}; 10 | use crate::state::{State, STATE}; 11 | 12 | // version info for migration info 13 | const CONTRACT_NAME: &str = "crates.io:cosmwasm-vm-test"; 14 | const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); 15 | 16 | #[cfg_attr(not(feature = "library"), entry_point)] 17 | pub fn instantiate( 18 | deps: DepsMut, 19 | _env: Env, 20 | info: MessageInfo, 21 | msg: InstantiateMsg, 22 | ) -> Result { 23 | let state = State { 24 | count: msg.count, 25 | owner: info.sender.clone(), 26 | }; 27 | set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; 28 | STATE.save(deps.storage, &state)?; 29 | 30 | Ok(Response::new() 31 | .add_attribute("method", "instantiate") 32 | .add_attribute("owner", info.sender) 33 | .add_attribute("count", msg.count.to_string())) 34 | } 35 | 36 | #[cfg_attr(not(feature = "library"), entry_point)] 37 | pub fn execute( 38 | deps: DepsMut, 39 | _env: Env, 40 | info: MessageInfo, 41 | msg: ExecuteMsg, 42 | ) -> Result { 43 | match msg { 44 | ExecuteMsg::Increment {} => try_increment(deps), 45 | ExecuteMsg::Reset { count } => try_reset(deps, info, count), 46 | ExecuteMsg::AddrValidate { str } => try_addr_validate(deps, str), 47 | ExecuteMsg::AddrHumanize { str } => try_addr_humanize(deps, str), 48 | ExecuteMsg::AddrCanonicalize { str } => try_addr_canonicalize(deps, str), 49 | ExecuteMsg::Secp256k1Verify { 50 | hash, 51 | signature, 52 | public_keystr, 53 | } => try_secp256k1_verify(deps, hash, signature, public_keystr), 54 | ExecuteMsg::Debug { message } => try_debug(deps, message), 55 | ExecuteMsg::DbWrite { key, value } => try_db_write(deps, key, value), 56 | ExecuteMsg::DbRead { key } => try_db_read(deps, key), 57 | } 58 | } 59 | 60 | pub fn try_debug(_deps: DepsMut, _message: String) -> Result { 61 | Ok(Default::default()) 62 | } 63 | 64 | pub fn try_db_write(deps: DepsMut, key: String, value: String) -> Result { 65 | deps.storage.set(key.as_bytes(), value.as_bytes()); 66 | Ok(Default::default()) 67 | } 68 | 69 | pub fn try_db_read(deps: DepsMut, key: String) -> Result { 70 | let value = deps.storage.get(key.as_bytes()).unwrap(); 71 | Ok(Response::default().add_attribute("value", String::from_utf8(value).unwrap())) 72 | } 73 | 74 | pub fn try_addr_humanize(deps: DepsMut, str: String) -> Result { 75 | Ok(Response::new().add_attribute( 76 | "result", 77 | deps.api 78 | .addr_humanize(&CanonicalAddr::from(str.as_bytes()))? 79 | .to_string(), 80 | )) 81 | } 82 | 83 | pub fn try_addr_canonicalize(deps: DepsMut, str: String) -> Result { 84 | Ok(Response::new().add_attribute("result", deps.api.addr_canonicalize(&str)?.to_string())) 85 | } 86 | 87 | pub fn try_secp256k1_verify( 88 | deps: DepsMut, 89 | hash: String, 90 | signature: String, 91 | public_keystr: String, 92 | ) -> Result { 93 | let hashed = hex::decode(&hash).unwrap(); 94 | let signed = hex::decode(&signature).unwrap(); 95 | let pubkey = hex::decode(&public_keystr).unwrap(); 96 | 97 | let res = deps 98 | .api 99 | .secp256k1_verify(&hashed, &signed, &pubkey) 100 | .unwrap_err(); 101 | Ok(Response::new().add_attribute("result", res.to_string())) 102 | } 103 | 104 | pub fn try_addr_validate(deps: DepsMut, str: String) -> Result { 105 | Ok(Response::new().add_attribute("result", deps.api.addr_validate(&str)?.to_string())) 106 | } 107 | 108 | pub fn try_increment(deps: DepsMut) -> Result { 109 | STATE.update(deps.storage, |mut state| -> Result<_, ContractError> { 110 | state.count += 1; 111 | Ok(state) 112 | })?; 113 | 114 | Ok(Response::new().add_attribute("method", "try_increment")) 115 | } 116 | pub fn try_reset(deps: DepsMut, info: MessageInfo, count: i32) -> Result { 117 | STATE.update(deps.storage, |mut state| -> Result<_, ContractError> { 118 | if info.sender != state.owner { 119 | return Err(ContractError::Unauthorized {}); 120 | } 121 | state.count = count; 122 | Ok(state) 123 | })?; 124 | Ok(Response::new().add_attribute("method", "reset")) 125 | } 126 | 127 | #[cfg_attr(not(feature = "library"), entry_point)] 128 | pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { 129 | match msg { 130 | QueryMsg::GetCount {} => to_binary(&query_count(deps)?), 131 | } 132 | } 133 | 134 | fn query_count(deps: Deps) -> StdResult { 135 | let state = STATE.load(deps.storage)?; 136 | Ok(CountResponse { count: state.count }) 137 | } 138 | 139 | #[cfg(test)] 140 | mod tests { 141 | use super::*; 142 | use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; 143 | use cosmwasm_std::Api; 144 | use cosmwasm_std::{coins, from_binary}; 145 | 146 | #[test] 147 | fn proper_initialization() { 148 | let mut deps = mock_dependencies(&[]); 149 | 150 | let msg = InstantiateMsg { count: 17 }; 151 | let info = mock_info("creator", &coins(1000, "earth")); 152 | 153 | // we can just call .unwrap() to assert this was a success 154 | let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); 155 | assert_eq!(0, res.messages.len()); 156 | 157 | // it worked, let's query the state 158 | let res = query(deps.as_ref(), mock_env(), QueryMsg::GetCount {}).unwrap(); 159 | let value: CountResponse = from_binary(&res).unwrap(); 160 | assert_eq!(17, value.count); 161 | } 162 | 163 | #[test] 164 | fn increment() { 165 | let mut deps = mock_dependencies(&coins(2, "token")); 166 | 167 | let msg = InstantiateMsg { count: 17 }; 168 | let info = mock_info("creator", &coins(2, "token")); 169 | let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); 170 | 171 | // beneficiary can release it 172 | let info = mock_info("anyone", &coins(2, "token")); 173 | let msg = ExecuteMsg::Increment {}; 174 | let _res = execute(deps.as_mut(), mock_env(), info, msg).unwrap(); 175 | 176 | // should increase counter by 1 177 | let res = query(deps.as_ref(), mock_env(), QueryMsg::GetCount {}).unwrap(); 178 | let value: CountResponse = from_binary(&res).unwrap(); 179 | assert_eq!(18, value.count); 180 | } 181 | 182 | #[test] 183 | fn reset() { 184 | let mut deps = mock_dependencies(&coins(2, "token")); 185 | 186 | let msg = InstantiateMsg { count: 17 }; 187 | let info = mock_info("creator", &coins(2, "token")); 188 | let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); 189 | 190 | // beneficiary can release it 191 | let unauth_info = mock_info("anyone", &coins(2, "token")); 192 | let msg = ExecuteMsg::Reset { count: 5 }; 193 | let res = execute(deps.as_mut(), mock_env(), unauth_info, msg); 194 | match res { 195 | Err(ContractError::Unauthorized {}) => {} 196 | _ => panic!("Must return unauthorized error"), 197 | } 198 | 199 | // only the original creator can reset the counter 200 | let auth_info = mock_info("creator", &coins(2, "token")); 201 | let msg = ExecuteMsg::Reset { count: 5 }; 202 | let _res = execute(deps.as_mut(), mock_env(), auth_info, msg).unwrap(); 203 | 204 | // should now be 5 205 | let res = query(deps.as_ref(), mock_env(), QueryMsg::GetCount {}).unwrap(); 206 | let value: CountResponse = from_binary(&res).unwrap(); 207 | assert_eq!(5, value.count); 208 | } 209 | 210 | #[test] 211 | fn secp256k1_verify_works() { 212 | let api = cosmwasm_std::testing::MockApi::default(); 213 | 214 | const SECP256K1_MSG_HASH_HEX: &str = 215 | "5ae8317d34d1e595e3fa7247db80c0af4320cce1116de187f8f7e2e099c0d8d0"; 216 | const SECP256K1_SIG_HEX: &str = "207082eb2c3dfa0b454e0906051270ba4074ac93760ba9e7110cd9471475111151eb0dbbc9920e72146fb564f99d039802bf6ef2561446eb126ef364d21ee9c4"; 217 | const SECP256K1_PUBKEY_HEX: &str = "04051c1ee2190ecfb174bfe4f90763f2b4ff7517b70a2aec1876ebcfd644c4633fb03f3cfbd94b1f376e34592d9d41ccaf640bb751b00a1fadeb0c01157769eb73"; 218 | 219 | let hash = hex::decode(SECP256K1_MSG_HASH_HEX).unwrap(); 220 | let signature = hex::decode(SECP256K1_SIG_HEX).unwrap(); 221 | let public_key = hex::decode(SECP256K1_PUBKEY_HEX).unwrap(); 222 | 223 | assert!(api 224 | .secp256k1_verify(&hash, &signature, &public_key) 225 | .unwrap()); 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /contracts/cw-vm-test/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /contracts/cosmwasm-vm-test/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /test/integration/crypto-verify.test.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs'; 2 | import { VMInstance } from "../../src/instance"; 3 | import { BasicBackendApi, BasicKVIterStorage, BasicQuerier, IBackend, } from '../../src/backend'; 4 | import * as testData from '../common/test-data'; 5 | import { parseBase64Response, wrapResult } from "../common/test-vm"; 6 | import { fromHex, toHex } from "@cosmjs/encoding"; 7 | 8 | const wasmBytecode = readFileSync('testdata/v1.1/crypto_verify.wasm'); 9 | 10 | const creator = 'creator'; 11 | const mockContractAddr = '0x12890D2cce102216644c59daE5baed380d84830c'; 12 | 13 | const mockEnv = { 14 | block: { 15 | height: 12345, 16 | time: '1571797419879305533', 17 | chain_id: 'cosmos-testnet-14002', 18 | }, 19 | contract: {address: mockContractAddr} 20 | }; 21 | 22 | const mockInfo = { 23 | sender: creator, 24 | funds: [] 25 | }; 26 | 27 | let vm: VMInstance; 28 | 29 | export function convertHexToBase64(hex: Uint8Array): string { 30 | return Buffer.from(toHex(hex), 'hex').toString('base64'); 31 | } 32 | 33 | export function convertStringToBase64(str: string): string { 34 | return Buffer.from(str, "binary").toString('base64'); 35 | } 36 | 37 | describe('crypto-verify', () => { 38 | beforeEach(async () => { 39 | const backend: IBackend = { 40 | backend_api: new BasicBackendApi('terra'), 41 | storage: new BasicKVIterStorage(), 42 | querier: new BasicQuerier(), 43 | }; 44 | 45 | vm = new VMInstance(backend); 46 | await vm.build(wasmBytecode); 47 | }); 48 | 49 | it('instantiate_works', async () => { 50 | const instantiateResponse = vm.instantiate(mockEnv, mockInfo, {}); 51 | 52 | expect(instantiateResponse.json).toEqual({ 53 | ok: { 54 | attributes: [], 55 | data: null, 56 | events: [], 57 | messages: [], 58 | }, 59 | }); 60 | }); 61 | 62 | it('cosmos_signature_verify_works', async () => { 63 | vm.instantiate(mockEnv, mockInfo, {}); 64 | 65 | const verify_msg = { 66 | verify_cosmos_signature: { 67 | message: convertHexToBase64(testData.SECP256K1_MESSAGE_HEX), 68 | signature: convertHexToBase64(testData.ECDSA_SIG_HEX), 69 | public_key: convertHexToBase64(testData.ECDSA_PUBKEY_HEX), 70 | } 71 | }; 72 | const raw = wrapResult(vm.query(mockEnv, verify_msg)).unwrap(); 73 | const res = parseBase64Response(raw); 74 | expect(res).toEqual({ 75 | verifies: true, 76 | }); 77 | }); 78 | 79 | it('cosmos_signature_verify_fails', async () => { 80 | vm.instantiate(mockEnv, mockInfo, {}); 81 | 82 | const message = new Uint8Array([... testData.SECP256K1_MESSAGE_HEX]); 83 | message[0] ^= 0x01; 84 | const verify_msg = { 85 | verify_cosmos_signature: { 86 | message: convertHexToBase64(message), 87 | signature: convertHexToBase64(testData.ECDSA_SIG_HEX), 88 | public_key: convertHexToBase64(testData.ECDSA_PUBKEY_HEX), 89 | } 90 | }; 91 | const raw = wrapResult(vm.query(mockEnv, verify_msg)).unwrap(); 92 | const res = parseBase64Response(raw); 93 | expect(res).toEqual({ 94 | verifies: false, 95 | }); 96 | }); 97 | 98 | it('cosmos_signature_verify_errors', async () => { 99 | vm.instantiate(mockEnv, mockInfo, {}); 100 | 101 | const verify_msg = { 102 | verify_cosmos_signature: { 103 | message: convertHexToBase64(testData.SECP256K1_MESSAGE_HEX), 104 | signature: convertHexToBase64(testData.ECDSA_SIG_HEX), 105 | public_key: convertHexToBase64(new Uint8Array(0)), 106 | } 107 | }; 108 | 109 | try { 110 | vm.query(mockEnv, verify_msg) 111 | } catch (e: any) { 112 | expect(e.message).toEqual('Expected public key to be an Uint8Array with length [33, 65]'); 113 | } 114 | }); 115 | 116 | it('ethereum_signature_verify_works', async () => { 117 | vm.instantiate(mockEnv, mockInfo, {}); 118 | const verify_msg = { 119 | verify_ethereum_text: { 120 | message: testData.ETHEREUM_MESSAGE, 121 | signature: convertHexToBase64(testData.ETHEREUM_SIGNATURE_HEX), 122 | signer_address: testData.ETHEREUM_SIGNER_ADDRESS, 123 | } 124 | }; 125 | const raw = wrapResult(vm.query(mockEnv, verify_msg)).unwrap(); 126 | const res = parseBase64Response(raw); 127 | 128 | expect(res).toEqual({ 129 | verifies: true, 130 | }); 131 | }); 132 | 133 | it('ethereum_signature_verify_fails_for_corrupted_message', async () => { 134 | vm.instantiate(mockEnv, mockInfo, {}); 135 | 136 | const message = testData.ETHEREUM_MESSAGE + '!'; 137 | const verify_msg = { 138 | verify_ethereum_text: { 139 | message: convertStringToBase64(message), 140 | signature: convertHexToBase64(testData.ETHEREUM_SIGNATURE_HEX), 141 | signer_address: testData.ETHEREUM_SIGNER_ADDRESS, 142 | } 143 | }; 144 | const raw = wrapResult(vm.query(mockEnv, verify_msg)).unwrap(); 145 | const res = parseBase64Response(raw); 146 | expect(res).toEqual({ 147 | verifies: false, 148 | }); 149 | }); 150 | 151 | it('ethereum_signature_verify_fails_for_corrupted_signature', async () => { 152 | vm.instantiate(mockEnv, mockInfo, {}); 153 | 154 | // Wrong signature 155 | const signature = new Uint8Array([... testData.ETHEREUM_SIGNATURE_HEX]); 156 | signature[5] ^= 0x01; 157 | const verify_msg = { 158 | verify_ethereum_text: { 159 | message: convertStringToBase64(testData.ETHEREUM_MESSAGE), 160 | signature: convertHexToBase64(signature), 161 | signer_address: testData.ETHEREUM_SIGNER_ADDRESS, 162 | } 163 | }; 164 | const raw = wrapResult(vm.query(mockEnv, verify_msg)).unwrap(); 165 | const res = parseBase64Response(raw); 166 | expect(res).toEqual({ 167 | verifies: false, 168 | }); 169 | 170 | // broken signature 171 | const signature2 = new Uint8Array(65).fill(0x1c); 172 | const verify_msg2 = { 173 | verify_ethereum_text: { 174 | message: convertStringToBase64(testData.ETHEREUM_MESSAGE), 175 | signature: convertHexToBase64(signature2), 176 | signer_address: testData.ETHEREUM_SIGNER_ADDRESS, 177 | } 178 | }; 179 | try { 180 | vm.query(mockEnv, verify_msg2); 181 | } catch (e: any) { 182 | expect(e.message).toEqual('Public key could not be recover'); 183 | } 184 | }); 185 | 186 | it('verify_ethereum_transaction_works', async () => { 187 | vm.instantiate(mockEnv, mockInfo, {}); 188 | 189 | const nonce = 225; 190 | const chain_id = 4; 191 | const from = '0x0a65766695a712af41b5cfecaad217b1a11cb22a'; 192 | const to = '0xe137f5264b6b528244e1643a2d570b37660b7f14'; 193 | const gas_limit = '141000'; 194 | const gas_price = '1000000000'; 195 | const value = '5445500'; 196 | const data = Buffer.from([83, 101, 97, 114, 99, 104, 32, 116, 120, 32, 116, 101, 115, 116, 32, 48, 46, 54, 53, 57, 48, 56, 54, 57, 49, 55, 51, 57, 54, 52, 51, 51, 53]).toString('base64'); 197 | const r = Buffer.from([185, 41, 157, 171, 80, 179, 205, 220, 174, 205, 100, 178, 155, 251, 213, 205, 48, 250, 193, 161, 173, 234, 27, 53, 154, 19, 196, 229, 23, 20, 146, 166]).toString('base64'); 198 | const s = Buffer.from([87, 48, 89, 198, 109, 137, 70, 132, 72, 143, 146, 231, 206, 31, 145, 177, 88, 202, 87, 176, 35, 84, 133, 98, 91, 87, 106, 59, 152, 196, 128, 172]).toString('base64'); 199 | const v = 43; 200 | 201 | const msg = { 202 | verify_ethereum_transaction: { 203 | from: from, 204 | to: to, 205 | nonce: nonce, 206 | gas_price: gas_price, 207 | gas_limit: gas_limit, 208 | value: value, 209 | data: data, 210 | chain_id: chain_id, 211 | r: r, 212 | s: s, 213 | v: v, 214 | } 215 | }; 216 | 217 | const raw = wrapResult(vm.query(mockEnv, msg)).unwrap(); 218 | const res = parseBase64Response(raw); 219 | expect(res).toEqual({ 220 | verifies: true, 221 | }); 222 | }); 223 | 224 | it('tendermint_signature_verify_works', async () => { 225 | vm.instantiate(mockEnv, mockInfo, {}); 226 | 227 | const verify_msg = { 228 | verify_tendermint_signature: { 229 | message: convertHexToBase64(testData.ED25519_MESSAGE_HEX), 230 | signature: convertHexToBase64(testData.ED25519_SIGNATURE_HEX), 231 | public_key: convertHexToBase64(testData.ED25519_PUBLIC_KEY_HEX), 232 | } 233 | }; 234 | 235 | const raw = wrapResult(vm.query(mockEnv, verify_msg)).unwrap(); 236 | const res = parseBase64Response(raw); 237 | expect(res).toEqual({ 238 | verifies: true, 239 | }); 240 | }); 241 | 242 | it('tendermint_signature_verify_fails', async () => { 243 | vm.instantiate(mockEnv, mockInfo, {}); 244 | 245 | const message = new Uint8Array([... testData.ED25519_MESSAGE_HEX]); 246 | message[0] ^= 0x01; 247 | 248 | const verify_msg = { 249 | verify_tendermint_signature: { 250 | message: convertHexToBase64(message), 251 | signature: convertHexToBase64(testData.ED25519_SIGNATURE_HEX), 252 | public_key: convertHexToBase64(testData.ED25519_PUBLIC_KEY_HEX), 253 | } 254 | }; 255 | 256 | const raw = wrapResult(vm.query(mockEnv, verify_msg)).unwrap(); 257 | const res = parseBase64Response(raw); 258 | expect(res).toEqual({ 259 | verifies: false, 260 | }); 261 | }); 262 | 263 | it('tendermint_signature_verify_errors', async () => { 264 | vm.instantiate(mockEnv, mockInfo, {}); 265 | 266 | const verify_msg = { 267 | verify_tendermint_signature: { 268 | message: convertHexToBase64(testData.ED25519_MESSAGE_HEX), 269 | signature: convertHexToBase64(testData.ED25519_SIGNATURE_HEX), 270 | public_key: convertHexToBase64(new Uint8Array(0)), 271 | } 272 | }; 273 | 274 | const raw = wrapResult(vm.query(mockEnv, verify_msg)).unwrap(); 275 | const res = parseBase64Response(raw); 276 | expect(res).toEqual({ 277 | verifies: false, 278 | }); 279 | }); 280 | 281 | it('tendermint_signatures_batch_verify_works', async () => { 282 | vm.instantiate(mockEnv, mockInfo, {}); 283 | 284 | const verifyMsg = { 285 | verify_tendermint_batch: { 286 | messages: [ 287 | convertHexToBase64(testData.ED25519_MESSAGE_HEX), 288 | convertHexToBase64(testData.ED25519_MESSAGE2_HEX), 289 | ], 290 | signatures: [ 291 | convertHexToBase64(testData.ED25519_SIGNATURE_HEX), 292 | convertHexToBase64(testData.ED25519_SIGNATURE2_HEX), 293 | ], 294 | public_keys: [ 295 | convertHexToBase64(testData.ED25519_PUBLIC_KEY_HEX), 296 | convertHexToBase64(testData.ED25519_PUBLIC_KEY2_HEX), 297 | ], 298 | } 299 | }; 300 | 301 | const queryResult = vm.query(mockEnv, verifyMsg); 302 | expect((queryResult.json as any).error).not.toBeDefined(); 303 | 304 | const raw = wrapResult(queryResult).unwrap(); 305 | const res = parseBase64Response(raw); 306 | expect(res).toEqual({ 307 | verifies: true, 308 | }); 309 | }); 310 | 311 | it('tendermint_signatures_batch_verify_message_multisig_works', async () => { 312 | vm.instantiate(mockEnv, mockInfo, {}); 313 | 314 | const verifyMsg = { 315 | verify_tendermint_batch: { 316 | messages: [ 317 | convertHexToBase64(testData.ED25519_MESSAGE_HEX), 318 | ], 319 | signatures: [ 320 | convertHexToBase64(testData.ED25519_SIGNATURE_HEX), 321 | convertHexToBase64(testData.ED25519_SIGNATURE_HEX), 322 | ], 323 | public_keys: [ 324 | convertHexToBase64(testData.ED25519_PUBLIC_KEY_HEX), 325 | convertHexToBase64(testData.ED25519_PUBLIC_KEY_HEX), 326 | ], 327 | } 328 | }; 329 | 330 | const queryResult = vm.query(mockEnv, verifyMsg); 331 | expect((queryResult.json as any).error).not.toBeDefined(); 332 | 333 | const raw = wrapResult(queryResult).unwrap(); 334 | const res = parseBase64Response(raw); 335 | expect(res).toEqual({ 336 | verifies: true, 337 | }); 338 | }); 339 | 340 | it('tendermint_signatures_batch_verify_single_public_key_works', async () => { 341 | vm.instantiate(mockEnv, mockInfo, {}); 342 | 343 | const verifyMsg = { 344 | verify_tendermint_batch: { 345 | messages: [ 346 | convertHexToBase64(testData.ED25519_MESSAGE_HEX), 347 | convertHexToBase64(testData.ED25519_MESSAGE_HEX), 348 | ], 349 | signatures: [ 350 | convertHexToBase64(testData.ED25519_SIGNATURE_HEX), 351 | convertHexToBase64(testData.ED25519_SIGNATURE_HEX), 352 | ], 353 | public_keys: [ 354 | convertHexToBase64(testData.ED25519_PUBLIC_KEY_HEX), 355 | ], 356 | } 357 | }; 358 | 359 | const queryResult = vm.query(mockEnv, verifyMsg); 360 | expect((queryResult.json as any).error).not.toBeDefined(); 361 | 362 | const raw = wrapResult(queryResult).unwrap(); 363 | const res = parseBase64Response(raw); 364 | expect(res).toEqual({ 365 | verifies: true, 366 | }); 367 | }); 368 | 369 | it('tendermint_signatures_batch_verify_fails', async () => { 370 | vm.instantiate(mockEnv, mockInfo, {}); 371 | const messages = [ 372 | new Uint8Array([... testData.ED25519_MESSAGE_HEX]), 373 | new Uint8Array([... testData.ED25519_MESSAGE2_HEX]), 374 | ]; 375 | messages[1][0] ^= 0x01; 376 | 377 | const verifyMsg = { 378 | verify_tendermint_batch: { 379 | messages: messages.map(m => convertHexToBase64(m)), 380 | signatures: [ 381 | convertHexToBase64(testData.ED25519_SIGNATURE_HEX), 382 | convertHexToBase64(testData.ED25519_SIGNATURE2_HEX), 383 | ], 384 | public_keys: [ 385 | convertHexToBase64(testData.ED25519_PUBLIC_KEY_HEX), 386 | convertHexToBase64(testData.ED25519_PUBLIC_KEY2_HEX), 387 | ], 388 | } 389 | }; 390 | 391 | const queryResult = vm.query(mockEnv, verifyMsg); 392 | expect((queryResult.json as any).error).not.toBeDefined(); 393 | 394 | const raw = wrapResult(queryResult).unwrap(); 395 | const res = parseBase64Response(raw); 396 | expect(res).toEqual({ 397 | verifies: false, 398 | }); 399 | }); 400 | 401 | it('tendermint_signatures_batch_verify_errors', async () => { 402 | vm.instantiate(mockEnv, mockInfo, {}); 403 | 404 | const verifyMsg = { 405 | verify_tendermint_batch: { 406 | messages: [ 407 | convertHexToBase64(testData.ED25519_MESSAGE_HEX), 408 | convertHexToBase64(testData.ED25519_MESSAGE2_HEX), 409 | ], 410 | signatures: [ 411 | convertHexToBase64(testData.ED25519_SIGNATURE_HEX), 412 | convertHexToBase64(testData.ED25519_SIGNATURE2_HEX), 413 | ], 414 | public_keys: [ 415 | convertHexToBase64(testData.ED25519_PUBLIC_KEY_HEX), 416 | convertHexToBase64(fromHex("")) 417 | ], 418 | } 419 | }; 420 | 421 | const queryResult = vm.query(mockEnv, verifyMsg); 422 | expect((queryResult.json as any).error).not.toBeDefined(); 423 | 424 | const raw = wrapResult(queryResult).unwrap(); 425 | const res = parseBase64Response(raw); 426 | expect(res).toEqual({ 427 | verifies: false, 428 | }); 429 | }); 430 | 431 | it('query_works', async () => { 432 | vm.instantiate(mockEnv, mockInfo, {}); 433 | 434 | const raw = wrapResult(vm.query(mockEnv, {list_verification_schemes: {}})).unwrap(); 435 | const result = parseBase64Response(raw); 436 | expect(result).toEqual({ 437 | verification_schemes: [ 438 | "secp256k1", 439 | "ed25519", 440 | "ed25519_batch", 441 | ] 442 | }); 443 | }); 444 | }); 445 | --------------------------------------------------------------------------------