├── .cargo └── config ├── .circleci └── config.yml ├── .editorconfig ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── NOTICE ├── README.md ├── contracts ├── mesh-consumer │ ├── .cargo │ │ └── config │ ├── Cargo.toml │ └── src │ │ ├── bin │ │ └── schema.rs │ │ ├── contract.rs │ │ ├── error.rs │ │ ├── ibc.rs │ │ ├── lib.rs │ │ ├── msg.rs │ │ ├── state.rs │ │ └── testing │ │ ├── mod.rs │ │ ├── test_contract.rs │ │ ├── test_ibc.rs │ │ ├── test_ibc_ack.rs │ │ ├── test_ibc_receive.rs │ │ └── utils │ │ ├── executes.rs │ │ ├── helpers.rs │ │ ├── mod.rs │ │ └── setup.rs ├── mesh-lockup │ ├── .cargo │ │ └── config │ ├── Cargo.toml │ └── src │ │ ├── bin │ │ └── schema.rs │ │ ├── contract.rs │ │ ├── error.rs │ │ ├── helpers.rs │ │ ├── lib.rs │ │ ├── msg.rs │ │ ├── multitest.rs │ │ ├── multitest │ │ ├── bonding.rs │ │ ├── mock_grantee.rs │ │ └── suite.rs │ │ └── state.rs ├── mesh-provider │ ├── .cargo │ │ └── config │ ├── Cargo.toml │ └── src │ │ ├── bin │ │ └── schema.rs │ │ ├── contract.rs │ │ ├── error.rs │ │ ├── ibc.rs │ │ ├── lib.rs │ │ ├── msg.rs │ │ ├── state.rs │ │ └── testing │ │ ├── mod.rs │ │ ├── test_contract.rs │ │ ├── test_ibc.rs │ │ ├── test_instantiate.rs │ │ ├── test_queries.rs │ │ └── utils │ │ ├── execute.rs │ │ ├── helpers.rs │ │ ├── ibc_helpers.rs │ │ ├── mod.rs │ │ ├── query.rs │ │ ├── setup.rs │ │ └── setup_unit.rs ├── mesh-slasher │ ├── .cargo │ │ └── config │ ├── Cargo.toml │ └── src │ │ ├── bin │ │ └── schema.rs │ │ ├── contract.rs │ │ ├── error.rs │ │ ├── helpers.rs │ │ ├── integration_tests.rs │ │ ├── lib.rs │ │ ├── msg.rs │ │ └── state.rs └── meta-staking │ ├── .cargo │ └── config │ ├── Cargo.toml │ └── src │ ├── bin │ └── schema.rs │ ├── contract.rs │ ├── error.rs │ ├── lib.rs │ ├── msg.rs │ ├── state.rs │ └── testing │ ├── mod.rs │ ├── test_consumer.rs │ ├── test_delegations.rs │ ├── test_queries.rs │ ├── test_rewards.rs │ └── utils │ ├── executes.rs │ ├── mod.rs │ ├── queries.rs │ └── setup.rs ├── docker-compose.yml ├── docker ├── osmosis │ ├── .gitignore │ ├── README.md │ ├── env │ ├── generate_template.sh │ ├── start.sh │ ├── stop.sh │ └── template │ │ └── .osmosisd │ │ ├── 6fc458e78b00b50b013a0fa11b2d5e1fdcc4029c.address │ │ ├── config │ │ ├── app.toml │ │ ├── client.toml │ │ ├── config.toml │ │ ├── genesis.json │ │ ├── gentx │ │ │ └── gentx-8c3f5817e96cc2a75d729f89a8ddfe23883f6c34.json │ │ ├── node_key.json │ │ └── priv_validator_key.json │ │ ├── data │ │ └── priv_validator_state.json │ │ ├── keyhash │ │ └── validator.info └── wasmd │ ├── README.md │ ├── env │ ├── generate_template.sh │ ├── scripts │ └── setup_wasmd.sh │ ├── start.sh │ ├── stop.sh │ └── template │ └── .wasmd │ ├── config │ ├── app.toml │ ├── client.toml │ ├── config.toml │ ├── genesis.json │ ├── gentx │ │ └── gentx-e00476a52bbadced4266814cee02b61c4cbdb860.json │ ├── node_key.json │ └── priv_validator_key.json │ ├── data │ └── priv_validator_state.json │ ├── f669faa106f9249f5a90c4fb05093f76e722a248.address │ ├── keyhash │ └── validator.info ├── docs ├── MeshSecurity.png ├── README.md ├── ROADMAP.md ├── UseCases.md ├── consumer │ ├── Consumer.md │ ├── Converter.md │ └── VirtualStaking.md ├── ibc │ ├── Overview.md │ ├── Rewards.md │ ├── Slashing.md │ └── Staking.md └── provider │ ├── LocalStaking.md │ ├── Provider.md │ └── Vault.md ├── justfile ├── package.json ├── packages ├── mesh-apis │ ├── .cargo │ │ └── config │ ├── Cargo.toml │ └── src │ │ ├── claims.rs │ │ ├── consumer_execute.rs │ │ ├── lib.rs │ │ ├── slash.rs │ │ └── staking_execute.rs ├── mesh-ibc │ ├── .cargo │ │ └── config │ ├── Cargo.toml │ └── src │ │ ├── ack.rs │ │ ├── checks.rs │ │ ├── ibc_msg.rs │ │ └── lib.rs └── mesh-testing │ ├── .cargo │ └── config │ ├── Cargo.toml │ └── src │ ├── constants.rs │ ├── contracts.rs │ ├── ibc_helpers.rs │ ├── instantiates.rs │ ├── lib.rs │ ├── macros.rs │ ├── msgs.rs │ └── multitest_helpers.rs ├── scripts ├── build_integration_wasm.sh ├── create_clients.sh ├── format_md.sh ├── format_sh.sh ├── format_yml.sh ├── optimize.sh └── schema.sh ├── tests ├── .eslintrc.json ├── .gitignore ├── .prettierignore ├── README.md ├── package.json ├── src │ ├── cosmwasm.spec.ts │ ├── scripts │ │ ├── config.ts │ │ ├── deploy-contracts.ts │ │ ├── helpers.ts │ │ └── networks.ts │ └── utils.ts └── tsconfig.json ├── typescript ├── .gitignore ├── contracts │ ├── mesh-consumer │ │ ├── MeshConsumer.client.ts │ │ ├── MeshConsumer.types.ts │ │ └── bundle.ts │ ├── mesh-lockup │ │ ├── MeshLockup.client.ts │ │ ├── MeshLockup.types.ts │ │ └── bundle.ts │ ├── mesh-provider │ │ ├── MeshProvider.client.ts │ │ ├── MeshProvider.types.ts │ │ └── bundle.ts │ ├── mesh-slasher │ │ ├── MeshSlasher.client.ts │ │ ├── MeshSlasher.types.ts │ │ └── bundle.ts │ └── meta-staking │ │ ├── MetaStaking.client.ts │ │ ├── MetaStaking.types.ts │ │ └── bundle.ts ├── package.json ├── src │ └── codegen.ts └── tsconfig.json └── yarn.lock /.cargo/config: -------------------------------------------------------------------------------- 1 | [alias] 2 | wasm = "build --release --lib --target wasm32-unknown-unknown" 3 | wasm-debug = "build --lib --target wasm32-unknown-unknown" 4 | clippy-all = "clippy --all-targets -- -D warnings" 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_style = space 7 | indent_size = 2 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | max_line_length = 120 11 | 12 | [*.rs] 13 | indent_size = 4 14 | max_line_length = 100 15 | 16 | [*.md] 17 | max_line_length = 0 18 | trim_trailing_whitespace = false 19 | 20 | [*.js] 21 | max_line_length = 80 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | artifacts/ 5 | internal/ 6 | 7 | # These are backup files generated by rustfmt 8 | **/*.rs.bk 9 | .idea 10 | 11 | # These files should not be commited 12 | *.log 13 | 14 | schema 15 | 16 | .DS_Store 17 | 18 | node_modules 19 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["packages/*", "contracts/*"] 3 | 4 | [workspace.package] 5 | edition = "2021" 6 | version = "0.1.0" 7 | license = "Apache-2.0" 8 | repository = "https://github.com/CosmWasm/mesh-security" 9 | homepage = "https://cosmwasm.com" 10 | documentation = "https://docs.cosmwasm.com" 11 | 12 | exclude = [ 13 | # 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. 14 | "contract.wasm", 15 | "hash.txt", 16 | ] 17 | 18 | [workspace.dependencies] 19 | mesh-ibc = { path = "./packages/mesh-ibc" } 20 | mesh-apis = { path = "./packages/mesh-apis" } 21 | mesh-consumer = { path = "./contracts/mesh-consumer" } 22 | mesh-slasher = { path = "./contracts/mesh-slasher" } 23 | meta-staking = { path = "./contracts/meta-staking" } 24 | mesh-provider = { path = "./contracts/mesh-provider" } 25 | cosmwasm-schema = "1.1" 26 | cosmwasm-std = { version = "1.1", features = ["ibc3"] } 27 | cosmwasm-storage = "1.1" 28 | cw-storage-plus = "1.0" 29 | cw-utils = "1.0" 30 | cw-controllers = "1.0" 31 | cw2 = "1.0" 32 | schemars = "0.8.11" 33 | serde = { version = "1.0.152", default-features = false, features = ["derive"] } 34 | thiserror = "1.0.38" 35 | # dev deps 36 | anyhow = "1" 37 | cw-multi-test = "0.16" 38 | derivative = "2" 39 | mesh-testing = { path = "./packages/mesh-testing" } 40 | test-case = "2.2.0" 41 | 42 | [profile.release.package.mesh-consumer] 43 | codegen-units = 1 44 | incremental = false 45 | 46 | [profile.release.package.mesh-lockup] 47 | codegen-units = 1 48 | incremental = false 49 | 50 | [profile.release.package.mesh-provider] 51 | codegen-units = 1 52 | incremental = false 53 | 54 | [profile.release.package.mesh-slasher] 55 | codegen-units = 1 56 | incremental = false 57 | 58 | [profile.release.package.meta-staking] 59 | codegen-units = 1 60 | incremental = false 61 | 62 | [profile.release] 63 | codegen-units = 1 64 | debug = false 65 | debug-assertions = false 66 | lto = true 67 | panic = 'abort' 68 | rpath = false 69 | opt-level = 3 70 | overflow-checks = true 71 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Mesh Security: Interchain Security in the Cosmos Model 2 | 3 | Copyright 2022 Ethan Frey 4 | Copyright 2022 Sunny Aggarwal 5 | Copyright 2022 Jake Hartnell 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | -------------------------------------------------------------------------------- /contracts/mesh-consumer/.cargo/config: -------------------------------------------------------------------------------- 1 | [alias] 2 | wasm = "build --release --lib --target wasm32-unknown-unknown" 3 | unit-test = "test --lib" 4 | schema = "run --bin schema" 5 | -------------------------------------------------------------------------------- /contracts/mesh-consumer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mesh-consumer" 3 | authors = ["Ethan Frey ", "Jake Hartnell "] 4 | 5 | edition = { workspace = true } 6 | version = { workspace = true } 7 | license = { workspace = true } 8 | repository = { workspace = true } 9 | homepage = { workspace = true } 10 | documentation = { workspace = true } 11 | exclude = { workspace = true } 12 | 13 | [lib] 14 | crate-type = ["cdylib", "rlib"] 15 | 16 | [features] 17 | # for more explicit tests, cargo test --features=backtraces 18 | backtraces = ["cosmwasm-std/backtraces"] 19 | # use library feature to disable all instantiate/execute/query exports 20 | library = [] 21 | 22 | [package.metadata.scripts] 23 | optimize = """docker run --rm -v "$(pwd)":/code \ 24 | --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ 25 | --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ 26 | cosmwasm/rust-optimizer:0.12.9 27 | """ 28 | 29 | [dependencies] 30 | cosmwasm-schema = { workspace = true } 31 | cosmwasm-std = { workspace = true } 32 | cosmwasm-storage = { workspace = true } 33 | cw-storage-plus = { workspace = true } 34 | cw2 = { workspace = true } 35 | mesh-apis = { workspace = true } 36 | mesh-ibc = { workspace = true } 37 | schemars = { workspace = true } 38 | serde = { workspace = true } 39 | thiserror = { workspace = true } 40 | 41 | [dev-dependencies] 42 | mesh-testing = { workspace = true } 43 | cw-multi-test = { workspace = true } 44 | 45 | [[bin]] 46 | name = "schema" 47 | doc = false 48 | -------------------------------------------------------------------------------- /contracts/mesh-consumer/src/bin/schema.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::write_api; 2 | use mesh_apis::ConsumerExecuteMsg; 3 | 4 | use mesh_consumer::msg::{InstantiateMsg, QueryMsg}; 5 | 6 | fn main() { 7 | write_api! { 8 | instantiate: InstantiateMsg, 9 | execute: ConsumerExecuteMsg, 10 | query: QueryMsg, 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /contracts/mesh-consumer/src/contract.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "library"))] 2 | use cosmwasm_std::entry_point; 3 | use cosmwasm_std::{ 4 | to_binary, Binary, Deps, DepsMut, Env, IbcMsg, MessageInfo, Response, StdResult, 5 | }; 6 | use cw2::set_contract_version; 7 | 8 | use mesh_apis::ConsumerExecuteMsg; 9 | use mesh_ibc::ConsumerMsg; 10 | 11 | use crate::error::ContractError; 12 | use crate::ibc::build_timeout; 13 | use crate::msg::{InstantiateMsg, QueryMsg}; 14 | use crate::state::{Config, CHANNEL, CONFIG, PACKET_LIFETIME}; 15 | 16 | // version info for migration info 17 | const CONTRACT_NAME: &str = "crates.io:mesh-consumer"; 18 | const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); 19 | 20 | // Default packet life time = 1 hour 21 | const DEFAULT_PACKET_LIFETIME: u64 = 60 * 60; 22 | 23 | #[cfg_attr(not(feature = "library"), entry_point)] 24 | pub fn instantiate( 25 | deps: DepsMut, 26 | _env: Env, 27 | _info: MessageInfo, 28 | msg: InstantiateMsg, 29 | ) -> Result { 30 | let meta_staking_contract_address = 31 | deps.api.addr_validate(&msg.meta_staking_contract_address)?; 32 | 33 | let config = Config { 34 | meta_staking_contract_address, 35 | provider: msg.provider, 36 | remote_to_local_exchange_rate: msg.remote_to_local_exchange_rate, 37 | ics20_channel: msg.ics20_channel, 38 | }; 39 | 40 | // Set packet lifetime from msg or set default 41 | PACKET_LIFETIME.save( 42 | deps.storage, 43 | &msg.packet_lifetime.unwrap_or(DEFAULT_PACKET_LIFETIME), 44 | )?; 45 | 46 | set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; 47 | CONFIG.save(deps.storage, &config)?; 48 | 49 | Ok(Response::new().add_attribute("method", "instantiate")) 50 | } 51 | 52 | #[cfg_attr(not(feature = "library"), entry_point)] 53 | pub fn execute( 54 | deps: DepsMut, 55 | env: Env, 56 | info: MessageInfo, 57 | msg: ConsumerExecuteMsg, 58 | ) -> Result { 59 | match msg { 60 | ConsumerExecuteMsg::MeshConsumerRecieveRewardsMsg { validator } => { 61 | execute_receive_rewards(deps, env, info, validator) 62 | } 63 | } 64 | } 65 | 66 | // We receive the rewards as funds from meta-stacking, and send it over IBC to mesh-provider 67 | pub fn execute_receive_rewards( 68 | deps: DepsMut, 69 | env: Env, 70 | info: MessageInfo, 71 | validator: String, 72 | ) -> Result { 73 | let channel_id = CHANNEL.load(deps.storage)?; 74 | 75 | let coin = info.funds[0].clone(); 76 | 77 | let msg = IbcMsg::SendPacket { 78 | channel_id, 79 | data: to_binary(&ConsumerMsg::Rewards { 80 | validator, 81 | total_funds: coin, 82 | })?, 83 | timeout: build_timeout(deps.as_ref(), &env)?, 84 | }; 85 | 86 | Ok(Response::default().add_message(msg)) 87 | } 88 | 89 | #[cfg_attr(not(feature = "library"), entry_point)] 90 | pub fn query(_deps: Deps, _env: Env, _msg: QueryMsg) -> StdResult { 91 | unimplemented!(); 92 | } 93 | 94 | #[cfg(test)] 95 | mod tests { 96 | use super::*; 97 | use crate::msg::ProviderInfo; 98 | use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; 99 | use cosmwasm_std::{coins, Decimal}; 100 | 101 | fn provider_info() -> ProviderInfo { 102 | ProviderInfo { 103 | port_id: "port-1".to_string(), 104 | connection_id: "conn-2".to_string(), 105 | } 106 | } 107 | 108 | #[test] 109 | fn proper_initialization() { 110 | let mut deps = mock_dependencies(); 111 | 112 | let msg = InstantiateMsg { 113 | meta_staking_contract_address: "meta_staking".to_string(), 114 | provider: provider_info(), 115 | remote_to_local_exchange_rate: Decimal::percent(10), 116 | ics20_channel: "channel-10".to_string(), 117 | packet_lifetime: None, 118 | }; 119 | let info = mock_info("creator", &coins(1000, "earth")); 120 | 121 | // we can just call .unwrap() to assert this was a success 122 | let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); 123 | assert_eq!(0, res.messages.len()); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /contracts/mesh-consumer/src/error.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::StdError; 2 | use thiserror::Error; 3 | 4 | use mesh_ibc::MeshSecurityError; 5 | 6 | #[derive(Error, Debug, PartialEq)] 7 | pub enum ContractError { 8 | #[error("{0}")] 9 | Std(#[from] StdError), 10 | 11 | #[error("{0}")] 12 | MeshSecurity(#[from] MeshSecurityError), 13 | 14 | #[error("Unauthorized")] 15 | Unauthorized {}, 16 | 17 | #[error("Couldn't parse provider from port_id")] 18 | ProviderAddrParsing {}, 19 | 20 | #[error("Contract already has a bound channel: {0}")] 21 | ChannelExists(String), 22 | 23 | #[error("Unauthorized counterparty chain, awaiting connection '{0}'")] 24 | WrongConnection(String), 25 | 26 | #[error("Unauthorized counterparty port, awaiting port '{0}'")] 27 | WrongPort(String), 28 | 29 | #[error("Refuse to respond on unregistered channel '{0}'")] 30 | UnknownChannel(String), 31 | 32 | #[error("Custom Error val: {val:?}")] 33 | CustomError { val: String }, 34 | 35 | #[error("Acknowledgement failed")] 36 | AckFailed {}, 37 | 38 | #[error("Rewards acknowledgement failed")] 39 | RewardsFailed {}, 40 | 41 | #[error("Update validators acknowledgement failed")] 42 | UpdateValidatorsFailed {}, 43 | } 44 | -------------------------------------------------------------------------------- /contracts/mesh-consumer/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod contract; 2 | mod error; 3 | pub mod ibc; 4 | pub mod msg; 5 | pub mod state; 6 | 7 | #[cfg(test)] 8 | mod testing; 9 | 10 | pub use crate::error::ContractError; 11 | -------------------------------------------------------------------------------- /contracts/mesh-consumer/src/msg.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::{cw_serde, QueryResponses}; 2 | use cosmwasm_std::Decimal; 3 | 4 | use crate::state::Config; 5 | 6 | #[cw_serde] 7 | pub struct InstantiateMsg { 8 | pub provider: ProviderInfo, 9 | pub remote_to_local_exchange_rate: Decimal, 10 | pub meta_staking_contract_address: String, 11 | pub ics20_channel: String, 12 | pub packet_lifetime: Option, 13 | } 14 | 15 | #[cw_serde] 16 | pub struct ProviderInfo { 17 | pub port_id: String, 18 | pub connection_id: String, 19 | } 20 | 21 | #[cw_serde] 22 | #[derive(QueryResponses)] 23 | pub enum QueryMsg { 24 | // Return configuration info 25 | #[returns(Config)] 26 | Config {}, 27 | } 28 | -------------------------------------------------------------------------------- /contracts/mesh-consumer/src/state.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::cw_serde; 2 | use cosmwasm_std::{Addr, Decimal}; 3 | use cw_storage_plus::Item; 4 | 5 | use crate::msg::ProviderInfo; 6 | 7 | #[cw_serde] 8 | pub struct Config { 9 | pub provider: ProviderInfo, 10 | pub remote_to_local_exchange_rate: Decimal, 11 | pub meta_staking_contract_address: Addr, 12 | pub ics20_channel: String, 13 | } 14 | 15 | pub const CONFIG: Item = Item::new("config"); 16 | pub const PACKET_LIFETIME: Item = Item::new("packet_time"); 17 | pub const CHANNEL: Item = Item::new("channel"); 18 | -------------------------------------------------------------------------------- /contracts/mesh-consumer/src/testing/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod utils; 2 | 3 | pub mod test_contract; 4 | pub mod test_ibc; 5 | pub mod test_ibc_ack; 6 | pub mod test_ibc_receive; 7 | -------------------------------------------------------------------------------- /contracts/mesh-consumer/src/testing/test_contract.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::{ 2 | coin, 3 | testing::{mock_env, mock_info}, 4 | to_binary, IbcMsg, 5 | }; 6 | use mesh_ibc::ConsumerMsg; 7 | use mesh_testing::constants::{CHANNEL_ID, NATIVE_DENOM, VALIDATOR}; 8 | 9 | use crate::{ 10 | ibc::build_timeout, 11 | testing::utils::{helpers::STAKING_ADDR, setup::setup_with_channel}, 12 | }; 13 | 14 | use super::utils::executes::execute_receive_rewards; 15 | 16 | #[test] 17 | fn recieve_rewards() { 18 | let (mut deps, _) = setup_with_channel(None); 19 | 20 | // test execute receive rewards 21 | let coin = coin(1000, NATIVE_DENOM); 22 | let info = mock_info(STAKING_ADDR, &[coin.clone()]); 23 | let res = execute_receive_rewards(deps.as_mut(), info, VALIDATOR).unwrap(); 24 | 25 | assert_eq!( 26 | res.messages[0].msg, 27 | IbcMsg::SendPacket { 28 | channel_id: CHANNEL_ID.to_string(), 29 | data: to_binary(&ConsumerMsg::Rewards { 30 | validator: VALIDATOR.to_string(), 31 | total_funds: coin, 32 | }) 33 | .unwrap(), 34 | timeout: build_timeout(deps.as_ref(), &mock_env()).unwrap(), 35 | } 36 | .into() 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /contracts/mesh-consumer/src/testing/test_ibc.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::{testing::mock_env, IbcChannelCloseMsg}; 2 | use mesh_ibc::IBC_APP_VERSION; 3 | use mesh_testing::{constants::CHANNEL_ID, ibc_helpers::mock_channel}; 4 | 5 | use crate::{ 6 | ibc::ibc_channel_close, 7 | testing::utils::{executes::ibc_connect, helpers::get_default_instantiate_msg}, 8 | ContractError, 9 | }; 10 | 11 | use super::utils::{ 12 | executes::{ibc_close_channel, ibc_open, ibc_open_channel}, 13 | setup::{setup, setup_with_channel}, 14 | }; 15 | 16 | #[test] 17 | fn close_channel() { 18 | let (mut deps, _) = setup_with_channel(None); 19 | 20 | ibc_close_channel(deps.as_mut()).unwrap(); 21 | } 22 | 23 | #[test] 24 | fn test_wrong_connection() { 25 | let wrong_connection = "some_connection".to_string(); 26 | let mut init_msg = get_default_instantiate_msg(); 27 | let channel = mock_channel(CHANNEL_ID, IBC_APP_VERSION); 28 | 29 | // Make sure we detect wrong connection 30 | init_msg.provider.connection_id = wrong_connection.clone(); 31 | let (mut deps, _) = setup(Some(init_msg)); 32 | let err = ibc_open(deps.as_mut(), channel).unwrap_err(); 33 | 34 | assert_eq!(err, ContractError::WrongConnection(wrong_connection)); 35 | } 36 | 37 | #[test] 38 | fn test_wrong_port() { 39 | let wrong_port = "some_port".to_string(); 40 | let mut init_msg = get_default_instantiate_msg(); 41 | let channel = mock_channel(CHANNEL_ID, IBC_APP_VERSION); 42 | // Check we detect wrong port 43 | init_msg.provider.port_id = wrong_port.clone(); 44 | let (mut deps, _) = setup(Some(init_msg)); 45 | let err = ibc_open(deps.as_mut(), channel).unwrap_err(); 46 | 47 | assert_eq!(err, ContractError::WrongPort(wrong_port)); 48 | } 49 | 50 | #[test] 51 | fn channel_already_exists() { 52 | let (mut deps, _) = setup_with_channel(None); 53 | 54 | let err = ibc_open_channel(deps.as_mut()).unwrap_err(); 55 | assert_eq!(err, ContractError::ChannelExists(CHANNEL_ID.to_string())); 56 | 57 | // Test we also get channelExist on connect 58 | let channel = mock_channel(CHANNEL_ID, IBC_APP_VERSION); 59 | let err = ibc_connect(deps.as_mut(), channel).unwrap_err(); 60 | assert_eq!(err, ContractError::ChannelExists(CHANNEL_ID.to_string())); 61 | } 62 | 63 | #[test] 64 | fn try_close_wrong_channel() { 65 | let (mut deps, _) = setup_with_channel(None); 66 | 67 | let some_channel = "some_channel"; 68 | let close_msg = IbcChannelCloseMsg::new_init(mock_channel(some_channel, IBC_APP_VERSION)); 69 | let err = ibc_channel_close(deps.as_mut(), mock_env(), close_msg).unwrap_err(); 70 | 71 | assert_eq!(err, ContractError::UnknownChannel(some_channel.to_string())) 72 | } 73 | -------------------------------------------------------------------------------- /contracts/mesh-consumer/src/testing/test_ibc_ack.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::{coin, testing::mock_env, IbcAcknowledgement, IbcMsg}; 2 | use mesh_ibc::RewardsResponse; 3 | use mesh_testing::{ 4 | constants::{ICS20_CHANNEL_ID, NATIVE_DENOM, REMOTE_PORT, VALIDATOR}, 5 | ibc_helpers::{to_ack_error, to_ack_success}, 6 | }; 7 | 8 | use crate::{ibc::build_timeout, ContractError}; 9 | 10 | use super::utils::{executes::ibc_ack_rewards, setup::setup_with_channel}; 11 | 12 | #[test] 13 | fn test_ibc_ack_rewards() { 14 | let (mut deps, _) = setup_with_channel(None); 15 | 16 | // We test a successful ack 17 | let ack = IbcAcknowledgement::new(to_ack_success(RewardsResponse {})); 18 | 19 | let res = ibc_ack_rewards(deps.as_mut(), VALIDATOR, 100, ack).unwrap(); 20 | 21 | assert_eq!( 22 | res.messages[0].msg, 23 | IbcMsg::Transfer { 24 | channel_id: ICS20_CHANNEL_ID.to_string(), 25 | to_address: REMOTE_PORT 26 | .to_string() 27 | .split('.') 28 | .last() 29 | .unwrap() 30 | .to_string(), // port - prefix 31 | amount: coin(100, NATIVE_DENOM), 32 | timeout: build_timeout(deps.as_ref(), &mock_env()).unwrap(), 33 | } 34 | .into() 35 | ); 36 | } 37 | 38 | // TODO: update_validators doesn't do anything, should we just delete it? 39 | #[test] 40 | #[ignore] 41 | fn test_ibc_update_validators() { 42 | unimplemented!() 43 | } 44 | 45 | // TODO: on failed ack we should revert stuff based on sent msg, 46 | // leaving todo here to make the tests better later. 47 | #[test] 48 | fn test_ibc_ack_failed() { 49 | let (mut deps, _) = setup_with_channel(None); 50 | 51 | // We test a error ack 52 | let ack = IbcAcknowledgement::new(to_ack_error("Something went wrong")); 53 | let err = ibc_ack_rewards(deps.as_mut(), VALIDATOR, 100, ack).unwrap_err(); 54 | assert_eq!(err, ContractError::AckFailed {}) 55 | } 56 | -------------------------------------------------------------------------------- /contracts/mesh-consumer/src/testing/test_ibc_receive.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::{ 2 | testing::mock_env, to_binary, Addr, Decimal, IbcPacketReceiveMsg, Uint128, Validator, WasmMsg, 3 | }; 4 | use mesh_apis::StakingExecuteMsg; 5 | use mesh_ibc::{ListValidatorsResponse, ProviderMsg, StakeResponse, UnstakeResponse}; 6 | use mesh_testing::{ 7 | addr, 8 | constants::{NATIVE_DENOM, RELAYER_ADDR, VALIDATOR}, 9 | ibc_helpers::{ack_unwrap, mock_packet}, 10 | }; 11 | 12 | use crate::{ibc::ibc_packet_receive, ContractError}; 13 | 14 | use super::utils::{ 15 | executes::ibc_receive_list_validators, 16 | executes::{ibc_receive_stake, ibc_receive_unstake}, 17 | helpers::STAKING_ADDR, 18 | setup::setup_with_channel, 19 | }; 20 | 21 | #[test] 22 | fn test_ibc_receive_list_validators() { 23 | let (mut deps, _) = setup_with_channel(None); 24 | 25 | // Update module to include validator 26 | deps.querier.update_staking( 27 | NATIVE_DENOM, 28 | &[Validator { 29 | address: VALIDATOR.to_string(), 30 | commission: Decimal::zero(), 31 | max_commission: Decimal::one(), 32 | max_change_rate: Decimal::one(), 33 | }], 34 | &[], 35 | ); 36 | 37 | let res = ibc_receive_list_validators(deps.as_mut()).unwrap(); 38 | let ack_res: ListValidatorsResponse = ack_unwrap(res.acknowledgement); 39 | 40 | assert_eq!(ack_res.validators, vec![VALIDATOR.to_string()],); 41 | } 42 | 43 | #[test] 44 | fn test_ibc_receive_stake() { 45 | let (mut deps, _) = setup_with_channel(None); 46 | 47 | let res = ibc_receive_stake(deps.as_mut(), VALIDATOR, 1000, "key_1").unwrap(); 48 | 49 | // Verify ack is success 50 | ack_unwrap::(res.acknowledgement.clone()); 51 | // Verify that we send msg to meat-staking, the sent msg amount is (amount / 10) or (amount * 0.1) 52 | assert_eq!( 53 | res.messages[0].msg, 54 | WasmMsg::Execute { 55 | contract_addr: STAKING_ADDR.to_string(), 56 | msg: to_binary(&StakingExecuteMsg::Delegate { 57 | validator: VALIDATOR.to_string(), 58 | amount: Uint128::new(100) 59 | }) 60 | .unwrap(), 61 | funds: vec![], 62 | } 63 | .into() 64 | ) 65 | } 66 | 67 | #[test] 68 | fn test_ibc_receive_unstake() { 69 | let (mut deps, _) = setup_with_channel(None); 70 | 71 | let res = ibc_receive_unstake(deps.as_mut(), VALIDATOR, 1000, "key_1").unwrap(); 72 | 73 | // Verify ack is success 74 | ack_unwrap::(res.acknowledgement.clone()); 75 | // Verify that we send msg to meat-staking, the sent msg amount is (amount / 10) or (amount * 0.1) 76 | assert_eq!( 77 | res.messages[0].msg, 78 | WasmMsg::Execute { 79 | contract_addr: STAKING_ADDR.to_string(), 80 | msg: to_binary(&StakingExecuteMsg::Undelegate { 81 | validator: VALIDATOR.to_string(), 82 | amount: Uint128::new(100) 83 | }) 84 | .unwrap(), 85 | funds: vec![], 86 | } 87 | .into() 88 | ) 89 | } 90 | 91 | #[test] 92 | fn test_ibc_receive_wrong_channel() { 93 | let (mut deps, _) = setup_with_channel(None); 94 | let mut packet = mock_packet(to_binary(&ProviderMsg::ListValidators {}).unwrap()); 95 | packet.dest.channel_id = "some_channel".to_string(); 96 | 97 | let err = ibc_packet_receive( 98 | deps.as_mut(), 99 | mock_env(), 100 | IbcPacketReceiveMsg::new(packet, addr!(RELAYER_ADDR)), 101 | ) 102 | .unwrap_err(); 103 | assert_eq!( 104 | err, 105 | ContractError::UnknownChannel("some_channel".to_string()) 106 | ) 107 | } 108 | -------------------------------------------------------------------------------- /contracts/mesh-consumer/src/testing/utils/executes.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::{ 2 | coin, 3 | testing::{mock_env, mock_info}, 4 | to_binary, Addr, DepsMut, Empty, Ibc3ChannelOpenResponse, IbcAcknowledgement, IbcBasicResponse, 5 | IbcChannel, IbcChannelCloseMsg, IbcChannelConnectMsg, IbcChannelOpenMsg, IbcPacketAckMsg, 6 | IbcPacketReceiveMsg, IbcReceiveResponse, MessageInfo, Response, Uint128, 7 | }; 8 | use mesh_apis::ConsumerExecuteMsg; 9 | use mesh_ibc::{ConsumerMsg, ProviderMsg, IBC_APP_VERSION}; 10 | use mesh_testing::{ 11 | addr, 12 | constants::{CHANNEL_ID, CREATOR_ADDR, NATIVE_DENOM, RELAYER_ADDR}, 13 | ibc_helpers::{mock_channel, mock_packet}, 14 | }; 15 | 16 | use crate::{ 17 | contract::{execute, instantiate}, 18 | ibc::{ 19 | ibc_channel_close, ibc_channel_connect, ibc_channel_open, ibc_packet_ack, 20 | ibc_packet_receive, 21 | }, 22 | msg::InstantiateMsg, 23 | ContractError, 24 | }; 25 | 26 | use super::helpers::get_default_instantiate_msg; 27 | 28 | pub fn instantiate_consumer(mut deps: DepsMut, init_msg: Option) -> Addr { 29 | let info = mock_info(CREATOR_ADDR, &[]); 30 | let env = mock_env(); 31 | let msg = init_msg.unwrap_or_else(get_default_instantiate_msg); 32 | 33 | instantiate(deps.branch(), env.clone(), info, msg).unwrap(); 34 | 35 | env.contract.address 36 | } 37 | 38 | pub fn execute_receive_rewards( 39 | deps: DepsMut, 40 | info: MessageInfo, 41 | validator: &str, 42 | ) -> Result, ContractError> { 43 | execute( 44 | deps, 45 | mock_env(), 46 | info, 47 | ConsumerExecuteMsg::MeshConsumerRecieveRewardsMsg { 48 | validator: validator.to_string(), 49 | }, 50 | ) 51 | } 52 | 53 | pub fn ibc_open( 54 | mut deps: DepsMut, 55 | channel: IbcChannel, 56 | ) -> Result, ContractError> { 57 | let open_msg = IbcChannelOpenMsg::new_init(channel); 58 | ibc_channel_open(deps.branch(), mock_env(), open_msg) 59 | } 60 | 61 | pub fn ibc_connect( 62 | mut deps: DepsMut, 63 | channel: IbcChannel, 64 | ) -> Result { 65 | let connect_msg = IbcChannelConnectMsg::new_ack(channel, IBC_APP_VERSION); 66 | ibc_channel_connect(deps.branch(), mock_env(), connect_msg) 67 | } 68 | 69 | pub fn ibc_open_channel(mut deps: DepsMut) -> Result<(), ContractError> { 70 | let channel = mock_channel(CHANNEL_ID, IBC_APP_VERSION); 71 | 72 | ibc_open(deps.branch(), channel.clone())?; 73 | ibc_connect(deps.branch(), channel)?; 74 | Ok(()) 75 | } 76 | 77 | pub fn ibc_close_channel(mut deps: DepsMut) -> Result<(), ContractError> { 78 | let channel = mock_channel(CHANNEL_ID, IBC_APP_VERSION); 79 | 80 | let close_msg = IbcChannelCloseMsg::new_init(channel); 81 | ibc_channel_close(deps.branch(), mock_env(), close_msg)?; 82 | Ok(()) 83 | } 84 | 85 | pub fn ibc_receive_list_validators(deps: DepsMut) -> Result { 86 | let packet = mock_packet(to_binary(&ProviderMsg::ListValidators {}).unwrap()); 87 | 88 | ibc_packet_receive( 89 | deps, 90 | mock_env(), 91 | IbcPacketReceiveMsg::new(packet, addr!(RELAYER_ADDR)), 92 | ) 93 | } 94 | 95 | pub fn ibc_receive_stake( 96 | deps: DepsMut, 97 | validator: &str, 98 | amount: u128, 99 | key: &str, 100 | ) -> Result { 101 | let packet = mock_packet( 102 | to_binary(&ProviderMsg::Stake { 103 | validator: validator.to_string(), 104 | amount: Uint128::new(amount), 105 | key: key.to_string(), 106 | }) 107 | .unwrap(), 108 | ); 109 | 110 | ibc_packet_receive( 111 | deps, 112 | mock_env(), 113 | IbcPacketReceiveMsg::new(packet, addr!(RELAYER_ADDR)), 114 | ) 115 | } 116 | 117 | pub fn ibc_receive_unstake( 118 | deps: DepsMut, 119 | validator: &str, 120 | amount: u128, 121 | key: &str, 122 | ) -> Result { 123 | let packet = mock_packet( 124 | to_binary(&ProviderMsg::Unstake { 125 | validator: validator.to_string(), 126 | amount: Uint128::new(amount), 127 | key: key.to_string(), 128 | }) 129 | .unwrap(), 130 | ); 131 | 132 | ibc_packet_receive( 133 | deps, 134 | mock_env(), 135 | IbcPacketReceiveMsg::new(packet, addr!(RELAYER_ADDR)), 136 | ) 137 | } 138 | 139 | pub fn ibc_ack_rewards( 140 | deps: DepsMut, 141 | validator: &str, 142 | amount: u128, 143 | ack: IbcAcknowledgement, 144 | ) -> Result { 145 | let original_packet = mock_packet( 146 | to_binary(&ConsumerMsg::Rewards { 147 | validator: validator.to_string(), 148 | total_funds: coin(amount, NATIVE_DENOM), 149 | }) 150 | .unwrap(), 151 | ); 152 | 153 | ibc_packet_ack( 154 | deps, 155 | mock_env(), 156 | IbcPacketAckMsg::new(ack, original_packet, addr!(RELAYER_ADDR)), 157 | ) 158 | } 159 | 160 | pub fn _ibc_ack_update_validators() {} 161 | -------------------------------------------------------------------------------- /contracts/mesh-consumer/src/testing/utils/helpers.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use cosmwasm_std::Decimal; 4 | use mesh_testing::constants::{CONNECTION_ID, ICS20_CHANNEL_ID, REMOTE_PORT}; 5 | 6 | use crate::msg::{InstantiateMsg, ProviderInfo}; 7 | 8 | pub const STAKING_ADDR: &str = "meta_staking"; 9 | 10 | pub fn get_default_instantiate_msg() -> InstantiateMsg { 11 | InstantiateMsg { 12 | provider: ProviderInfo { 13 | port_id: REMOTE_PORT.to_string(), 14 | connection_id: CONNECTION_ID.to_string(), 15 | }, 16 | remote_to_local_exchange_rate: Decimal::from_str("0.1").unwrap(), 17 | meta_staking_contract_address: STAKING_ADDR.to_string(), 18 | ics20_channel: ICS20_CHANNEL_ID.to_string(), 19 | packet_lifetime: None, 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /contracts/mesh-consumer/src/testing/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod executes; 2 | pub mod helpers; 3 | 4 | pub mod setup; 5 | -------------------------------------------------------------------------------- /contracts/mesh-consumer/src/testing/utils/setup.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::{ 2 | testing::{mock_dependencies, MockApi, MockQuerier}, 3 | Addr, Empty, MemoryStorage, OwnedDeps, 4 | }; 5 | 6 | use crate::msg::InstantiateMsg; 7 | 8 | use super::executes::{ibc_open_channel, instantiate_consumer}; 9 | 10 | type OwnedDepsType = OwnedDeps, Empty>; 11 | 12 | pub fn setup(init_msg: Option) -> (OwnedDepsType, Addr) { 13 | let mut deps = mock_dependencies(); 14 | let consumer_addr = instantiate_consumer(deps.as_mut(), init_msg); 15 | 16 | (deps, consumer_addr) 17 | } 18 | 19 | pub fn setup_with_channel(init_msg: Option) -> (OwnedDepsType, Addr) { 20 | let (mut deps, consumer_addr) = setup(init_msg); 21 | 22 | ibc_open_channel(deps.as_mut()).unwrap(); 23 | 24 | (deps, consumer_addr) 25 | } 26 | -------------------------------------------------------------------------------- /contracts/mesh-lockup/.cargo/config: -------------------------------------------------------------------------------- 1 | [alias] 2 | wasm = "build --release --lib --target wasm32-unknown-unknown" 3 | unit-test = "test --lib" 4 | schema = "run --bin schema" 5 | -------------------------------------------------------------------------------- /contracts/mesh-lockup/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mesh-lockup" 3 | authors = ["Ethan Frey "] 4 | 5 | edition = { workspace = true } 6 | version = { workspace = true } 7 | license = { workspace = true } 8 | repository = { workspace = true } 9 | homepage = { workspace = true } 10 | documentation = { workspace = true } 11 | exclude = { workspace = true } 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 | [features] 19 | # for more explicit tests, cargo test --features=backtraces 20 | backtraces = ["cosmwasm-std/backtraces"] 21 | # use library feature to disable all instantiate/execute/query exports 22 | library = [] 23 | 24 | [package.metadata.scripts] 25 | optimize = """docker run --rm -v "$(pwd)":/code \ 26 | --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ 27 | --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ 28 | cosmwasm/rust-optimizer:0.12.9 29 | """ 30 | 31 | [dependencies] 32 | mesh-apis = { workspace = true } 33 | cosmwasm-schema = { workspace = true } 34 | cosmwasm-std = { workspace = true } 35 | cosmwasm-storage = { workspace = true } 36 | cw-storage-plus = { workspace = true } 37 | cw2 = { workspace = true } 38 | cw-utils = { workspace = true } 39 | schemars = { workspace = true } 40 | serde = { workspace = true } 41 | thiserror = { workspace = true } 42 | 43 | [dev-dependencies] 44 | cw-multi-test = { workspace = true } 45 | test-case = { workspace = true } 46 | derivative = { workspace = true } 47 | anyhow = { workspace = true } 48 | 49 | [[bin]] 50 | name = "schema" 51 | doc = false 52 | -------------------------------------------------------------------------------- /contracts/mesh-lockup/src/bin/schema.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::write_api; 2 | 3 | use mesh_lockup::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; 4 | 5 | fn main() { 6 | write_api! { 7 | instantiate: InstantiateMsg, 8 | execute: ExecuteMsg, 9 | query: QueryMsg, 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /contracts/mesh-lockup/src/error.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::{StdError, Uint128}; 2 | use cw_utils::PaymentError; 3 | use thiserror::Error; 4 | 5 | #[derive(Error, Debug)] 6 | pub enum ContractError { 7 | #[error("{0}")] 8 | Std(#[from] StdError), 9 | 10 | #[error("{0}")] 11 | Payment(#[from] PaymentError), 12 | 13 | #[error("Unauthorized")] 14 | Unauthorized {}, 15 | 16 | #[error("Claim is locked, only {0} can be unbonded")] 17 | ClaimsLocked(Uint128), 18 | 19 | #[error("The address doesn't have sufficient balance for this operation")] 20 | InsufficentBalance, 21 | 22 | #[error("The leinholder doesn't have any claims")] 23 | UnknownLeinholder, 24 | 25 | #[error("The leinholder doesn't have enough claims for the action")] 26 | InsufficientLein, 27 | 28 | #[error("Custom Error val: {val:?}")] 29 | CustomError { val: String }, 30 | // Add any other custom errors you like here. 31 | // Look at https://docs.rs/thiserror/1.0.21/thiserror/ for details. 32 | } 33 | -------------------------------------------------------------------------------- /contracts/mesh-lockup/src/helpers.rs: -------------------------------------------------------------------------------- 1 | use schemars::JsonSchema; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use cosmwasm_std::Addr; 5 | // use cosmwasm_std::{to_binary, Addr, CosmosMsg, StdResult, WasmMsg}; 6 | // 7 | // use crate::msg::ExecuteMsg; 8 | 9 | /// MeshLockupContract is a wrapper around Addr that provides a lot of helpers 10 | /// for working with this. 11 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] 12 | pub struct MeshLockupContract(pub Addr); 13 | 14 | impl MeshLockupContract { 15 | pub fn addr(&self) -> Addr { 16 | self.0.clone() 17 | } 18 | 19 | // pub fn call>(&self, msg: T) -> StdResult { 20 | // let msg = to_binary(&msg.into())?; 21 | // Ok(WasmMsg::Execute { 22 | // contract_addr: self.addr().into(), 23 | // msg, 24 | // funds: vec![], 25 | // } 26 | // .into()) 27 | // } 28 | } 29 | -------------------------------------------------------------------------------- /contracts/mesh-lockup/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod contract; 2 | mod error; 3 | pub mod helpers; 4 | pub mod msg; 5 | #[cfg(test)] 6 | mod multitest; 7 | mod state; 8 | 9 | pub use crate::error::ContractError; 10 | -------------------------------------------------------------------------------- /contracts/mesh-lockup/src/msg.rs: -------------------------------------------------------------------------------- 1 | use crate::state::LeinAddr; 2 | use cosmwasm_schema::{cw_serde, QueryResponses}; 3 | use cosmwasm_std::Uint128; 4 | 5 | #[cw_serde] 6 | pub struct InstantiateMsg { 7 | pub denom: String, 8 | } 9 | 10 | #[cw_serde] 11 | pub enum ExecuteMsg { 12 | /// Places tokens in Lockup so they can be staked in multiple contracts. 13 | /// Must be sent in funds and proper denom 14 | Bond {}, 15 | /// Withdraws tokens from Lockup. 16 | /// Only works if the account has sufficient funds that is not backing open claims 17 | Unbond { amount: Uint128 }, 18 | /// This gives a claim on my balance to leinholder, granting it to a given validator 19 | /// In the case of granting a claim, the leinholder is the mesh-provider contract 20 | GrantClaim { 21 | leinholder: String, 22 | amount: Uint128, 23 | validator: String, 24 | }, 25 | /// This releases a previously received claim without slashing it 26 | ReleaseClaim { owner: String, amount: Uint128 }, 27 | /// This slashes a previously provided claim 28 | SlashClaim { owner: String, amount: Uint128 }, 29 | } 30 | 31 | #[cw_serde] 32 | #[derive(QueryResponses)] 33 | pub enum QueryMsg { 34 | #[returns(BalanceResponse)] 35 | Balance { account: String }, 36 | } 37 | 38 | #[cw_serde] 39 | pub struct BalanceResponse { 40 | pub bonded: Uint128, 41 | pub free: Uint128, 42 | pub claims: Vec, 43 | } 44 | 45 | #[cw_serde] 46 | pub struct Lein { 47 | pub leinholder: String, 48 | pub amount: Uint128, 49 | } 50 | 51 | impl From for Lein { 52 | fn from(lein: LeinAddr) -> Self { 53 | Lein { 54 | leinholder: lein.leinholder.into_string(), 55 | amount: lein.amount, 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /contracts/mesh-lockup/src/multitest.rs: -------------------------------------------------------------------------------- 1 | mod bonding; 2 | mod mock_grantee; 3 | mod suite; 4 | -------------------------------------------------------------------------------- /contracts/mesh-lockup/src/multitest/bonding.rs: -------------------------------------------------------------------------------- 1 | use super::suite::SuiteBuilder; 2 | use crate::multitest::suite::Suite; 3 | 4 | #[test] 5 | fn bond_and_unbond_same_tokens() { 6 | let actor = "jakub"; 7 | let start = 1234000u128; 8 | 9 | let mut suite: Suite = SuiteBuilder::new().with_funds(actor, start).build(); 10 | 11 | assert_eq!(suite.balance(actor).unwrap().u128(), start); 12 | 13 | // bond too much 14 | suite.bond(actor, 999999999999).unwrap_err(); 15 | // bond a bit 16 | let bond = 234000u128; 17 | suite.bond(actor, bond).unwrap(); 18 | 19 | // query amounts 20 | assert_eq!(suite.balance(actor).unwrap().u128(), start - bond); 21 | let bal = suite.lockup_balance(actor).unwrap(); 22 | assert_eq!(bal.bonded.u128(), bond); 23 | assert_eq!(bal.free.u128(), bond); 24 | assert_eq!(bal.claims.len(), 0); 25 | 26 | // unbond some back 27 | let unbond = 123000u128; 28 | suite.unbond(actor, unbond).unwrap(); 29 | 30 | // query amounts 31 | assert_eq!(suite.balance(actor).unwrap().u128(), start - bond + unbond); 32 | let bal = suite.lockup_balance(actor).unwrap(); 33 | assert_eq!(bal.bonded.u128(), bond - unbond); 34 | assert_eq!(bal.free.u128(), bond - unbond); 35 | assert_eq!(bal.claims.len(), 0); 36 | 37 | // cannot unbond more than you have 38 | suite.unbond(actor, start).unwrap_err(); 39 | } 40 | 41 | #[test] 42 | fn grant_and_release_tokens() { 43 | let actor = "jakub"; 44 | let validator = "val"; 45 | let start = 1234000u128; 46 | 47 | let mut suite: Suite = SuiteBuilder::new() 48 | .with_funds(actor, start) 49 | .with_denom("ujuno") 50 | .build(); 51 | 52 | // bond all 53 | suite.bond(actor, start).unwrap(); 54 | // proivde a claim 55 | let grant = 777000u128; 56 | suite.grant_claim(actor, grant, validator).unwrap(); 57 | 58 | // cannot unbond all 59 | suite.unbond(actor, start).unwrap_err(); 60 | // can only unbond remainder 61 | suite.unbond(actor, start - grant).unwrap(); 62 | assert_eq!(suite.balance(actor).unwrap().u128(), start - grant); 63 | 64 | // query amounts 65 | let bal = suite.lockup_balance(actor).unwrap(); 66 | assert_eq!(bal.bonded.u128(), grant); 67 | assert_eq!(bal.free.u128(), 0); 68 | assert_eq!(bal.claims.len(), 1); 69 | 70 | // release portion of a grant 71 | let release = 500_000u128; 72 | // not too much 73 | suite.release_claim(actor, grant + 1).unwrap_err(); 74 | // just right 75 | suite.release_claim(actor, release).unwrap(); 76 | 77 | // query amounts 78 | let bal = suite.lockup_balance(actor).unwrap(); 79 | assert_eq!(bal.bonded.u128(), grant); 80 | assert_eq!(bal.free.u128(), release); 81 | assert_eq!(bal.claims.len(), 1); 82 | 83 | // can only unbond remainder 84 | suite.unbond(actor, release + 1).unwrap_err(); 85 | suite.unbond(actor, release).unwrap(); 86 | } 87 | 88 | #[test] 89 | fn slashing_tokens() { 90 | let actor = "jakub"; 91 | let validator = "val"; 92 | let start = 1234000u128; 93 | 94 | let mut suite: Suite = SuiteBuilder::new().with_funds(actor, start).build(); 95 | 96 | // bond all 97 | suite.bond(actor, start).unwrap(); 98 | // proivde a claim 99 | let grant = 777000u128; 100 | suite.grant_claim(actor, grant, validator).unwrap(); 101 | 102 | // slash some 103 | let slash = 300_000u128; 104 | let release = grant - slash; 105 | suite.slash_claim(actor, slash).unwrap(); 106 | 107 | // query amounts 108 | let bal = suite.lockup_balance(actor).unwrap(); 109 | assert_eq!(bal.bonded.u128(), start - slash); 110 | assert_eq!(bal.free.u128(), start - grant); 111 | assert_eq!(bal.claims.len(), 1); 112 | 113 | // release rest and unbond 114 | suite.release_claim(actor, release).unwrap(); 115 | let bal = suite.lockup_balance(actor).unwrap(); 116 | assert_eq!(bal.bonded.u128(), start - slash); 117 | assert_eq!(bal.free.u128(), start - slash); 118 | assert_eq!(bal.claims.len(), 0); 119 | 120 | suite.unbond(actor, start - slash + 1).unwrap_err(); 121 | suite.unbond(actor, start - slash).unwrap(); 122 | assert_eq!(suite.balance(actor).unwrap().u128(), start - slash); 123 | } 124 | -------------------------------------------------------------------------------- /contracts/mesh-lockup/src/multitest/mock_grantee.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::cw_serde; 2 | use cosmwasm_std::{ 3 | to_binary, Addr, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, Uint128, 4 | WasmMsg, 5 | }; 6 | 7 | use cw_multi_test::{Contract, ContractWrapper}; 8 | use cw_storage_plus::Item; 9 | 10 | pub fn contract_mock() -> Box> { 11 | let contract = ContractWrapper::new(execute, instantiate, query); 12 | Box::new(contract) 13 | } 14 | 15 | #[cw_serde] 16 | pub struct InstantiateMsg { 17 | /// Address of Lockup contract from which we accept ReceiveClaim 18 | pub lockup: String, 19 | } 20 | 21 | #[cw_serde] 22 | pub enum ExecuteMsg { 23 | /// This gives the receiver access to slash part up to this much claim 24 | ReceiveClaim { 25 | owner: String, 26 | amount: Uint128, 27 | validator: String, 28 | }, 29 | /// This releases a previously received claim without slashing it 30 | Release { owner: String, amount: Uint128 }, 31 | /// This slashes a previously provided claim 32 | Slash { owner: String, amount: Uint128 }, 33 | } 34 | 35 | #[cw_serde] 36 | pub enum QueryMsg { 37 | DoNothing {}, 38 | } 39 | 40 | const LOCKUP: Item = Item::new("lockup"); 41 | 42 | pub fn instantiate( 43 | deps: DepsMut, 44 | _env: Env, 45 | _info: MessageInfo, 46 | msg: InstantiateMsg, 47 | ) -> StdResult { 48 | let addr = deps.api.addr_validate(&msg.lockup)?; 49 | LOCKUP.save(deps.storage, &addr)?; 50 | Ok(Response::new()) 51 | } 52 | 53 | pub fn execute( 54 | deps: DepsMut, 55 | _env: Env, 56 | _info: MessageInfo, 57 | msg: ExecuteMsg, 58 | ) -> StdResult { 59 | match msg { 60 | ExecuteMsg::ReceiveClaim { .. } => Ok(Response::new()), 61 | ExecuteMsg::Release { owner, amount } => { 62 | let msg = WasmMsg::Execute { 63 | contract_addr: LOCKUP.load(deps.storage)?.into_string(), 64 | msg: to_binary(&crate::msg::ExecuteMsg::ReleaseClaim { owner, amount })?, 65 | funds: vec![], 66 | }; 67 | Ok(Response::new().add_message(msg)) 68 | } 69 | ExecuteMsg::Slash { owner, amount } => { 70 | let msg = WasmMsg::Execute { 71 | contract_addr: LOCKUP.load(deps.storage)?.into_string(), 72 | msg: to_binary(&crate::msg::ExecuteMsg::SlashClaim { owner, amount })?, 73 | funds: vec![], 74 | }; 75 | Ok(Response::new().add_message(msg)) 76 | } 77 | } 78 | } 79 | 80 | pub fn query(_deps: Deps, _env: Env, _msg: QueryMsg) -> StdResult { 81 | unimplemented!() 82 | } 83 | -------------------------------------------------------------------------------- /contracts/mesh-lockup/src/multitest/suite.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result as AnyResult; 2 | use derivative::Derivative; 3 | 4 | use cosmwasm_std::{coin, coins, Addr, Empty, StdResult, Uint128}; 5 | use cw_multi_test::{App, AppBuilder, AppResponse, Contract, ContractWrapper, Executor}; 6 | 7 | use super::mock_grantee::contract_mock; 8 | use crate::msg::{BalanceResponse, ExecuteMsg, InstantiateMsg, QueryMsg}; 9 | 10 | pub fn contract_lockup() -> Box> { 11 | let contract = ContractWrapper::new( 12 | crate::contract::execute, 13 | crate::contract::instantiate, 14 | crate::contract::query, 15 | ); 16 | Box::new(contract) 17 | } 18 | 19 | #[derive(Derivative)] 20 | #[derivative(Default = "new")] 21 | pub struct SuiteBuilder { 22 | funds: Vec<(Addr, u128)>, 23 | #[derivative(Default(value = "\"uosmo\".to_owned()"))] 24 | denom: String, 25 | } 26 | 27 | impl SuiteBuilder { 28 | /// Sets initial amount of distributable tokens on address 29 | pub fn with_funds(mut self, addr: impl Into, amount: u128) -> Self { 30 | self.funds.push((Addr::unchecked(addr), amount)); 31 | self 32 | } 33 | 34 | pub fn with_denom(mut self, denom: impl Into) -> Self { 35 | self.denom = denom.into(); 36 | self 37 | } 38 | 39 | #[track_caller] 40 | pub fn build(self) -> Suite { 41 | let denom = self.denom; 42 | let funds: Vec<_> = self 43 | .funds 44 | .iter() 45 | .map(|(addr, amount)| (addr, coin(*amount, &denom))) 46 | .collect(); 47 | 48 | let mut app = AppBuilder::new().build(|router, _, storage| { 49 | for (addr, fund) in funds.into_iter() { 50 | router.bank.init_balance(storage, addr, vec![fund]).unwrap(); 51 | } 52 | }); 53 | 54 | let owner = Addr::unchecked("foobar"); 55 | let contract_id = app.store_code(contract_lockup()); 56 | let lockup_contract = app 57 | .instantiate_contract( 58 | contract_id, 59 | owner.clone(), 60 | &InstantiateMsg { 61 | denom: denom.clone(), 62 | }, 63 | &[], 64 | "lockup demo", 65 | None, 66 | ) 67 | .unwrap(); 68 | 69 | let mock_contract_id = app.store_code(contract_mock()); 70 | let mock_contract = app 71 | .instantiate_contract( 72 | mock_contract_id, 73 | owner, 74 | &super::mock_grantee::InstantiateMsg { 75 | lockup: lockup_contract.to_string(), 76 | }, 77 | &[], 78 | "mock grantee", 79 | None, 80 | ) 81 | .unwrap(); 82 | 83 | Suite { 84 | app, 85 | lockup_contract, 86 | mock_contract, 87 | denom, 88 | } 89 | } 90 | } 91 | 92 | #[derive(Derivative)] 93 | #[derivative(Debug)] 94 | pub struct Suite { 95 | #[derivative(Debug = "ignore")] 96 | pub app: App, 97 | /// Lockup contract address 98 | pub lockup_contract: Addr, 99 | /// Mock receiver address 100 | pub mock_contract: Addr, 101 | /// Denom of tokens which might be distributed by this contract 102 | pub denom: String, 103 | } 104 | 105 | impl Suite { 106 | pub fn bond(&mut self, executor: &str, amount: u128) -> AnyResult { 107 | let funds = coins(amount, &self.denom); 108 | self.app.execute_contract( 109 | Addr::unchecked(executor), 110 | self.lockup_contract.clone(), 111 | &ExecuteMsg::Bond {}, 112 | &funds, 113 | ) 114 | } 115 | 116 | pub fn unbond(&mut self, executor: &str, amount: u128) -> AnyResult { 117 | self.app.execute_contract( 118 | Addr::unchecked(executor), 119 | self.lockup_contract.clone(), 120 | &ExecuteMsg::Unbond { 121 | amount: amount.into(), 122 | }, 123 | &[], 124 | ) 125 | } 126 | 127 | pub fn grant_claim( 128 | &mut self, 129 | executor: &str, 130 | amount: u128, 131 | validator: &str, 132 | ) -> AnyResult { 133 | self.app.execute_contract( 134 | Addr::unchecked(executor), 135 | self.lockup_contract.clone(), 136 | &ExecuteMsg::GrantClaim { 137 | leinholder: self.mock_contract.to_string(), 138 | amount: amount.into(), 139 | validator: validator.to_string(), 140 | }, 141 | &[], 142 | ) 143 | } 144 | 145 | pub fn release_claim(&mut self, executor: &str, amount: u128) -> AnyResult { 146 | self.app.execute_contract( 147 | Addr::unchecked(executor), 148 | self.mock_contract.clone(), 149 | &super::mock_grantee::ExecuteMsg::Release { 150 | owner: executor.to_string(), 151 | amount: amount.into(), 152 | }, 153 | &[], 154 | ) 155 | } 156 | 157 | pub fn slash_claim(&mut self, executor: &str, amount: u128) -> AnyResult { 158 | self.app.execute_contract( 159 | Addr::unchecked(executor), 160 | self.mock_contract.clone(), 161 | &super::mock_grantee::ExecuteMsg::Slash { 162 | owner: executor.to_string(), 163 | amount: amount.into(), 164 | }, 165 | &[], 166 | ) 167 | } 168 | 169 | pub fn lockup_balance(&self, account: impl Into) -> StdResult { 170 | self.app.wrap().query_wasm_smart( 171 | self.lockup_contract.clone(), 172 | &QueryMsg::Balance { 173 | account: account.into(), 174 | }, 175 | ) 176 | } 177 | 178 | pub fn balance(&self, account: impl Into) -> StdResult { 179 | Ok(self.app.wrap().query_balance(account, &self.denom)?.amount) 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /contracts/mesh-provider/.cargo/config: -------------------------------------------------------------------------------- 1 | [alias] 2 | wasm = "build --release --lib --target wasm32-unknown-unknown" 3 | unit-test = "test --lib" 4 | schema = "run --bin schema" 5 | -------------------------------------------------------------------------------- /contracts/mesh-provider/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mesh-provider" 3 | authors = ["Ethan Frey ", "Jake Hartnell "] 4 | 5 | edition = { workspace = true } 6 | version = { workspace = true } 7 | license = { workspace = true } 8 | repository = { workspace = true } 9 | homepage = { workspace = true } 10 | documentation = { workspace = true } 11 | exclude = { workspace = true } 12 | 13 | [lib] 14 | crate-type = ["cdylib", "rlib"] 15 | 16 | [features] 17 | # for more explicit tests, cargo test --features=backtraces 18 | backtraces = ["cosmwasm-std/backtraces"] 19 | # use library feature to disable all instantiate/execute/query exports 20 | library = [] 21 | 22 | [package.metadata.scripts] 23 | optimize = """docker run --rm -v "$(pwd)":/code \ 24 | --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ 25 | --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ 26 | cosmwasm/rust-optimizer:0.12.9 27 | """ 28 | 29 | [dependencies] 30 | mesh-apis = { workspace = true } 31 | mesh-ibc = { workspace = true } 32 | cosmwasm-schema = { workspace = true } 33 | cosmwasm-std = { workspace = true } 34 | cosmwasm-storage = { workspace = true } 35 | cw-controllers = { workspace = true } 36 | cw-utils = { workspace = true } 37 | cw-storage-plus = { workspace = true } 38 | cw2 = { workspace = true } 39 | schemars = { workspace = true } 40 | serde = { workspace = true } 41 | thiserror = { workspace = true } 42 | 43 | [dev-dependencies] 44 | cw-multi-test = { workspace = true } 45 | mesh-testing = { workspace = true } 46 | anyhow = { workspace = true } 47 | 48 | [[bin]] 49 | name = "schema" 50 | doc = false 51 | -------------------------------------------------------------------------------- /contracts/mesh-provider/src/bin/schema.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::write_api; 2 | 3 | use mesh_provider::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; 4 | 5 | fn main() { 6 | write_api! { 7 | instantiate: InstantiateMsg, 8 | execute: ExecuteMsg, 9 | query: QueryMsg, 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /contracts/mesh-provider/src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | use cosmwasm_std::{ 4 | CheckedFromRatioError, DecimalRangeExceeded, DivideByZeroError, OverflowError, StdError, 5 | }; 6 | use cw_utils::ParseReplyError; 7 | 8 | use mesh_ibc::MeshSecurityError; 9 | 10 | #[derive(Error, Debug, PartialEq)] 11 | pub enum ContractError { 12 | #[error("{0}")] 13 | Std(#[from] StdError), 14 | 15 | #[error("{0}")] 16 | OverflowError(#[from] OverflowError), 17 | 18 | #[error("{0}")] 19 | DivideByZeroError(#[from] DivideByZeroError), 20 | 21 | #[error("{0}")] 22 | CheckedFromRatioError(#[from] CheckedFromRatioError), 23 | 24 | #[error("{0}")] 25 | DecimalRangeExceeded(#[from] DecimalRangeExceeded), 26 | 27 | #[error("{0}")] 28 | Parse(#[from] ParseReplyError), 29 | 30 | #[error("{0}")] 31 | MeshSecurity(#[from] MeshSecurityError), 32 | 33 | #[error("Unauthorized")] 34 | Unauthorized, 35 | 36 | #[error("Contract already has a bound channel: {0}")] 37 | ChannelExists(String), 38 | 39 | #[error("Contract already has a bound port: {0}")] 40 | PortExists(String), 41 | 42 | #[error("Unauthorized counterparty chain, awaiting connection '{0}'")] 43 | WrongConnection(String), 44 | 45 | #[error("Refuse to respond on unregistered channel '{0}'")] 46 | UnknownChannel(String), 47 | 48 | #[error("Invalid reply id: {0}")] 49 | InvalidReplyId(u64), 50 | 51 | #[error("Insufficient stake to withdraw this")] 52 | InsufficientStake, 53 | 54 | #[error("Cannot send zero tokens to any methods")] 55 | ZeroAmount, 56 | 57 | #[error("No tokens are ready to be unbonded")] 58 | NothingToClaim, 59 | 60 | #[error("No rewards to be claimed")] 61 | NoRewardsToClaim, 62 | 63 | #[error("Balance is too low: {rewards} > {balance}")] 64 | WrongBalance { balance: String, rewards: String }, 65 | 66 | #[error("Validator was never registered: {0}")] 67 | UnknownValidator(String), 68 | 69 | #[error("No staked tokens for this validator: {0}")] 70 | NoStakedTokens(String), 71 | 72 | #[error("Validator was removed from valset: {0}")] 73 | RemovedValidator(String), 74 | 75 | #[error("Something went wrong in the rewards calculation of the validator")] 76 | ValidatorRewardsCalculationWrong {}, 77 | 78 | #[error("Rewards amount is 0")] 79 | ZeroRewardsToSend {}, 80 | 81 | #[error("Unable to communicate for message: {0} on channel: {1}")] 82 | NoResponse(String, String), 83 | 84 | #[error("Custom Error val: {val:?}")] 85 | CustomError { val: String }, 86 | // Add any other custom errors you like here. 87 | // Look at https://docs.rs/thiserror/1.0.21/thiserror/ for details. 88 | } 89 | -------------------------------------------------------------------------------- /contracts/mesh-provider/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod contract; 2 | mod error; 3 | pub mod ibc; 4 | pub mod msg; 5 | pub mod state; 6 | 7 | pub use crate::error::ContractError; 8 | 9 | #[cfg(test)] 10 | mod testing; 11 | -------------------------------------------------------------------------------- /contracts/mesh-provider/src/msg.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | 3 | use crate::state::ValStatus; 4 | use cosmwasm_schema::{cw_serde, QueryResponses}; 5 | use cosmwasm_std::{to_binary, Binary, Decimal, StdResult, Uint128}; 6 | 7 | #[cw_serde] 8 | pub struct InstantiateMsg { 9 | pub consumer: ConsumerInfo, 10 | /// data to instantiate the slasher 11 | pub slasher: SlasherInfo, 12 | /// Address of Lockup contract from which we accept ReceiveClaim 13 | pub lockup: String, 14 | /// Unbonding period of the remote chain in seconds 15 | pub unbonding_period: u64, 16 | /// IBC denom string - "port_id/channel_id/denom" 17 | pub rewards_ibc_denom: String, 18 | /// Packet time for ibc calls 19 | pub packet_lifetime: Option, 20 | } 21 | 22 | #[cw_serde] 23 | pub struct ConsumerInfo { 24 | /// We can add port later if we have it, for now, just assert the chain we talk with 25 | pub connection_id: String, 26 | } 27 | 28 | #[cw_serde] 29 | pub struct SlasherInfo { 30 | pub code_id: u64, 31 | pub msg: Binary, 32 | } 33 | 34 | impl SlasherInfo { 35 | pub fn new(code_id: u64, msg: &T) -> StdResult { 36 | Ok(SlasherInfo { 37 | code_id, 38 | msg: to_binary(msg)?, 39 | }) 40 | } 41 | } 42 | 43 | #[cw_serde] 44 | pub enum ExecuteMsg { 45 | Slash { 46 | /// which validator to slash 47 | validator: String, 48 | /// what percentage we should slash all stakers 49 | percentage: Decimal, 50 | /// do we forcibly unbond this validator on the provider side, 51 | /// regardless of the behavior of the consumer? 52 | force_unbond: bool, 53 | }, 54 | /// This gives the receiver access to slash part up to this much claim 55 | ReceiveClaim { 56 | owner: String, 57 | amount: Uint128, 58 | validator: String, 59 | }, 60 | /// Triggers the unbonding period for your staked tokens 61 | Unstake { 62 | amount: Uint128, 63 | validator: String, 64 | }, 65 | /// Called after unbonding_period has passed from Unstake. Releases claim on lockup contract 66 | Unbond {/* ??? */}, 67 | ClaimRewards { 68 | validator: String, 69 | }, 70 | } 71 | 72 | #[cw_serde] 73 | #[derive(QueryResponses)] 74 | pub enum QueryMsg { 75 | #[returns(ConfigResponse)] 76 | Config {}, 77 | /// how much this account has staked where 78 | #[returns(AccountResponse)] 79 | Account { address: String }, 80 | /// Details of one validator 81 | #[returns(ValidatorResponse)] 82 | Validator { address: String }, 83 | /// Details of one validator 84 | #[returns(ListValidatorsResponse)] 85 | ListValidators { 86 | start_after: Option, 87 | limit: Option, 88 | }, 89 | } 90 | 91 | #[cw_serde] 92 | pub struct ConfigResponse { 93 | pub consumer: ConsumerInfo, 94 | pub slasher: Option, 95 | } 96 | 97 | #[cw_serde] 98 | pub struct AccountResponse { 99 | pub staked: Vec, 100 | } 101 | 102 | #[cw_serde] 103 | pub struct StakeInfo { 104 | pub validator: String, 105 | pub tokens: Uint128, 106 | pub slashed: Uint128, 107 | } 108 | 109 | #[cw_serde] 110 | pub struct ValidatorResponse { 111 | pub address: String, 112 | pub tokens: Uint128, 113 | pub status: ValStatus, 114 | pub multiplier: Decimal, 115 | } 116 | 117 | #[cw_serde] 118 | pub struct ListValidatorsResponse { 119 | pub validators: Vec, 120 | } 121 | -------------------------------------------------------------------------------- /contracts/mesh-provider/src/testing/mod.rs: -------------------------------------------------------------------------------- 1 | //mod integration_tests; 2 | mod utils; 3 | 4 | mod test_contract; 5 | mod test_ibc; 6 | mod test_instantiate; 7 | mod test_queries; 8 | -------------------------------------------------------------------------------- /contracts/mesh-provider/src/testing/test_instantiate.rs: -------------------------------------------------------------------------------- 1 | use mesh_testing::{ 2 | constants::{CONNECTION_ID, CREATOR_ADDR}, 3 | msgs::SlasherConfigResponse, 4 | }; 5 | 6 | use crate::{ 7 | msg::ConsumerInfo, 8 | testing::utils::query::{query_provider_config, query_slasher_config}, 9 | }; 10 | 11 | use super::utils::setup::setup_with_contract; 12 | 13 | #[test] 14 | fn test_instantiate() { 15 | let (app, mesh_provider_addr) = setup_with_contract(); 16 | 17 | let provider_config = query_provider_config(&app, mesh_provider_addr.as_str()).unwrap(); 18 | let mesh_slasher_addr = provider_config.slasher.clone().unwrap(); 19 | 20 | assert!(provider_config.slasher.is_some()); 21 | assert_eq!( 22 | provider_config.consumer, 23 | ConsumerInfo { 24 | connection_id: CONNECTION_ID.to_string() 25 | } 26 | ); 27 | 28 | let slasher_config = query_slasher_config(&app, mesh_slasher_addr.as_str()).unwrap(); 29 | 30 | assert_eq!( 31 | slasher_config, 32 | SlasherConfigResponse { 33 | owner: CREATOR_ADDR.to_string(), 34 | slashee: mesh_provider_addr.to_string() 35 | } 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /contracts/mesh-provider/src/testing/test_queries.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::Uint128; 2 | use mesh_testing::constants::{CHANNEL_ID, DELEGATOR_ADDR, VALIDATOR}; 3 | 4 | use super::utils::{ 5 | ibc_helpers::{add_stake_unit, query_account_unit, update_validator_unit}, 6 | setup_unit::setup_unit_with_channel, 7 | }; 8 | 9 | #[test] 10 | fn test_query_account() { 11 | let (mut deps, _) = setup_unit_with_channel(None, CHANNEL_ID); 12 | 13 | update_validator_unit(deps.as_mut(), vec![VALIDATOR.to_string()], vec![]).unwrap(); 14 | 15 | add_stake_unit(deps.as_mut(), DELEGATOR_ADDR, VALIDATOR, Uint128::new(1000)).unwrap(); 16 | 17 | let account = query_account_unit(deps.as_ref(), DELEGATOR_ADDR).unwrap(); 18 | 19 | assert_eq!(account.staked[0].tokens, Uint128::new(1000)) 20 | } 21 | -------------------------------------------------------------------------------- /contracts/mesh-provider/src/testing/utils/execute.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::Addr; 2 | use std::str::FromStr; 3 | 4 | use anyhow::Result as AnyResult; 5 | use cosmwasm_std::Decimal; 6 | use cw_multi_test::{App, AppResponse, Executor}; 7 | use mesh_testing::{addr, constants::DELEGATOR_ADDR}; 8 | 9 | use crate::msg::ExecuteMsg; 10 | 11 | pub fn execute_slash( 12 | app: &mut App, 13 | slasher_addr: &str, 14 | contract_addr: &str, 15 | validator: &str, 16 | slash_amount: &str, 17 | force_unbond: bool, 18 | ) -> AnyResult { 19 | app.execute_contract( 20 | addr!(slasher_addr), 21 | addr!(contract_addr), 22 | &ExecuteMsg::Slash { 23 | validator: validator.to_string(), 24 | percentage: Decimal::from_str(slash_amount).unwrap(), 25 | force_unbond, 26 | }, 27 | &[], 28 | ) 29 | } 30 | 31 | pub fn execute_claim_rewards( 32 | app: &mut App, 33 | contract_addr: &str, 34 | validator: &str, 35 | ) -> AnyResult { 36 | app.execute_contract( 37 | addr!(DELEGATOR_ADDR), 38 | addr!(contract_addr), 39 | &ExecuteMsg::ClaimRewards { 40 | validator: validator.to_string(), 41 | }, 42 | &[], 43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /contracts/mesh-provider/src/testing/utils/helpers.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::{coins, Addr, Decimal, Uint128}; 2 | use cw_multi_test::{App, BankSudo, SudoMsg}; 3 | 4 | use crate::state::{ 5 | DelegatorRewards, Stake, ValStatus, Validator, ValidatorRewards, STAKED, VALIDATORS, 6 | }; 7 | 8 | use mesh_testing::{ 9 | constants::{DELEGATOR_ADDR, REWARDS_IBC_DENOM, VALIDATOR}, 10 | macros::addr, 11 | multitest_helpers::update_storage, 12 | }; 13 | 14 | /// Function for multi-test to add validator to storage directly and by-pass 15 | /// the IBC call that is needed to do so. 16 | pub fn add_validator(app: &mut App, addr: Addr) { 17 | update_storage(app, addr.as_bytes(), &mut |storage| { 18 | VALIDATORS 19 | .save(storage, VALIDATOR, &Validator::new()) 20 | .unwrap(); 21 | }); 22 | } 23 | 24 | pub fn add_stake(app: &mut App, addr: Addr, rewards_amount: Decimal) { 25 | update_storage(app, addr.as_bytes(), &mut |storage| { 26 | STAKED 27 | .save( 28 | storage, 29 | (&addr!(DELEGATOR_ADDR), VALIDATOR), 30 | &Stake { 31 | locked: Uint128::new(1000), 32 | shares: Uint128::new(1000), 33 | rewards: DelegatorRewards { 34 | pending: rewards_amount, 35 | paid_rewards_per_token: Decimal::zero(), 36 | }, 37 | }, 38 | ) 39 | .unwrap(); 40 | }); 41 | } 42 | 43 | pub fn add_rewards(app: &mut App, addr: Addr) { 44 | // Fund the contract to have enough rewards to send 45 | app.sudo(SudoMsg::Bank(BankSudo::Mint { 46 | to_address: addr.to_string(), 47 | amount: coins(100000, REWARDS_IBC_DENOM), 48 | })) 49 | .unwrap(); 50 | 51 | update_storage(app, addr.as_bytes(), &mut |storage| { 52 | VALIDATORS 53 | .save( 54 | storage, 55 | VALIDATOR, 56 | &Validator { 57 | stake: Uint128::new(1000), 58 | multiplier: Decimal::one(), 59 | status: ValStatus::Active, 60 | rewards: ValidatorRewards { 61 | rewards_per_token: Decimal::from_atomics(1_u128, 0).unwrap(), 62 | }, 63 | }, 64 | ) 65 | .unwrap(); 66 | 67 | STAKED 68 | .save( 69 | storage, 70 | (&addr!(DELEGATOR_ADDR), VALIDATOR), 71 | &Stake { 72 | locked: Uint128::new(1000), 73 | shares: Uint128::new(1000), 74 | rewards: DelegatorRewards::default(), 75 | }, 76 | ) 77 | .unwrap(); 78 | }); 79 | } 80 | -------------------------------------------------------------------------------- /contracts/mesh-provider/src/testing/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod execute; 2 | pub mod helpers; 3 | pub mod ibc_helpers; 4 | pub mod query; 5 | 6 | pub mod setup; 7 | pub mod setup_unit; 8 | -------------------------------------------------------------------------------- /contracts/mesh-provider/src/testing/utils/query.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::StdResult; 2 | use cw_multi_test::App; 3 | use mesh_testing::msgs::{SlasherConfigResponse, SlasherQueryMsg}; 4 | 5 | use crate::msg::{ConfigResponse, ListValidatorsResponse, QueryMsg}; 6 | 7 | pub fn query_provider_config(app: &App, contract_addr: &str) -> StdResult { 8 | app.wrap() 9 | .query_wasm_smart(contract_addr, &QueryMsg::Config {}) 10 | } 11 | 12 | pub fn query_slasher_config(app: &App, contract_addr: &str) -> StdResult { 13 | app.wrap() 14 | .query_wasm_smart(contract_addr, &SlasherQueryMsg::Config {}) 15 | } 16 | 17 | pub fn query_validators( 18 | app: &App, 19 | contract_addr: &str, 20 | start: Option, 21 | limit: Option, 22 | ) -> StdResult { 23 | app.wrap().query_wasm_smart( 24 | contract_addr, 25 | &QueryMsg::ListValidators { 26 | start_after: start, 27 | limit, 28 | }, 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /contracts/mesh-provider/src/testing/utils/setup.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::{Addr, Coin, Uint128}; 2 | use cw_multi_test::{App, AppBuilder}; 3 | use mesh_testing::{ 4 | addr, 5 | constants::{CREATOR_ADDR, NATIVE_DENOM}, 6 | instantiates::instantiate_mesh_provider, 7 | }; 8 | 9 | pub fn setup_app() -> App { 10 | AppBuilder::new().build(|router, _, storage| { 11 | router 12 | .bank 13 | .init_balance( 14 | storage, 15 | &addr!(CREATOR_ADDR), 16 | vec![Coin { 17 | denom: NATIVE_DENOM.to_string(), 18 | amount: Uint128::new(1), 19 | }], 20 | ) 21 | .unwrap(); 22 | }) 23 | } 24 | 25 | pub fn setup_with_contract() -> (App, Addr) { 26 | let mut app = setup_app(); 27 | 28 | let mesh_provider_addr = instantiate_mesh_provider(&mut app, None); 29 | 30 | (app, mesh_provider_addr) 31 | } 32 | -------------------------------------------------------------------------------- /contracts/mesh-provider/src/testing/utils/setup_unit.rs: -------------------------------------------------------------------------------- 1 | // File to setup unit testing for IBC stuff. 2 | 3 | use cosmwasm_std::{ 4 | testing::{mock_dependencies, MockApi, MockQuerier}, 5 | Addr, Empty, MemoryStorage, OwnedDeps, 6 | }; 7 | 8 | use crate::msg::InstantiateMsg; 9 | 10 | use super::ibc_helpers::{ibc_open_channel, instantiate_provider}; 11 | 12 | type OwnedDepsType = OwnedDeps, Empty>; 13 | 14 | pub fn setup_unit(init_msg: Option) -> (OwnedDepsType, Addr) { 15 | let mut deps = mock_dependencies(); 16 | let provider_addr = instantiate_provider(deps.as_mut(), init_msg); 17 | 18 | (deps, provider_addr) 19 | } 20 | 21 | pub fn setup_unit_with_channel( 22 | init_msg: Option, 23 | channel: &str, 24 | ) -> (OwnedDepsType, Addr) { 25 | let (mut deps, consumer_addr) = setup_unit(init_msg); 26 | 27 | ibc_open_channel(deps.as_mut(), channel).unwrap(); 28 | 29 | (deps, consumer_addr) 30 | } 31 | -------------------------------------------------------------------------------- /contracts/mesh-slasher/.cargo/config: -------------------------------------------------------------------------------- 1 | [alias] 2 | wasm = "build --release --lib --target wasm32-unknown-unknown" 3 | unit-test = "test --lib" 4 | schema = "run --bin schema" 5 | -------------------------------------------------------------------------------- /contracts/mesh-slasher/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mesh-slasher" 3 | authors = ["Ethan Frey ", "Jake Hartnell "] 4 | 5 | edition = { workspace = true } 6 | version = { workspace = true } 7 | license = { workspace = true } 8 | repository = { workspace = true } 9 | homepage = { workspace = true } 10 | documentation = { workspace = true } 11 | exclude = { workspace = true } 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 | [features] 19 | # for more explicit tests, cargo test --features=backtraces 20 | backtraces = ["cosmwasm-std/backtraces"] 21 | # use library feature to disable all instantiate/execute/query exports 22 | library = [] 23 | 24 | [package.metadata.scripts] 25 | optimize = """docker run --rm -v "$(pwd)":/code \ 26 | --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ 27 | --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ 28 | cosmwasm/rust-optimizer:0.12.9 29 | """ 30 | 31 | [dependencies] 32 | cosmwasm-schema = { workspace = true } 33 | cosmwasm-std = { workspace = true } 34 | cosmwasm-storage = { workspace = true } 35 | cw-storage-plus = { workspace = true } 36 | cw2 = { workspace = true } 37 | schemars = { workspace = true } 38 | serde = { workspace = true } 39 | thiserror = { workspace = true } 40 | 41 | [dev-dependencies] 42 | cw-multi-test = { workspace = true } 43 | 44 | [[bin]] 45 | name = "schema" 46 | doc = false 47 | -------------------------------------------------------------------------------- /contracts/mesh-slasher/src/bin/schema.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::write_api; 2 | 3 | use mesh_slasher::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; 4 | 5 | fn main() { 6 | write_api! { 7 | instantiate: InstantiateMsg, 8 | execute: ExecuteMsg, 9 | query: QueryMsg, 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /contracts/mesh-slasher/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::{ConfigResponse, ExecuteMsg, InstantiateMsg, QueryMsg}; 8 | use crate::state::{Config, CONFIG}; 9 | 10 | // version info for migration info 11 | const CONTRACT_NAME: &str = "crates.io:mesh-slasher"; 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 | set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; 22 | 23 | let cfg = Config { 24 | owner: deps.api.addr_validate(&msg.owner)?, 25 | slashee: info.sender, 26 | }; 27 | CONFIG.save(deps.storage, &cfg)?; 28 | 29 | Ok(Response::new()) 30 | } 31 | 32 | #[cfg_attr(not(feature = "library"), entry_point)] 33 | pub fn execute( 34 | _deps: DepsMut, 35 | _env: Env, 36 | _info: MessageInfo, 37 | _msg: ExecuteMsg, 38 | ) -> Result { 39 | unimplemented!(); 40 | } 41 | 42 | #[cfg_attr(not(feature = "library"), entry_point)] 43 | pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { 44 | match msg { 45 | QueryMsg::Config {} => to_binary(&query_config(deps)?), 46 | } 47 | } 48 | 49 | pub fn query_config(deps: Deps) -> StdResult { 50 | let cfg = CONFIG.load(deps.storage)?; 51 | Ok(ConfigResponse { 52 | owner: cfg.owner.into_string(), 53 | slashee: cfg.slashee.into_string(), 54 | }) 55 | } 56 | 57 | #[cfg(test)] 58 | mod tests { 59 | use super::*; 60 | use cosmwasm_std::coins; 61 | use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; 62 | 63 | #[test] 64 | fn proper_initialization() { 65 | let mut deps = mock_dependencies(); 66 | 67 | let msg = InstantiateMsg { 68 | owner: "foo".to_string(), 69 | }; 70 | let info = mock_info("creator", &coins(1000, "earth")); 71 | 72 | // we can just call .unwrap() to assert this was a success 73 | let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); 74 | assert_eq!(0, res.messages.len()); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /contracts/mesh-slasher/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/mesh-slasher/src/helpers.rs: -------------------------------------------------------------------------------- 1 | use schemars::JsonSchema; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use cosmwasm_std::Addr; 5 | // use cosmwasm_std::{to_binary, Addr, CosmosMsg, StdResult, WasmMsg}; 6 | // 7 | // use crate::msg::ExecuteMsg; 8 | 9 | /// CwTemplateContract is a wrapper around Addr that provides a lot of helpers 10 | /// for working with this. 11 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] 12 | pub struct MeshSlasherContract(pub Addr); 13 | 14 | impl MeshSlasherContract { 15 | pub fn addr(&self) -> Addr { 16 | self.0.clone() 17 | } 18 | 19 | // pub fn call>(&self, msg: T) -> StdResult { 20 | // let msg = to_binary(&msg.into())?; 21 | // Ok(WasmMsg::Execute { 22 | // contract_addr: self.addr().into(), 23 | // msg, 24 | // funds: vec![], 25 | // } 26 | // .into()) 27 | // } 28 | } 29 | -------------------------------------------------------------------------------- /contracts/mesh-slasher/src/integration_tests.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use crate::helpers::MeshSlasherContract; 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, MeshSlasherContract) { 38 | let mut app = mock_app(); 39 | let cw_template_id = app.store_code(contract_template()); 40 | 41 | let msg = InstantiateMsg {}; 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 = MeshSlasherContract(cw_template_contract_addr); 54 | 55 | (app, cw_template_contract) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /contracts/mesh-slasher/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod contract; 2 | mod error; 3 | pub mod helpers; 4 | // pub mod integration_tests; 5 | pub mod msg; 6 | mod state; 7 | 8 | pub use crate::error::ContractError; 9 | -------------------------------------------------------------------------------- /contracts/mesh-slasher/src/msg.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::{cw_serde, QueryResponses}; 2 | use cosmwasm_std::Decimal; 3 | 4 | #[cw_serde] 5 | pub struct InstantiateMsg { 6 | // owner is allowed to submit evidence 7 | pub owner: String, 8 | } 9 | 10 | /// This is a mock contract 11 | #[cw_serde] 12 | pub enum ExecuteMsg { 13 | /// Owner can slash validator by X% 14 | SubmitEvidence { validator: String, amount: Decimal }, 15 | } 16 | 17 | #[cw_serde] 18 | #[derive(QueryResponses)] 19 | pub enum QueryMsg { 20 | #[returns(ConfigResponse)] 21 | Config {}, 22 | } 23 | 24 | #[cw_serde] 25 | pub struct ConfigResponse { 26 | /// The address that can trigger a slash 27 | pub owner: String, 28 | /// The contract that will be slashed 29 | pub slashee: String, 30 | } 31 | -------------------------------------------------------------------------------- /contracts/mesh-slasher/src/state.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::cw_serde; 2 | use cosmwasm_std::Addr; 3 | 4 | use cw_storage_plus::Item; 5 | 6 | #[cw_serde] 7 | pub struct Config { 8 | pub owner: Addr, 9 | pub slashee: Addr, 10 | } 11 | 12 | pub const CONFIG: Item = Item::new("config"); 13 | -------------------------------------------------------------------------------- /contracts/meta-staking/.cargo/config: -------------------------------------------------------------------------------- 1 | [alias] 2 | wasm = "build --release --lib --target wasm32-unknown-unknown" 3 | unit-test = "test --lib" 4 | schema = "run --bin schema" 5 | -------------------------------------------------------------------------------- /contracts/meta-staking/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "meta-staking" 3 | authors = ["Ethan Frey ", "Jake Hartnell "] 4 | 5 | edition = { workspace = true } 6 | version = { workspace = true } 7 | license = { workspace = true } 8 | repository = { workspace = true } 9 | homepage = { workspace = true } 10 | documentation = { workspace = true } 11 | exclude = { workspace = true } 12 | 13 | [lib] 14 | crate-type = ["cdylib", "rlib"] 15 | 16 | [features] 17 | # for more explicit tests, cargo test --features=backtraces 18 | backtraces = ["cosmwasm-std/backtraces"] 19 | # use library feature to disable all instantiate/execute/query exports 20 | library = [] 21 | 22 | [package.metadata.scripts] 23 | optimize = """docker run --rm -v "$(pwd)":/code \ 24 | --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ 25 | --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ 26 | cosmwasm/rust-optimizer:0.12.9 27 | """ 28 | 29 | [dependencies] 30 | mesh-apis = { workspace = true } 31 | cosmwasm-schema = { workspace = true } 32 | cosmwasm-std = { workspace = true } 33 | cosmwasm-storage = { workspace = true } 34 | cw-storage-plus = { workspace = true } 35 | cw-utils = { workspace = true } 36 | cw2 = { workspace = true } 37 | schemars = { workspace = true } 38 | serde = { workspace = true } 39 | thiserror = { workspace = true } 40 | 41 | [dev-dependencies] 42 | cw-multi-test = { workspace = true } 43 | mesh-testing = { workspace = true } 44 | anyhow = { workspace = true } 45 | 46 | [[bin]] 47 | name = "schema" 48 | doc = false 49 | -------------------------------------------------------------------------------- /contracts/meta-staking/src/bin/schema.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::write_api; 2 | 3 | use mesh_apis::StakingExecuteMsg as ExecuteMsg; 4 | use meta_staking::msg::{InstantiateMsg, QueryMsg}; 5 | 6 | fn main() { 7 | write_api! { 8 | instantiate: InstantiateMsg, 9 | execute: ExecuteMsg, 10 | query: QueryMsg, 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /contracts/meta-staking/src/error.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::{ 2 | CheckedFromRatioError, DecimalRangeExceeded, DivideByZeroError, OverflowError, StdError, 3 | }; 4 | use cw_utils::ParseReplyError; 5 | use thiserror::Error; 6 | 7 | #[derive(Error, Debug, PartialEq)] 8 | pub enum ContractError { 9 | #[error("{0}")] 10 | Std(#[from] StdError), 11 | 12 | #[error("{0}")] 13 | OverflowErr(#[from] OverflowError), 14 | 15 | #[error(transparent)] 16 | ParseReplyError(#[from] ParseReplyError), 17 | 18 | #[error(transparent)] 19 | DivideByZeroError(#[from] DivideByZeroError), 20 | 21 | #[error(transparent)] 22 | DecimalRangeExceeded(#[from] DecimalRangeExceeded), 23 | 24 | #[error(transparent)] 25 | CheckedFromRatioError(#[from] CheckedFromRatioError), 26 | 27 | #[error("Unauthorized")] 28 | Unauthorized {}, 29 | 30 | #[error("Incorrect coin denom")] 31 | IncorrectDenom {}, 32 | 33 | #[error("Cannot undelegate more than you previously delegated")] 34 | InsufficientDelegation {}, 35 | 36 | #[error("Contract has run out of funds to delegate for consumer chain")] 37 | NoFundsToDelegate {}, 38 | 39 | #[error("Cannot undelegate or claim rewards from a validator that does not have delegations")] 40 | NoDelegationsForValidator {}, 41 | 42 | #[error("Contract does not have enough funds for consumer")] 43 | NotEnoughFunds {}, 44 | 45 | #[error("Consumer already exists")] 46 | ConsumerAlreadyExists {}, 47 | 48 | #[error("Consumer does not exists")] 49 | NoConsumer {}, 50 | 51 | #[error("Rewards amount is 0")] 52 | ZeroRewardsToSend {}, 53 | 54 | #[error("Something went wrong in the rewards calculation of the validator")] 55 | ValidatorRewardsCalculationWrong {}, 56 | 57 | #[error("We are missing the validator rewards info")] 58 | ValidatorRewardsIsMissing {}, 59 | 60 | #[error("An unknown reply ID was received.")] 61 | UnknownReplyID {}, 62 | } 63 | -------------------------------------------------------------------------------- /contracts/meta-staking/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 | 8 | #[cfg(test)] 9 | mod testing; 10 | -------------------------------------------------------------------------------- /contracts/meta-staking/src/msg.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::{cw_serde, QueryResponses}; 2 | use cosmwasm_std::{Addr, Coin, Uint128}; 3 | 4 | use crate::state::ConsumerInfo; 5 | 6 | // mesh-consumer msg to receive rewards 7 | #[cw_serde] 8 | pub struct MeshConsumerRecieveRewardsMsg { 9 | pub rewards_by_validator: Vec<(String, Vec)>, 10 | } 11 | 12 | #[cw_serde] 13 | pub struct InstantiateMsg {} 14 | 15 | #[cw_serde] 16 | #[derive(QueryResponses)] 17 | pub enum QueryMsg { 18 | /// AllDelegations will return all delegations by the consumer 19 | #[returns(Vec)] 20 | AllDelegations { consumer: String }, 21 | /// Returns an individual consumer 22 | #[returns(ConsumerInfo)] 23 | Consumer { address: String }, 24 | /// Returns list of consumers 25 | #[returns(Vec)] 26 | Consumers { 27 | start: Option, 28 | limit: Option, 29 | }, 30 | /// Delegation will return more detailed info on a particular 31 | /// delegation, defined by delegator/validator pair 32 | #[returns(Uint128)] 33 | Delegation { consumer: String, validator: String }, 34 | /// Returns all validators the consumer delegates to. 35 | /// 36 | /// The query response type is `AllValidatorsResponse`. 37 | #[returns(Vec)] 38 | AllValidators { 39 | consumer: String, 40 | start: Option, 41 | limit: Option, 42 | }, 43 | } 44 | 45 | #[cw_serde] 46 | pub struct Delegation { 47 | pub validator: String, 48 | pub delegation: Uint128, 49 | } 50 | -------------------------------------------------------------------------------- /contracts/meta-staking/src/testing/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod utils; 2 | 3 | mod test_consumer; 4 | mod test_delegations; 5 | mod test_queries; 6 | mod test_rewards; 7 | -------------------------------------------------------------------------------- /contracts/meta-staking/src/testing/test_consumer.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::{Decimal, Uint128}; 2 | 3 | use crate::{ 4 | testing::utils::{ 5 | executes::{add_consumer, remove_consumer}, 6 | queries::query_consumer, 7 | setup::{setup_with_consumer, setup_with_contracts}, 8 | }, 9 | ContractError, 10 | }; 11 | 12 | use mesh_testing::{constants::CREATOR_ADDR, macros::assert_error}; 13 | 14 | #[test] 15 | fn add_and_remove_consumer() { 16 | let (mut app, meta_staking_addr, mesh_consumer_addr) = setup_with_consumer(); 17 | 18 | let consumer = query_consumer( 19 | &app, 20 | meta_staking_addr.as_str(), 21 | mesh_consumer_addr.as_str(), 22 | ) 23 | .unwrap(); 24 | 25 | assert_eq!(consumer.available_funds, Uint128::new(10000_u128)); 26 | assert_eq!(consumer.rewards.pending, Decimal::zero()); 27 | 28 | remove_consumer( 29 | &mut app, 30 | meta_staking_addr.as_str(), 31 | CREATOR_ADDR, 32 | mesh_consumer_addr.as_str(), 33 | ) 34 | .unwrap(); 35 | 36 | // Should return error because we didn't find it 37 | query_consumer( 38 | &app, 39 | meta_staking_addr.as_str(), 40 | mesh_consumer_addr.as_str(), 41 | ) 42 | .unwrap_err(); 43 | } 44 | 45 | #[test] 46 | fn consumer_already_exists() { 47 | let (mut app, meta_staking_addr, mesh_consumer_addr) = setup_with_consumer(); 48 | 49 | // Should failed because we already have a consumer 50 | let err = add_consumer( 51 | &mut app, 52 | meta_staking_addr.as_str(), 53 | CREATOR_ADDR, 54 | mesh_consumer_addr.as_str(), 55 | 10000, 56 | ); 57 | 58 | assert_error!(err, ContractError::ConsumerAlreadyExists {}) 59 | } 60 | 61 | #[test] 62 | fn consumer_not_enough_funds() { 63 | let (mut app, meta_staking_addr, mesh_consumer_addr) = setup_with_contracts(); 64 | 65 | let err = add_consumer( 66 | &mut app, 67 | meta_staking_addr.as_str(), 68 | CREATOR_ADDR, 69 | mesh_consumer_addr.as_str(), 70 | 999999999, 71 | ); 72 | 73 | assert_error!(err, ContractError::NotEnoughFunds {}) 74 | } 75 | 76 | #[test] 77 | fn consumer_remove_not_exists() { 78 | let (mut app, meta_staking_addr, mesh_consumer_addr) = setup_with_contracts(); 79 | 80 | let err = remove_consumer( 81 | &mut app, 82 | meta_staking_addr.as_str(), 83 | CREATOR_ADDR, 84 | mesh_consumer_addr.as_str(), 85 | ); 86 | 87 | assert_error!(err, ContractError::NoConsumer {}) 88 | } 89 | -------------------------------------------------------------------------------- /contracts/meta-staking/src/testing/test_delegations.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::Uint128; 2 | 3 | use crate::ContractError; 4 | 5 | use super::utils::{ 6 | executes::{delegate, undelegate}, 7 | queries::{query_delegation, query_module_delegation}, 8 | setup::{setup_with_consumer, setup_with_contracts}, 9 | }; 10 | 11 | use mesh_testing::{constants::VALIDATOR, macros::assert_error}; 12 | 13 | #[test] 14 | fn add_remove_delegations() { 15 | let (mut app, meta_staking_addr, mesh_consumer_addr) = setup_with_consumer(); 16 | 17 | let delegation_amount = Uint128::new(10000_u128); 18 | 19 | // deleate from consumer 20 | delegate( 21 | &mut app, 22 | meta_staking_addr.as_str(), 23 | mesh_consumer_addr.as_str(), 24 | VALIDATOR, 25 | delegation_amount, 26 | ) 27 | .unwrap(); 28 | 29 | let contract_delegation = query_delegation( 30 | &app, 31 | meta_staking_addr.as_str(), 32 | mesh_consumer_addr.as_str(), 33 | VALIDATOR, 34 | ) 35 | .unwrap(); 36 | 37 | let module_delegation = 38 | query_module_delegation(&app, meta_staking_addr.as_str(), VALIDATOR).unwrap(); 39 | 40 | assert_eq!(delegation_amount, contract_delegation); 41 | assert_eq!(delegation_amount, module_delegation.amount.amount); 42 | 43 | // undeleate 44 | undelegate( 45 | &mut app, 46 | meta_staking_addr.as_str(), 47 | mesh_consumer_addr.as_str(), 48 | VALIDATOR, 49 | delegation_amount, 50 | ) 51 | .unwrap(); 52 | 53 | let contract_delegation = query_delegation( 54 | &app, 55 | meta_staking_addr.as_str(), 56 | mesh_consumer_addr.as_str(), 57 | VALIDATOR, 58 | ) 59 | .unwrap(); 60 | 61 | assert_eq!(contract_delegation, Uint128::zero()); 62 | } 63 | 64 | #[test] 65 | fn no_consumer() { 66 | let (mut app, meta_staking_addr, mesh_consumer_addr) = setup_with_contracts(); 67 | 68 | let err = delegate( 69 | &mut app, 70 | meta_staking_addr.as_str(), 71 | mesh_consumer_addr.as_str(), 72 | VALIDATOR, 73 | Uint128::new(1000), 74 | ); 75 | 76 | assert_error!(err, ContractError::Unauthorized {}); 77 | 78 | let err = undelegate( 79 | &mut app, 80 | meta_staking_addr.as_str(), 81 | mesh_consumer_addr.as_str(), 82 | VALIDATOR, 83 | Uint128::new(1000), 84 | ); 85 | 86 | assert_error!(err, ContractError::NoConsumer {}); 87 | } 88 | 89 | #[test] 90 | fn delegate_too_much() { 91 | let (mut app, meta_staking_addr, mesh_consumer_addr) = setup_with_consumer(); 92 | 93 | let err = delegate( 94 | &mut app, 95 | meta_staking_addr.as_str(), 96 | mesh_consumer_addr.as_str(), 97 | VALIDATOR, 98 | Uint128::new(999999999), 99 | ); 100 | 101 | assert_error!(err, ContractError::NoFundsToDelegate {}); 102 | } 103 | 104 | #[test] 105 | fn undelegate_too_much() { 106 | let (mut app, meta_staking_addr, mesh_consumer_addr) = setup_with_consumer(); 107 | 108 | delegate( 109 | &mut app, 110 | meta_staking_addr.as_str(), 111 | mesh_consumer_addr.as_str(), 112 | VALIDATOR, 113 | Uint128::new(1000), 114 | ) 115 | .unwrap(); 116 | 117 | let err = undelegate( 118 | &mut app, 119 | meta_staking_addr.as_str(), 120 | mesh_consumer_addr.as_str(), 121 | VALIDATOR, 122 | Uint128::new(999999999), 123 | ); 124 | 125 | assert_error!(err, ContractError::InsufficientDelegation {}); 126 | } 127 | -------------------------------------------------------------------------------- /contracts/meta-staking/src/testing/test_queries.rs: -------------------------------------------------------------------------------- 1 | use super::utils::{ 2 | queries::{query_all_delegations, query_all_validators, query_consumers}, 3 | setup::{setup_with_consumer, setup_with_multiple_delegations}, 4 | }; 5 | 6 | #[test] 7 | fn test_query_all_delegations() { 8 | let (app, meta_staking_addr, mesh_consumer_addr_1, _) = setup_with_multiple_delegations(); 9 | 10 | let delegations = query_all_delegations( 11 | &app, 12 | meta_staking_addr.as_str(), 13 | mesh_consumer_addr_1.as_str(), 14 | ) 15 | .unwrap(); 16 | 17 | assert!(delegations.len() == 1) 18 | } 19 | 20 | #[test] 21 | fn test_query_all_validators() { 22 | let (app, meta_staking_addr, mesh_consumer_addr_1, _) = setup_with_multiple_delegations(); 23 | 24 | let validators = query_all_validators( 25 | &app, 26 | meta_staking_addr.as_str(), 27 | mesh_consumer_addr_1.as_str(), 28 | None, 29 | None, 30 | ) 31 | .unwrap(); 32 | 33 | assert!(validators.len() == 1) 34 | } 35 | 36 | #[test] 37 | fn test_query_consumers() { 38 | let (app, meta_staking_addr, _) = setup_with_consumer(); 39 | 40 | let consumers = query_consumers(&app, meta_staking_addr.as_str(), None, None).unwrap(); 41 | 42 | assert!(consumers.len() == 1) 43 | } 44 | -------------------------------------------------------------------------------- /contracts/meta-staking/src/testing/utils/executes.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result as AnyResult; 2 | use cosmwasm_std::{coin, Addr, Uint128}; 3 | use cw_multi_test::{App, AppResponse, Executor}; 4 | 5 | use mesh_apis::{StakingExecuteMsg as ExecuteMsg, StakingSudoMsg}; 6 | 7 | use mesh_testing::{constants::NATIVE_DENOM, macros::addr}; 8 | 9 | pub fn delegate( 10 | app: &mut App, 11 | contract_addr: &str, 12 | sender: &str, 13 | validator: &str, 14 | amount: Uint128, 15 | ) -> AnyResult { 16 | app.execute_contract( 17 | addr!(sender), 18 | addr!(contract_addr), 19 | &ExecuteMsg::Delegate { 20 | validator: validator.to_string(), 21 | amount, 22 | }, 23 | &[], 24 | ) 25 | } 26 | 27 | pub fn undelegate( 28 | app: &mut App, 29 | contract_addr: &str, 30 | sender: &str, 31 | validator: &str, 32 | amount: Uint128, 33 | ) -> AnyResult { 34 | app.execute_contract( 35 | addr!(sender), 36 | addr!(contract_addr), 37 | &ExecuteMsg::Undelegate { 38 | validator: validator.to_string(), 39 | amount, 40 | }, 41 | &[], 42 | ) 43 | } 44 | 45 | pub fn withdraw_rewards( 46 | app: &mut App, 47 | contract_addr: &str, 48 | sender: &str, 49 | validator: &str, 50 | ) -> AnyResult { 51 | app.execute_contract( 52 | addr!(sender), 53 | addr!(contract_addr), 54 | &ExecuteMsg::WithdrawDelegatorReward { 55 | validator: validator.to_string(), 56 | }, 57 | &[], 58 | ) 59 | } 60 | 61 | pub fn add_consumer( 62 | app: &mut App, 63 | contract_addr: &str, 64 | sender: &str, 65 | consumer_addr: &str, 66 | funds_avaiable: u128, 67 | ) -> AnyResult { 68 | let sudo_msg = StakingSudoMsg::AddConsumer { 69 | consumer_address: consumer_addr.to_string(), 70 | funds_available_for_staking: coin(funds_avaiable, NATIVE_DENOM), 71 | }; 72 | 73 | app.execute_contract( 74 | addr!(sender), 75 | addr!(contract_addr), 76 | &ExecuteMsg::Sudo(sudo_msg), 77 | &[], 78 | ) 79 | } 80 | 81 | pub fn remove_consumer( 82 | app: &mut App, 83 | contract_addr: &str, 84 | sender: &str, 85 | consumer_addr: &str, 86 | ) -> AnyResult { 87 | let sudo_msg = StakingSudoMsg::RemoveConsumer { 88 | consumer_address: consumer_addr.to_string(), 89 | }; 90 | 91 | app.execute_contract( 92 | addr!(sender), 93 | addr!(contract_addr), 94 | &ExecuteMsg::Sudo(sudo_msg), 95 | &[], 96 | ) 97 | } 98 | 99 | // TODO: withdraw to consumer end with IBC call which is not supported by cw-multi-test 100 | pub fn _withdraw_to_consumer( 101 | _app: &mut App, 102 | _contract_addr: Addr, 103 | _sender: &str, 104 | _consumer: &str, 105 | _validator: &str, 106 | ) -> AnyResult { 107 | unimplemented!() 108 | // app.execute_contract( 109 | // addr!(sender), 110 | // contract_addr.clone(), 111 | // &ExecuteMsg::WithdrawToCostumer { consumer: consumer.to_string(), validator: validator.to_string() }, 112 | // &[], 113 | // ) 114 | } 115 | -------------------------------------------------------------------------------- /contracts/meta-staking/src/testing/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod executes; 2 | pub mod queries; 3 | 4 | pub mod setup; 5 | -------------------------------------------------------------------------------- /contracts/meta-staking/src/testing/utils/queries.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::{Addr, FullDelegation, StdResult, Uint128}; 2 | use cw_multi_test::App; 3 | 4 | use crate::{ 5 | msg::{Delegation, QueryMsg}, 6 | state::ConsumerInfo, 7 | }; 8 | 9 | pub fn query_delegation( 10 | app: &App, 11 | contract_addr: &str, 12 | consumer: &str, 13 | validator: &str, 14 | ) -> StdResult { 15 | let delegation = app.wrap().query_wasm_smart( 16 | contract_addr, 17 | &QueryMsg::Delegation { 18 | consumer: consumer.to_string(), 19 | validator: validator.to_string(), 20 | }, 21 | )?; 22 | Ok(delegation) 23 | } 24 | 25 | pub fn query_module_delegation( 26 | app: &App, 27 | delegator: &str, 28 | validator: &str, 29 | ) -> Option { 30 | app.wrap().query_delegation(delegator, validator).unwrap() 31 | } 32 | 33 | pub fn query_rewards(app: &App, delegator: &str, validator: &str) -> Option { 34 | let rewards = query_module_delegation(app, delegator, validator) 35 | .unwrap() 36 | .accumulated_rewards; 37 | 38 | if rewards.is_empty() { 39 | None 40 | } else { 41 | Some(rewards[0].amount) 42 | } 43 | } 44 | 45 | pub fn query_all_delegations( 46 | app: &App, 47 | contract_addr: &str, 48 | consumer: &str, 49 | ) -> StdResult> { 50 | let delegations = app.wrap().query_wasm_smart( 51 | contract_addr, 52 | &QueryMsg::AllDelegations { 53 | consumer: consumer.to_string(), 54 | }, 55 | )?; 56 | Ok(delegations) 57 | } 58 | 59 | pub fn query_consumer(app: &App, contract_addr: &str, consumer: &str) -> StdResult { 60 | let consumer = app.wrap().query_wasm_smart( 61 | contract_addr, 62 | &QueryMsg::Consumer { 63 | address: consumer.to_string(), 64 | }, 65 | )?; 66 | Ok(consumer) 67 | } 68 | 69 | pub fn query_consumers( 70 | app: &App, 71 | contract_addr: &str, 72 | start: Option, 73 | limit: Option, 74 | ) -> StdResult> { 75 | let consumers = app 76 | .wrap() 77 | .query_wasm_smart(contract_addr, &&QueryMsg::Consumers { start, limit })?; 78 | Ok(consumers) 79 | } 80 | 81 | pub fn query_all_validators( 82 | app: &App, 83 | contract_addr: &str, 84 | consumer: &str, 85 | start: Option, 86 | limit: Option, 87 | ) -> StdResult> { 88 | let validators = app.wrap().query_wasm_smart( 89 | contract_addr, 90 | &&QueryMsg::AllValidators { 91 | consumer: consumer.to_string(), 92 | start, 93 | limit, 94 | }, 95 | )?; 96 | Ok(validators) 97 | } 98 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | services: 3 | osmosis: 4 | image: "confio/osmosis-ci:9.0.0" 5 | ports: 6 | - "26653:26657" 7 | user: root 8 | volumes: 9 | - ${PWD}/docker/osmosis/template:/template 10 | wasmd: 11 | image: "confio/wasm-ci:0.27.0" 12 | ports: 13 | - "26659:26657" 14 | user: root 15 | volumes: 16 | - ${PWD}/docker/wasmd/template:/template 17 | -------------------------------------------------------------------------------- /docker/osmosis/.gitignore: -------------------------------------------------------------------------------- 1 | debug.log -------------------------------------------------------------------------------- /docker/osmosis/README.md: -------------------------------------------------------------------------------- 1 | # Local Osmosisd development network 2 | 3 | Configuration is in the `env` file, that is the most likely place you want to adjust 4 | 5 | ## Initializing new data 6 | 7 | ```bash 8 | scripts/osmosis/generate_template.sh 9 | ``` 10 | 11 | Note that the addresses receiving tokens in genesis are set here, you can customize by editting this file 12 | 13 | ## Starting the blockchain 14 | 15 | Run the following: 16 | 17 | ```bash 18 | scripts/osmosis/start.sh 19 | ``` 20 | 21 | You get filtered output on the console. If it crashes and you want the full logs, look at `debug-osmosis.log`. 22 | 23 | ## Stopping the blockchain 24 | 25 | While running the blockchain in one terminal, open a second one and run this: 26 | 27 | ```bash 28 | scripts/osmosis/stop.sh 29 | ``` 30 | -------------------------------------------------------------------------------- /docker/osmosis/env: -------------------------------------------------------------------------------- 1 | REPOSITORY="confio/osmosis-ci" 2 | # Choose from https://hub.docker.com/r/cephalopodequipment/osmosisd/tags 3 | VERSION="9.0.0" 4 | CONTAINER_NAME="osmosis" 5 | -------------------------------------------------------------------------------- /docker/osmosis/generate_template.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit -o nounset -o pipefail 3 | command -v shellcheck >/dev/null && shellcheck "$0" 4 | 5 | SCRIPT_DIR="$(realpath "$(dirname "$0")")" 6 | echo $SCRIPT_DIR 7 | # shellcheck source=./env 8 | # shellcheck disable=SC1091 9 | source "$SCRIPT_DIR"/env 10 | 11 | mkdir -p "$SCRIPT_DIR"/template 12 | 13 | echo "Trying docker $REPOSITORY:$VERSION" 14 | 15 | # The usage of the accounts below is documented in README.md of this directory 16 | docker run --rm \ 17 | --user=root \ 18 | -e TRANSFER_PORT=transfer \ 19 | --mount type=bind,source="$SCRIPT_DIR/template",target=/root \ 20 | "$REPOSITORY:$VERSION" \ 21 | /opt/setup.sh \ 22 | osmo1lvrwcvrqlc5ktzp2c4t22xgkx29q3y83hdcc5d 23 | 24 | sudo chmod -R g+rwx template/.osmosisd/ 25 | sudo chmod -R a+rx template/.osmosisd/ 26 | 27 | # The ./template folder is created by the docker daemon's user (root on Linux, current user 28 | # when using Docker Desktop on macOS), let's make it ours if needed 29 | if [ ! -x "$SCRIPT_DIR/template/.osmosisd/config/gentx" ]; then 30 | sudo chown -R "$(id -u):$(id -g)" "$SCRIPT_DIR/template" 31 | fi 32 | -------------------------------------------------------------------------------- /docker/osmosis/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit -o nounset -o pipefail 3 | command -v shellcheck >/dev/null && shellcheck "$0" 4 | 5 | # Please keep this in sync with the Ports overview in HACKING.md 6 | TENDERMINT_PORT_GUEST="26657" 7 | TENDERMINT_PORT_HOST="26653" 8 | 9 | SCRIPT_DIR="$(realpath "$(dirname "$0")")" 10 | # shellcheck source=./env 11 | # shellcheck disable=SC1091 12 | source "$SCRIPT_DIR"/env 13 | 14 | # Use a fresh volume for every start 15 | docker volume rm -f osmosis_data 16 | # only pull if we don't have it 17 | (docker images | grep "$REPOSITORY" | grep -q "$VERSION") || docker pull "$REPOSITORY:$VERSION" 18 | 19 | echo "starting osmosisd running on http://localhost:$TENDERMINT_PORT_HOST" 20 | 21 | docker run --rm \ 22 | --user=root \ 23 | --name "$CONTAINER_NAME" \ 24 | -p "$TENDERMINT_PORT_HOST":"$TENDERMINT_PORT_GUEST" \ 25 | --mount type=bind,source="$SCRIPT_DIR/template",target=/template \ 26 | --mount type=volume,source=osmosis_data,target=/root \ 27 | "$REPOSITORY:$VERSION" \ 28 | 2>&1 | tee debug-osmosis.log | grep 'executed block' 29 | -------------------------------------------------------------------------------- /docker/osmosis/stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit -o nounset -o pipefail 3 | command -v shellcheck >/dev/null && shellcheck "$0" 4 | 5 | SCRIPT_DIR="$(realpath "$(dirname "$0")")" 6 | # shellcheck source=./env 7 | # shellcheck disable=SC1091 8 | source "$SCRIPT_DIR"/env 9 | 10 | echo "Killing osmosisd container..." 11 | docker container kill "$CONTAINER_NAME" 12 | -------------------------------------------------------------------------------- /docker/osmosis/template/.osmosisd/6fc458e78b00b50b013a0fa11b2d5e1fdcc4029c.address: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjcmVhdGVkIjoiMjAyMi0wNy0yMCAxOTo0NToxMi41NjA2NzAzODkgKzAwMDAgVVRDIG09KzAuNjUzOTIzMzc2IiwiZW5jIjoiQTI1NkdDTSIsInAyYyI6ODE5MiwicDJzIjoiaENnU202RTBtWFhlY1l5VyJ9.8Efu-9QYciwxAT5pmQAuAyBNJtbMM9TdKI-ENVYaGsB4_djL7BrbPA.4F_EKWRdkvTDp6ql.Qqt5AoHVIUkQFqYodggrvm41dy-ztkfrVo1EUF_pb-VDakKY8WpLmY49RfNioxciQ2uwjCTIss6Tn7upooeUPh75N2zZeLtTVl41zbqyvL8b7Lb2tmw1k3FmMQUAw_zx8u4FPklXQ3guIkhXEkB_IF635ZK5GK7iZh3s30M-Wk4UrzVwefZiyMB4PaTnjpSKFblt7dwZ4C4DPAAofyH9P9urrJQN_qaNVvaFV70auYZRyXgQ79ybG5gD.CvvYRZn-hFxXGqvaRJ9REQ -------------------------------------------------------------------------------- /docker/osmosis/template/.osmosisd/config/client.toml: -------------------------------------------------------------------------------- 1 | # This is a TOML config file. 2 | # For more information, see https://github.com/toml-lang/toml 3 | 4 | ############################################################################### 5 | ### Client Configuration ### 6 | ############################################################################### 7 | 8 | # The network chain ID 9 | chain-id = "" 10 | # The keyring's backend, where the keys are stored (os|file|kwallet|pass|test|memory) 11 | keyring-backend = "os" 12 | # CLI output format (text|json) 13 | output = "text" 14 | # : to Tendermint RPC interface for this chain 15 | node = "tcp://localhost:26657" 16 | # Transaction broadcasting mode (sync|async|block) 17 | broadcast-mode = "sync" 18 | -------------------------------------------------------------------------------- /docker/osmosis/template/.osmosisd/config/gentx/gentx-8c3f5817e96cc2a75d729f89a8ddfe23883f6c34.json: -------------------------------------------------------------------------------- 1 | {"body":{"messages":[{"@type":"/cosmos.staking.v1beta1.MsgCreateValidator","description":{"moniker":"osmo-moniker","identity":"","website":"","security_contact":"","details":""},"commission":{"rate":"0.100000000000000000","max_rate":"0.200000000000000000","max_change_rate":"0.010000000000000000"},"min_self_delegation":"1","delegator_address":"osmo1dlz93eutqz6skqf6p7s3kt27rlwvgq5up9xma3","validator_address":"osmovaloper1dlz93eutqz6skqf6p7s3kt27rlwvgq5umjwc2k","pubkey":{"@type":"/cosmos.crypto.ed25519.PubKey","key":"SqQYMs5aVi4XUZP2Pet4YnhVQFWsWGpZZzewg1fLUjg="},"value":{"denom":"uosmo","amount":"3000000"}}],"memo":"8c3f5817e96cc2a75d729f89a8ddfe23883f6c34@172.17.0.2:26656","timeout_height":"0","extension_options":[],"non_critical_extension_options":[]},"auth_info":{"signer_infos":[{"public_key":{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"A+WeeHXm8WuDNWQ21OorfzGpIvGOqLYFlEYS+niPO7Gx"},"mode_info":{"single":{"mode":"SIGN_MODE_DIRECT"}},"sequence":"0"}],"fee":{"amount":[],"gas_limit":"350000","payer":"","granter":""}},"signatures":["jniNpsTjH5FtktRzMt91mhHZPfwYxRG3Hy4ITNQX0I49oX8nQ1NNbR2vmyebZwWLORrkoy9mvXBpMLtNZqyebQ=="]} 2 | -------------------------------------------------------------------------------- /docker/osmosis/template/.osmosisd/config/node_key.json: -------------------------------------------------------------------------------- 1 | {"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"lI+YR/HllQTo92S583RbyRGyCJQvib+v+OqK8Dxss0oeE7Ga7gljmq2xmWgfK2E48SV3Eae9aG3xgjl9+D5f/A=="}} -------------------------------------------------------------------------------- /docker/osmosis/template/.osmosisd/config/priv_validator_key.json: -------------------------------------------------------------------------------- 1 | { 2 | "address": "0303542D813398A9F452F249A60016FC1DC4A79F", 3 | "pub_key": { 4 | "type": "tendermint/PubKeyEd25519", 5 | "value": "SqQYMs5aVi4XUZP2Pet4YnhVQFWsWGpZZzewg1fLUjg=" 6 | }, 7 | "priv_key": { 8 | "type": "tendermint/PrivKeyEd25519", 9 | "value": "wlDNMiFw0gTVGgBdb5jZs8ShXi8gHTKKx1J/9q8YECNKpBgyzlpWLhdRk/Y963hieFVAVaxYallnN7CDV8tSOA==" 10 | } 11 | } -------------------------------------------------------------------------------- /docker/osmosis/template/.osmosisd/data/priv_validator_state.json: -------------------------------------------------------------------------------- 1 | { 2 | "height": "0", 3 | "round": 0, 4 | "step": 0 5 | } -------------------------------------------------------------------------------- /docker/osmosis/template/.osmosisd/keyhash: -------------------------------------------------------------------------------- 1 | $2a$10$bNpXDhTBeAqVzaTC4MIsXeltpKG1V2OpNSINs6ql5WIrYRGXv9HWm -------------------------------------------------------------------------------- /docker/osmosis/template/.osmosisd/validator.info: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjcmVhdGVkIjoiMjAyMi0wNy0yMCAxOTo0NToxMi41MjMwMjk4NDcgKzAwMDAgVVRDIG09KzAuNjE2MjgzMzc2IiwiZW5jIjoiQTI1NkdDTSIsInAyYyI6ODE5MiwicDJzIjoiLWJrVDlya1ZmRFQ0T0owLSJ9.WpY0n05cm3Git29URAKchjmjZpE6U5VirwEFAjGjkU98M8Yvj5-bGQ.AZkszDfiWhrd0Pwo.pmKQTCy5V68XO7X9mRBMZqZkexzjaBiAMJrgjJoOqABbeinY-RXsvcVKizXNSeKmWhV3OjNPqm1yoWMcXAo3uhjlxH6VEZhRM_khsufQm5__XtJpoDM_frZFprpapxWZX9B3RHBxqjZMeSzi0UvvawFqvRPxQFZg6En1mmnCpyto2_1dnpKOQ-QO74DbALF3bTJk088-dxttzMw_uaAbADatOqCcC3LAoteSoOeuaCqT_ckfUVHaKggr4H2W6e0Er6wTI9oHMD5odGCvYRZgjWuOhQOfbaAE7lTIWSpd7ZZn7BGrtiGu-3Ur4LaH6l4FPkqVxJ2AdKBk__zx2T8RnO49AdvM0lGcMJmhEMxXH5d7g6uq.YSUMG1-dEGKZAwiK-y34iQ -------------------------------------------------------------------------------- /docker/wasmd/README.md: -------------------------------------------------------------------------------- 1 | # Local Osmosisd development network 2 | 3 | Configuration is in the `env` file, that is the most likely place you want to adjust 4 | 5 | ## Initializing new data 6 | 7 | ```bash 8 | scripts/wasmd/generate_template.sh 9 | ``` 10 | 11 | Note that the addresses receiving tokens in genesis are set here, you can customize by editting this file 12 | 13 | ## Starting the blockchain 14 | 15 | Run the following: 16 | 17 | ```bash 18 | scripts/wasmd/start.sh 19 | ``` 20 | 21 | You get filtered output on the console. If it crashes and you want the full logs, look at `debug-wasmd.log`. 22 | 23 | ## Stopping the blockchain 24 | 25 | While running the blockchain in one terminal, open a second one and run this: 26 | 27 | ```bash 28 | scripts/wasmd/stop.sh 29 | ``` 30 | -------------------------------------------------------------------------------- /docker/wasmd/env: -------------------------------------------------------------------------------- 1 | REPOSITORY="confio/wasm-ci" 2 | # This should be pinned to the newest version we support 3 | VERSION="0.27.0" 4 | CONTAINER_NAME="wasmd" 5 | -------------------------------------------------------------------------------- /docker/wasmd/generate_template.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit -o nounset -o pipefail 3 | command -v shellcheck >/dev/null && shellcheck "$0" 4 | 5 | SCRIPT_DIR="$(realpath "$(dirname "$0")")" 6 | # shellcheck source=./env 7 | # shellcheck disable=SC1091 8 | source "$SCRIPT_DIR"/env 9 | 10 | mkdir -p "$SCRIPT_DIR"/template 11 | 12 | export CHAIN_ID=wasmd-1 13 | 14 | # The usage of the accounts below is documented in README.md of this directory 15 | docker run --rm \ 16 | -e PASSWORD=my-secret-password \ 17 | -e CHAIN_ID \ 18 | --mount type=bind,source="$SCRIPT_DIR/template",target=/root \ 19 | "$REPOSITORY:$VERSION" \ 20 | /opt/setup.sh \ 21 | wasm14qemq0vw6y3gc3u3e0aty2e764u4gs5lndxgyk 22 | 23 | sudo chmod -R g+rwx template/.wasmd/ 24 | sudo chmod -R a+rx template/.wasmd/ 25 | 26 | # The ./template folder is created by the docker daemon's user (root on Linux, current user 27 | # when using Docker Desktop on macOS), let's make it ours if needed 28 | if [ ! -x "$SCRIPT_DIR/template/.wasmd/config/gentx" ]; then 29 | sudo chown -R "$(id -u):$(id -g)" "$SCRIPT_DIR/template" 30 | fi 31 | -------------------------------------------------------------------------------- /docker/wasmd/scripts/setup_wasmd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #set -o errexit -o nounset -o pipefail 3 | 4 | PASSWORD=${PASSWORD:-1234567890} 5 | STAKE=${STAKE_TOKEN:-ustake} 6 | FEE=${FEE_TOKEN:-ucosm} 7 | CHAIN_ID=${CHAIN_ID:-testing} 8 | MONIKER=${MONIKER:-node001} 9 | 10 | wasmd init --chain-id "$CHAIN_ID" "$MONIKER" 11 | # staking/governance token is hardcoded in config, change this 12 | sed -i "s/\"stake\"/\"$STAKE\"/" "$HOME"/.wasmd/config/genesis.json 13 | # this is essential for sub-1s block times (or header times go crazy) 14 | sed -i 's/"time_iota_ms": "1000"/"time_iota_ms": "10"/' "$HOME"/.wasmd/config/genesis.json 15 | 16 | if ! wasmd keys show validator; then 17 | (echo "$PASSWORD"; echo "$PASSWORD") | wasmd keys add validator 18 | fi 19 | # hardcode the validator account for this instance 20 | echo "$PASSWORD" | wasmd add-genesis-account validator "1000000000$STAKE,1000000000$FEE" 21 | 22 | # (optionally) add a few more genesis accounts 23 | for addr in "$@"; do 24 | echo $addr 25 | wasmd add-genesis-account "$addr" "1000000000$STAKE,1000000000$FEE" 26 | done 27 | 28 | # submit a genesis validator tx 29 | ## Workraround for https://github.com/cosmos/cosmos-sdk/issues/8251 30 | (echo "$PASSWORD"; echo "$PASSWORD"; echo "$PASSWORD") | wasmd gentx validator "250000000$STAKE" --chain-id="$CHAIN_ID" --amount="250000000$STAKE" 31 | ## should be: 32 | # (echo "$PASSWORD"; echo "$PASSWORD"; echo "$PASSWORD") | wasmd gentx validator "250000000$STAKE" --chain-id="$CHAIN_ID" 33 | wasmd collect-gentxs 34 | -------------------------------------------------------------------------------- /docker/wasmd/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit -o nounset -o pipefail 3 | command -v shellcheck >/dev/null && shellcheck "$0" 4 | 5 | # Please keep this in sync with the Ports overview in HACKING.md 6 | TENDERMINT_PORT_GUEST="26657" 7 | TENDERMINT_PORT_HOST="26659" 8 | 9 | SCRIPT_DIR="$(realpath "$(dirname "$0")")" 10 | # shellcheck source=./env 11 | # shellcheck disable=SC1091 12 | source "$SCRIPT_DIR"/env 13 | 14 | # TMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/wasmd.XXXXXXXXX") 15 | # chmod 777 "$TMP_DIR" 16 | # echo "Using temporary dir $TMP_DIR" 17 | # WASMD_LOGFILE="$TMP_DIR/wasmd.log" 18 | 19 | # Use a fresh volume for every start 20 | docker volume rm -f wasmd_data 21 | # only pull if we don't have it 22 | (docker images | grep "$REPOSITORY" | grep -q "$VERSION") || docker pull "$REPOSITORY:$VERSION" 23 | 24 | # This starts up wasmd 25 | echo "starting wasmd with rpc on port $TENDERMINT_PORT_HOST" 26 | docker run --rm \ 27 | --name "$CONTAINER_NAME" \ 28 | -p "$TENDERMINT_PORT_HOST":"$TENDERMINT_PORT_GUEST" \ 29 | --mount type=bind,source="$SCRIPT_DIR/template",target=/template \ 30 | --mount type=volume,source=wasmd_data,target=/root \ 31 | "$REPOSITORY:$VERSION" \ 32 | /opt/run.sh \ 33 | 2>&1 | tee debug-wasmd.log | grep 'executed block' 34 | -------------------------------------------------------------------------------- /docker/wasmd/stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit -o nounset -o pipefail 3 | command -v shellcheck >/dev/null && shellcheck "$0" 4 | 5 | SCRIPT_DIR="$(realpath "$(dirname "$0")")" 6 | # shellcheck source=./env 7 | # shellcheck disable=SC1091 8 | source "$SCRIPT_DIR"/env 9 | 10 | echo "Killing Wasmd container..." 11 | docker container kill "$CONTAINER_NAME" 12 | -------------------------------------------------------------------------------- /docker/wasmd/template/.wasmd/config/client.toml: -------------------------------------------------------------------------------- 1 | # This is a TOML config file. 2 | # For more information, see https://github.com/toml-lang/toml 3 | 4 | ############################################################################### 5 | ### Client Configuration ### 6 | ############################################################################### 7 | 8 | # The network chain ID 9 | chain-id = "" 10 | # The keyring's backend, where the keys are stored (os|file|kwallet|pass|test|memory) 11 | keyring-backend = "os" 12 | # CLI output format (text|json) 13 | output = "text" 14 | # : to Tendermint RPC interface for this chain 15 | node = "tcp://localhost:26657" 16 | # Transaction broadcasting mode (sync|async|block) 17 | broadcast-mode = "sync" 18 | -------------------------------------------------------------------------------- /docker/wasmd/template/.wasmd/config/gentx/gentx-e00476a52bbadced4266814cee02b61c4cbdb860.json: -------------------------------------------------------------------------------- 1 | {"body":{"messages":[{"@type":"/cosmos.staking.v1beta1.MsgCreateValidator","description":{"moniker":"wasm-moniker","identity":"","website":"","security_contact":"","details":""},"commission":{"rate":"0.100000000000000000","max_rate":"0.200000000000000000","max_change_rate":"0.010000000000000000"},"min_self_delegation":"1","delegator_address":"wasm17e5l4ggxlyjf7k5scnas2zflwmnj9gjg54kce7","validator_address":"wasmvaloper17e5l4ggxlyjf7k5scnas2zflwmnj9gjgpfryhy","pubkey":{"@type":"/cosmos.crypto.ed25519.PubKey","key":"pbhQLheUqr0nbaNZ7z1wzAsXjLRilfq4xbcaMN2XRUY="},"value":{"denom":"ustake","amount":"3000000"}}],"memo":"e00476a52bbadced4266814cee02b61c4cbdb860@172.17.0.2:26656","timeout_height":"0","extension_options":[],"non_critical_extension_options":[]},"auth_info":{"signer_infos":[{"public_key":{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"A+Mv0AWmUlfsrAvrUCYB2wOxnlBXzjFkAHXmUMalA50y"},"mode_info":{"single":{"mode":"SIGN_MODE_DIRECT"}},"sequence":"0"}],"fee":{"amount":[],"gas_limit":"200000","payer":"","granter":""}},"signatures":["oHhjuBqgpc8xV6SZu5v8E0Bf653PPRPvqYjsI+6l1ht1aeGF8f+ym+UynVvQj9mHc0EXlts23dN9Ht7/jm2WEA=="]} 2 | -------------------------------------------------------------------------------- /docker/wasmd/template/.wasmd/config/node_key.json: -------------------------------------------------------------------------------- 1 | {"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"n9jWRTekT/X5IxciXaXt7tNkVqvnrz74shUX9CctXsJp7wZeMgedPT4DvASVkJz1EkF0v/yU40ePH7AwL4IlQg=="}} -------------------------------------------------------------------------------- /docker/wasmd/template/.wasmd/config/priv_validator_key.json: -------------------------------------------------------------------------------- 1 | { 2 | "address": "DB364DA3F19040792740269C079AB040D0ACC9B2", 3 | "pub_key": { 4 | "type": "tendermint/PubKeyEd25519", 5 | "value": "pbhQLheUqr0nbaNZ7z1wzAsXjLRilfq4xbcaMN2XRUY=" 6 | }, 7 | "priv_key": { 8 | "type": "tendermint/PrivKeyEd25519", 9 | "value": "8+09drNpixEZe/Dbfm+0x46hD/8jZS5c3NLLt6UmmJGluFAuF5SqvSdto1nvPXDMCxeMtGKV+rjFtxow3ZdFRg==" 10 | } 11 | } -------------------------------------------------------------------------------- /docker/wasmd/template/.wasmd/data/priv_validator_state.json: -------------------------------------------------------------------------------- 1 | { 2 | "height": "0", 3 | "round": 0, 4 | "step": 0 5 | } -------------------------------------------------------------------------------- /docker/wasmd/template/.wasmd/f669faa106f9249f5a90c4fb05093f76e722a248.address: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjcmVhdGVkIjoiMjAyMi0wNy0yMCAyMDowMjozNS45NTM2Nzg1OTQgKzAwMDAgVVRDIG09KzAuNjM3NzgyNzEwIiwiZW5jIjoiQTI1NkdDTSIsInAyYyI6ODE5MiwicDJzIjoiZ25rZXlFR19hUnN3YTZJUiJ9.AoopwzMV7GLdqWwAkZK3MM3PEIwYCTdmeUtx15k5GqLVjoKHUFb0uw.hbxwFa3kKKGwXpAi.en1eHLJPI3FBMlNd5wI894HgHQV_EqRjFnS3ou9BuGESqbZUrvCNyCfJBP3tRGhnkSrUCvFuDiUolNd69o83QtThNK8BFRRbQVyHGaJPI5ZuhuaF_kt3FYmp7UrfVlumUve6Lx4_WY9aBN46MvRF3y5E49Qjer6Y8kqx61F23ag7NJ3ZHYhfRjbV6FyLcH-pSAjmIjLIqxIoLQOvZTWfg8z5aRhGIbRG9D_OCUjRX6-0jensqZVo9AJP.RWSTQClG4C9PalvJuBGJcA -------------------------------------------------------------------------------- /docker/wasmd/template/.wasmd/keyhash: -------------------------------------------------------------------------------- 1 | $2a$10$nL84R.qjyT23fDjFWaNZPOp8DBiOoQRsZmDwXy65PfKoh7uWNz6OW -------------------------------------------------------------------------------- /docker/wasmd/template/.wasmd/validator.info: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjcmVhdGVkIjoiMjAyMi0wNy0yMCAyMDowMjozNS45MTY2MTYxMzUgKzAwMDAgVVRDIG09KzAuNjAwNzIwMzM1IiwiZW5jIjoiQTI1NkdDTSIsInAyYyI6ODE5MiwicDJzIjoieU50NHRidGt6TUFZUVhOUyJ9.An0WIFG-o5_GHSfxTVs_PBVXKUHAlWjRdtLdKLbvDFqZ8GAPKebQSA.D0iZKIsCrLxx2zei.V9Zu-tGT9ULjYDu9pwwnjT5bJ7UidmTC4JbEKgac_GryRCfhy9Lmvumqyb38EbGGkAich27ZUHASLzWEC7clh3Z9_e70WQNsTqegoKdG_IG9YOa0-rC2eW_JGfnI-WSpE80tF7foA2sXBtug7YAfQL4JSyfIASbuc4odtTb_Vs0GhfPNS4cQSu0Z2GfT6WEgfPYnE7xU6nPAe39wiZDDgXPhB_TZZrLqMqycHYrYiIo4IwLwHDGlERjXnyh1KFOz0w0yRpl666HyUMAgkM7BR6D6_FXipr1g5d7ngfj4FWoAKjoxtq6vA2IjcWqGV1vSMC7ZbkXwr8M4Fqs0L3SYBYRqfWSMQEkMi3p0geKn74wOsMvg.a5z4pZrwHtY5USo8fj8UeA -------------------------------------------------------------------------------- /docs/MeshSecurity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CosmWasm/mesh-security-hackathon/0fa06e8d86ad91b3c551324f5517689f2edb6f08/docs/MeshSecurity.png -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Mesh Security Architecture 2 | 3 | This is an architectural overview of the various components of Mesh Security. 4 | These attempt to explain a full-refined v2 state. As needed, we will include 5 | simplifications used for MVP (testnet) or v1 (production-ready, feature-limited) 6 | as footnotes in the documents. 7 | 8 | ```mermaid 9 | flowchart TD 10 | %%{init: {'theme': 'forest'}}%% 11 | 12 | subgraph Osmosis 13 | A{{$OSMO}} -- User Deposit --> B(Vault); 14 | B -- $OSMO --> C(Local Staker); 15 | C -- $OSMO --> D[Native Staking] 16 | B -- Lein --> E(External Staker Juno); 17 | B -- Lein --> G(External Staker Stars); 18 | end 19 | 20 | E -. IBC .-> M; 21 | H -. IBC .-> N; 22 | 23 | subgraph Akash 24 | H{{Akash Provider}}; 25 | end 26 | 27 | subgraph Juno 28 | M(Osmosis Converter) -- virtual stake --> O(Virtual Staking 1); 29 | N(Akash Converter) -- virtual stake --> P(Virtual Staking 2); 30 | O & P -- $JUNO --> Q[Native Staking]; 31 | end 32 | 33 | G -. IBC .-> R; 34 | 35 | subgraph Stargaze 36 | R{{Osmosis Receiver}} -- virtual stake --> S(Virtual Staking); 37 | S -- $STARS --> T[Native Staking]; 38 | end 39 | 40 | ``` 41 | 42 | You can get a good overview of the whole system flow in the above diagram. 43 | The design should allow one chain to provide security to multiple chains, while 44 | at the same time receiving security from multiple chains. 45 | 46 | A key to understanding the design, is that the whole system is _delegator-centeric_ 47 | not _validator-centric_. This means that we don't try to match the same validators on 48 | multiple chains, or even subsets, but rather focus on leveraging the security 49 | provided by staking tokens to secure validators on multiple chains. This provides a way to 50 | scale bidirectionally, while avoiding some sort of infinite-loop recursion, as 51 | chains can only provide security in their native token(s). 52 | 53 | ## Use Cases 54 | 55 | Before we dig into the architecture of Mesh Security, please take a look at 56 | the [use cases we aim to serve](./UseCases.md). 57 | 58 | ## Definitions 59 | 60 | * **Pairing** - a trust relationship between two chains, such that one promises to lock up slashable 61 | stake, while the other leverages this promise to issue validation power in the dPoS system. 62 | Notably, chain A and chain B may have two pairings in opposite directions at the same time. 63 | * **Provider** _(also "Provider Chain")_ - when looking at one pairing, this is the chain which 64 | locks up tokens and provides them as staking collateral to the other chain. 65 | * **Consumer** _(also "Consumer Chain")_ - when looking at one pairing, this is the chain which 66 | adjusts validator weights and provides rewards based on remote collateral. 67 | * **Collateral** - Tokens locked up on the provider chain, which are providing security 68 | guarantees to one or more providers. These tokens may be slashed upon any slashing event 69 | in the consumers. 70 | * **Staking** _(also "Delegation")_ - The act of providing collateral for a validator, such that the 71 | staker receives a portion of the block rewards if they behave correctly, as well as the slashing risk 72 | if they misbehave. 73 | * **Unstaking** _(also "Unbonding")_ - The act of initialing the removal of stake from a system. During 74 | the "unbonding period", the collateral will not receive any rewards, but will be liable to slashing 75 | based on misbehavior of that validator. 76 | * **Unbonding period** - The time delay between initiating unbonding and having free access to the 77 | underlying collateral. Once this time has passed after unstaking, all claims on the underlying 78 | collateral are released and 79 | * **Rewards** - Block rewards are issued to validators in the native token of the consumer chain. 80 | A portion of these rewards goes to the stakers and is collected cross-chain. 81 | * **Slashing** - If a validator misbehaves, the tokens delegated to it, which provided the 82 | voting power, can be slashed. The percentage of slashing for each misbehavior depends on the chain. 83 | This slash must be reflected by stakers on the provider chain, but we may make some approximations 84 | as needed to implement this efficiently. 85 | * **Jailing** - If a validator misbehaves, it may be jailed, or removed from the validator set and 86 | prevented from returning. Tokens staked to it would be partially slashed and should be unstaked 87 | as soon as possible, as they will receive no more rewards. Stake to a jailed validator still must 88 | wait the unbonding period to be liquid. 89 | * **Latency** - Time delay from an action being initiated and the effects being reflected in 90 | another contract or chain. This doesn't refer to the unbonding period, but rather the delay between 91 | initiating bonding or unbonding on the provider and the equivalent action occurring on the consumer. 92 | 93 | ## Sections 94 | 95 | Below are links to detailed documents on various sub-systems: 96 | 97 | [Provider](./provider/Provider.md): 98 | * [Vault](./provider/Vault.md) 99 | * [Local Staking](./provider/LocalStaking.md) 100 | * [External Staking](./provider/ExternalStaking.md) 101 | * TODO - Rust interfaces 102 | 103 | [Consumer](./consumer/Consumer.md): 104 | * [Converter](./consumer/Converter.md) 105 | * [Virtual Staking](./consumer/VirtualStaking.md) 106 | * SDK Changes 107 | 108 | [IBC Protocol](./ibc/Overview.md): 109 | * [Cross-Chain Staking](./ibc/Staking.md) 110 | * [Reward Flow](./ibc/Rewards.md) 111 | * [Handling Slashing Evidence](./ibc/Slashing.md) 112 | 113 | ## Limitations 114 | 115 | **Unbonding Limits** 116 | 117 | As we are focused on the standard implementation of the Cosmos SDK, one account may only have 118 | a fixed number of simultaneous unbondings on a given delegator. By default, that number is 7. 119 | We must batch together multiple unbonding requests so there are never more than that number 120 | active at once. The typical solution for LSDs is to divide the unbonding period into segments, 121 | so if the unbonding period is 3 weeks, the contract initiates unbonding every 3 days for all 122 | requests that occurred in that period. 123 | -------------------------------------------------------------------------------- /docs/ROADMAP.md: -------------------------------------------------------------------------------- 1 | # Development Roadmap 2 | 3 | Created: Apr 17, 2023 4 | Status: Draft 5 | 6 | ## High Level Milestones 7 | 8 | ### MVP 9 | 10 | Target Date: June/July 2023 11 | 12 | The first milestone will be design containing all major features in a solid foundation to build on. 13 | It is designed to be launched on two testnets along with a usable UI, so we can begin getting real 14 | feedback from users and relayers, while building out the more complex features and polishing off some 15 | tougher issues. 16 | 17 | ### V1 18 | 19 | Target date: Early Q4 2023 ?? 20 | 21 | This will include a feature complete version of all code (eg including slashing), but may not have all 22 | extensions (such as remote staking not providing gov voting power). The provider side should 23 | be well reviewed and free from any security holes (safe to deploy on larger chains). The consumer side 24 | (which includes a custom SDK module) will be as solid as possible, but not recommended to run on serious 25 | chains. At this points, Osmosis could provide security to a small market cap chain. 26 | 27 | ### V2 28 | 29 | Target date: Early Q1 2024 ?? 30 | 31 | This will include all optional features we consider feasible in a realistic time frame and most importantly 32 | will have much deeper security model, and have received some external reviews (maybe audit if someone pays). 33 | This should be stable enough such that chains with solid market caps could provide peer security (being both 34 | provider and consumer). 35 | 36 | ## Plan for MVP 37 | 38 | A higher-level backlog for what it takes to create the MVP. 39 | This may be too many points and we need to review this. Currently in draft form. 40 | 41 | **Part 1: Foundation** 42 | 43 | * Start new repo for production mesh-security (port ideas from prototype, but we don't need to build on it) 44 | * Finalize the documentation for provider side 45 | * Define all contract interfaces / APIs as Rust files 46 | * Produce stub-contracts with proper APIs (all `unimplemented!()`) 47 | 48 | **Part 2: The Vault** 49 | 50 | * Produce mock contracts with proper APIs (all with dummy testing implementation) 51 | * Mocks should also be usable for UI testing with eg 1 minute unbonding 52 | * Write and test vault contract (that calls mock local and remote staking via multi-test) 53 | * Ensure vault accepts both native and cw20 tokens 54 | * All configuration options should be implemented 55 | * Finalize the documentation for the consumer side, including the custom SDK modules 56 | * Create initial designs (wireframes) for the UI, focusing around vault and local/remote staking 57 | 58 | **Part 3: Provider Staking** 59 | 60 | * Implement local staking module (simple version - no optional features) 61 | * Ensure user can withdraw rewards from local staking 62 | * Implement remote staking module, provider side (simple version - no optional features) 63 | * Implement mock converter, consumer side (connects via IBC properly, but ) 64 | * Full stack IBC test from `token -> vault -> remote staking -> converter`, bonding and unbonding 65 | * Usable UI for provider side (mocking out remote providers), with bonding and unbonding 66 | 67 | **Part 4: Consumer Staking** 68 | 69 | * Implement minimal Virtual Staking contract using minimal modifications to Osmosis' [superfluid staking](https://github.com/osmosis-labs/osmosis/tree/main/x/superfluid#messages) and [lockup](https://github.com/osmosis-labs/osmosis/blob/main/x/lockup/README.md#lock-tokens) modules. 70 | * Try to keep changes minimal to work without LP shares (rather using 1:1, or the converter rate) 71 | * Doesn't have to work properly in all cases, but should allow multiple bondings and unbondings to different validators 72 | * Implement proper converter that uses governance set rate but talks IBC fully and calls into Virtual Staking to make stake 73 | * Cross-chain full stack tests with IBC and staking (using osmosisd testnet config for consumer side, wasmd for provider side) 74 | * Rewards not yet implemented 75 | * Improve provider-side UI 76 | 77 | **Part 5: Rewards** 78 | 79 | * Make custom Virtual Staking module if needed 80 | * Minimal changes to Osmosis contracts if possible, so can be backported 81 | * Custom SDK app with this module (based on wasmd) for consumer side 82 | * Handles reward withdrawl 83 | * Handles multi bonding/unbonding in quick succession (not blocked by 7 pending unbonds issue) 84 | * Add reward flow to Converter 85 | * Send rewards over IBC to RemoteStaking contract 86 | * Add reward withdrawal to remote staking 87 | * Add cross-chain staking to UI 88 | 89 | **Part 6: Finalize Binaries** 90 | 91 | * Close any TODOs or known issues / bugs that fit in MVP scope 92 | * Create well-defined GitHub issues for items to be addressed later (label V1 or V2) 93 | 94 | * At least one: 95 | * Create fork of (wasmd? junod?) that supports virtual staking 96 | * Create fork of osmosisd that supports virtual staking (ideally reusing as much as possible) 97 | * Provide dev net environment showing this is working with a usable UI (for internal testing) 98 | 99 | **Part 7: Bring to testnet** 100 | 101 | * Pass code to the chains' testnets, so they can bring to the masses 102 | -------------------------------------------------------------------------------- /docs/UseCases.md: -------------------------------------------------------------------------------- 1 | # Use Cases 2 | 3 | ## Sibling Chains 4 | 5 | Two chains of similar size want to support each other. 6 | There can be a difference of the strength, such that 7 | larger -> smaller has more weight than smaller -> larger. 8 | 9 | ```mermaid 10 | flowchart LR 11 | %%{init: {'theme': 'forest'}}%% 12 | A(Juno) -- 40% --> B(Star); 13 | B -- 20% --> A; 14 | ``` 15 | 16 | ## Full Mesh 17 | 18 | This is meant for multiple chains with market caps within 19 | say an order of magnitude that all have supportive relations 20 | and want to enter a joint security zone. They can all provide meaningful levels of security 21 | to each other. This leads to many bidirectional flows and 22 | even loops (yes, the algorithm handles this). 23 | 24 | Note that Osmosis is about 5x the size of the other two, 25 | so the weights are proportional to their relative market caps. 26 | 27 | ```mermaid 28 | flowchart LR 29 | %%{init: {'theme': 'forest'}}%% 30 | A(OSMO) == 50% ==> B(Juno); 31 | A == 50% ==> C(Akash); 32 | B -- 20% --> C; 33 | C -- 20% --> B; 34 | B -- 10% --> A; 35 | C -- 10% --> A; 36 | ``` 37 | 38 | ## DAOs migrating to own chain 39 | 40 | A number of Juno DAOs launching their own chains. They want to inherit most of their security from Juno, 41 | but keep governance to their own token. 42 | 43 | ```mermaid 44 | flowchart TD 45 | %%{init: {'theme': 'forest'}}%% 46 | Juno -- 75%, no gov --> DAO1; 47 | Juno -- 75%, no gov --> DAO2; 48 | Juno -- 75%, no gov --> DAO3; 49 | ``` 50 | 51 | ## Almost Replicated Security 52 | 53 | Mesh Security is not ICSv1 "Replicated Security". We do not map validators from provider to consumer, but rather delegators. 54 | And the power is the subset of staked tokens that opt-in, so will always be lower than full stake. By design we always require 55 | a native staking token in the consumer chain, but we can approximate "replciated security" for the "fully owned subsidiary" 56 | use case. 57 | 58 | You can launch a chain with a governance token with minimal distribution to bootstrap the chain. Then accept another chain as a 59 | provider, locking it's token 1:1 with the native token (hardcoded "oracle" feed), and allow it to control up to 99% of the voting power 60 | of the chain, including gov votes. The end effect is the new chain is almost 100% controlled by the cross-stakers of the parent chain: 61 | 62 | ```mermaid 63 | flowchart TD 64 | %%{init: {'theme': 'forest'}}%% 65 | Juno -- 99%, gov --> Eve; 66 | ``` 67 | 68 | 69 | ## Credibly Neutral Common Good 70 | 71 | There are some items that should be neutral or independent of multiple chains, 72 | like a shared name service. In this case, we allow multiple controlling chains to 73 | control the staking and governance, even without any native staking power. 74 | 75 | ```mermaid 76 | flowchart TD 77 | %%{init: {'theme': 'forest'}}%% 78 | A(OSMO) --> |25%| N[Name Service]; 79 | B(Juno) -- 25% --> N; 80 | C(Stargaze) -- 25% --> N; 81 | D(Cosmos Hub) -- 25% --> N; 82 | ``` 83 | 84 | -------------------------------------------------------------------------------- /docs/consumer/Consumer.md: -------------------------------------------------------------------------------- 1 | # Consumer 2 | 3 | The consumer side of the system receives "virtual tokens" from 4 | some trusted providers and uses those to update the local staking weights. 5 | It then provides rewards to those providers in exchange for 6 | the security promise it receives. 7 | 8 | This is the other half of [the provider flow](../provider/Provider.md) 9 | 10 | ```mermaid 11 | flowchart LR 12 | %%{init: {'theme': 'forest'}}%% 13 | 14 | A{{Osmosis Provider}}; 15 | B{{Juno Provider}}; 16 | 17 | A -. IBC .-> D; 18 | 19 | subgraph Stargaze 20 | C(Osmosis Price feed) --> D(Osmosis Converter) 21 | D -- virtual stake --> E(Virtual Staking 1); 22 | K(Juno Converter) -- virtual stake --> L(Virtual Staking 2); 23 | M(Juno Price feed) --> K; 24 | E & L -- $STARS --> G[Native Staking]; 25 | end 26 | 27 | B -. IBC .-> K; 28 | ``` 29 | 30 | ## Converting Foreign Stake 31 | 32 | Not all providers are treated equally. (And this is a good thing) 33 | 34 | Each Converter accepts messages from exactly one provider and is 35 | the point where we establish trust. The Converter is responsible for 36 | converting the staking messages into local units. It does two transformations. 37 | This first is convert the token based on a price oracle. The second step is to apply a discount, 38 | which captures both the volatility of the remote asset, as well as 39 | a general preference for local/native staking. 40 | 41 | This is described [more in depth under Converter](./Converter.md#staking-flow). 42 | 43 | ## Virtual Staking 44 | 45 | Once the Converter has normalized the foreign stake into the native staking units, 46 | it calls into the associated ["Virtual Staking" contract](./VirtualStaking.md) in order 47 | to convert this into real stake without owning actual tokens. This contract must be 48 | authorized to have extra power in a native Cosmos SDK module to do this impact, and has 49 | a total limit of tokens it can stake. It performs 3 high level tasks: 50 | 51 | * Staking "virtual tokens" as requested by the Converter (up to a limit) 52 | * Periodically withdrawing rewards and sending them to the Converter 53 | * Unstaking "virtual tokens" as requested by the Converter. This must be immediate and 54 | avoid the "7 concurrent unbonding" limit on the `x/staking` module to be properly usable. 55 | 56 | ## Handling Failures 57 | 58 | **TODO** 59 | 60 | Explain how this system limits systemic failures in a few cases. 61 | (Does this section belong here? Or in a higher level doc?) 62 | 63 | Price issues: 64 | 65 | * Foreign Provider price moons (Existing stake hits the max cap) 66 | * Foreign Provider price crashes (Usually accounted by discount, generally should not get more power staking 67 | $1 of foreign tokens than $1 of local tokens) 68 | * Consumer price crashes (All providers get better conversion, but limited by max cap) 69 | 70 | Byzantine Actors: 71 | 72 | * Foreign Provider goes Byzantine (okay as long as their max cap is less than circa 30% of total stake) 73 | * Consumer goes Byzantine (Can force unbond, withhold rewards, but not slash or permanently lock up tokens) 74 | -------------------------------------------------------------------------------- /docs/ibc/Overview.md: -------------------------------------------------------------------------------- 1 | # IBC Protocol 2 | 3 | Mesh Security is fundamentally an interchain-protocol, 4 | and it is important to define the exact mechanics of 5 | how the different subsystems interact. 6 | 7 | Here were are focusing just on the dotted lines, that 8 | we abstracted away when focusing on [Providers](../provider/Provider.md) 9 | and [Consumers](../consumer/Consumer.md). 10 | 11 | Fundamentally there are these two flows, although slashing 12 | is also done cross chain, but outside of IBC (by external observers). 13 | For the time being, we focus on the two IBC data flows 14 | and discuss cross-chain slashing in another section. 15 | 16 | ```mermaid 17 | flowchart RL 18 | %%{init: {'theme': 'forest'}}%% 19 | 20 | A[Provider] -- Staking --> B[Consumer]; 21 | B -- Rewards --> A; 22 | 23 | C{{Observer}} -. Slashing Evidence .-> A; 24 | ``` 25 | 26 | ## Deployment 27 | 28 | As mentioned in [the consumer section](../consumer/Consumer.md), 29 | we need to establish a trust relationship between the Provider side and the Consumer side. This is done in multiple stages. 30 | 31 | I refer to "Admin" here in most of the setup, which can be any address that can make calls on two chains. 32 | It is responsible for proper configuration, but revokes all it's power before the provider-consumer pairing has any 33 | voting power (permitted to virtually stake). It can be a normal account, a ledger, a cosmos sdk native multisig, 34 | a DAO using ICA to act on two chains. In theory this could be on-chain governance, but that would be unwieldy. 35 | 36 | I recommend using the approach of a single actor deploying (for simplicity), then using governance to authorize 37 | the connection once it is all configured (for security). 38 | 39 | Establish a channel (allow list): 40 | 41 | 2. Admin deploys _Converter_ and _Virtual Staking_ on Consumer side, both referencing each other. 42 | 3. Admin deploys _External Staker_ on Provider side, registering `(connection, port)` from _Converter_ contract from (1) 43 | 4. Admin updates _Converter_ (1) to allow the `(connection, port)` from (2) 44 | 5. Anyone establishes a new channel between _Converter_ and _External Staker_. 45 | Both contracts ensure the other side matches the stored `(connection, port)` and refused the channel otherwise. 46 | 6. Neither contract accept any more channels. They only accept one channel and only what matches their config. 47 | 48 | Now that we have a channel and know which contract is talking to whom, we need 49 | to authorize them: 50 | 51 | 1. Admin removes their admin rights on both contracts (or passes to chain governance) 52 | 2. Due diligence performed on configuration of both contracts and the channel. Parties from both chains can verify the contract code and configuration. 53 | 3. Consumer chain governance votes to authorize this _Virtual Staking_ contract to have special 54 | privileges on the staking system (see [Virtual Staking](../consumer/VirtualStaking.md)). 55 | 4. Authorization on the Provider chain [is not required](https://github.com/CosmWasm/mesh-security/blob/begin-architecture/docs/provider/Vault.md#design-decisions), 56 | but the default cross-stake frontend application should add the _External Staker_ to the recommended list. 57 | 58 | Once this has been completed, everything is set up and the token holders on the provider side 59 | can now safely cross-stake on the consumer side with one click. 60 | 61 | Note that the majority of the setup is permissionless. And it just requires one governance vote on the 62 | consumer side to authorize ability to stake virtual tokens, which is essential for any security guarantees. 63 | No forks, no complicated processes... just one proposal. 64 | 65 | ## Protocol design 66 | 67 | We use this custom channel to communicate securely and directly 68 | between the two sides, sending messages about bonding and unbonding 69 | delegations. This is the principle form of communication, 70 | and we dig into the [Cross-Chain Staking Protocol](./Staking.md) 71 | in more depth. 72 | 73 | We send reward tokens over a standard ics20 channel so that 74 | they are fungible with the native staking token currently sent 75 | between the chains (it will have the same denom as what's listed on the DEX). 76 | Besides moving the rewards, we need to inform the _External Staker_ that 77 | it has received rewards so it can distribute them to validators. 78 | We will do that either with a separate message in the control channel (above), 79 | or by making use of the ICS20 memo field to pass this info. 80 | Read more about the [Cross-Chain Rewards Protocol](./Rewards.md) 81 | in more depth. 82 | 83 | Finally, slashing is not in-protocol, so a malicious consumer chain 84 | cannot slash an honest delegator. Rather, an external observer must 85 | submit evidence of a double sign on the consumer chain directly to 86 | the provider chain. With that direct proof, it will take action to slash 87 | the appropriate stake. We explain this design more in "Trust Assumptions" below. 88 | Read more about the mechanics of the [Slashing Evidence Handling](./Slashing.md). 89 | 90 | ## Trust Assumptions 91 | 92 | **TODO** list between the consumer and the provider 93 | -------------------------------------------------------------------------------- /docs/ibc/Rewards.md: -------------------------------------------------------------------------------- 1 | # Cross-Chain Rewards Protocol 2 | 3 | **TODO** Define IBC design 4 | -------------------------------------------------------------------------------- /docs/ibc/Slashing.md: -------------------------------------------------------------------------------- 1 | # Slashing Evidence Handling 2 | 3 | Note: Slashing will not be part of the MVP rollout, 4 | and first implemented in v1. However, we define 5 | proper slashing mechanics here. 6 | 7 | **TODO** Define Slashing design 8 | -------------------------------------------------------------------------------- /docs/ibc/Staking.md: -------------------------------------------------------------------------------- 1 | # Cross-Chain Staking Protocol 2 | 3 | **TODO** Define IBC design 4 | -------------------------------------------------------------------------------- /docs/provider/LocalStaking.md: -------------------------------------------------------------------------------- 1 | # Local Staking 2 | 3 | A local staking contract connects to a [Vault](./Vault.md). Unlike other creditors, it actually 4 | accepts the native token along with the lien. It manages staking the vault tokens to the native 5 | protocol and returns them when finished unbonding. 6 | 7 | **TODO** flesh this out 8 | -------------------------------------------------------------------------------- /docs/provider/Vault.md: -------------------------------------------------------------------------------- 1 | # Vault 2 | 3 | The entry point of Mesh Security is the **Vault**. This is where a potential 4 | staker can provide collateral in the form of native tokens, with which he or she wants 5 | to stake on multiple chains. 6 | 7 | Connected to the _Vault_ contract, is exactly one [local Staking contract](./LocalStaking.md) 8 | which can delegate the actual token to the native staking module. It also can connect to an 9 | arbitrary number of [external staking contracts](./ExternalStaking.md) which can make use 10 | of said collateral as "virtual stake" to use in an external staking system (that doesn't 11 | use the vault token as collateral). 12 | 13 | The key here is that we can safely use the 14 | same collateral for multiple protocols, as the maximum slashing penalty is significantly 15 | below 100%. If double-signing is penalized by a 5% slash (typical in Cosmos SDK chains), 16 | then one could safely use the same collateral to provide security to 20 chains, as even 17 | if every validator that used that collateral double-signed, there would still be enough 18 | stake to slash to cover that security promise. 19 | 20 | As discussed in the higher-level description of the provider design, about extending 21 | this [concept to local DAOs](./Provider.md#dao-dao-extension), 22 | there may be many different implementations of both the _Local Staking_ concept as well 23 | as the _External Staking_ concept. However, we must define 24 | standard interfaces here that can plug into the Vault. 25 | 26 | We define this interface as a _Creditor_ (as it accepts leins). 27 | 28 | ## Definitions 29 | 30 | TODO: refine this 31 | 32 | * **Native Token** - The native staking token of this blockchain. More specifically, 33 | the token in which all collateral is measured. 34 | * **Slashable Collateral** - `Leins(user).map(|x| x.amount * x.slashable).sum()` 35 | * **Maximum Lein** - `Leins(user).map(|x| x.amount).max()` 36 | * **Free Collateral** - `Collateral(user) - max(SlashableCollateral(user), MaximumLein(user))` 37 | 38 | ## Design Decisions 39 | 40 | The _vault_ contract requires one canonical _Local Staking_ contract to be defined when it is 41 | created, and this contract address cannot be changed. 42 | 43 | The _vault_ contract doesn't require the _External Stakers_ to be pre-registered. Each user can decide 44 | which external staker it trusts with their tokens. (We will provide guidance in the UI to only 45 | show "recommended" externals, but do not enforce on the contract level, if someone wants to build their own UI) 46 | 47 | The _vault_ contract enforces the maximum amount a given Creditor can slash to whatever was 48 | agreed when making the lien. 49 | 50 | The _vault_ contract will only release a lien when requested by the creditor. (No auto-release override). 51 | 52 | The _vault_ contract may force-reduce the size of the lien only in the case of slashing by another creditor. 53 | The creditors must be able to handle this case properly. 54 | 55 | The _vault_ contract ensures the user's collateral is sufficient to service all the liens 56 | made on said collateral. 57 | 58 | The _vault_ contract may have a parameter to limit slashable collateral or maximum lien to less than 59 | 100% of the size of the collateral. This makes handling a small slash condition much simpler. 60 | 61 | The _creditor_ is informed of a new lien and may reject it by returning an error 62 | (eg if the slashing percentage is too small, or if the total credit would be too high). 63 | 64 | The _creditor_ may slash the collateral up to the agreed upon amount when it was lent out. 65 | 66 | The _creditor_ should release the lien once the user terminates any agreement with the creditor. 67 | 68 | ## Implementation 69 | 70 | **TODO** translate the below into Rust code. After writing this in text, I realize 71 | it is much less clear than the corresponding code. 72 | 73 | ### State 74 | 75 | * Collateral: `User -> Amount` 76 | * Leins: `User -> {Creditor, Amount, Slashable}[]` 77 | * Credit `Creditor -> Amount` 78 | 79 | ### Invariants 80 | 81 | * `SlashableCollateral(user) <= Collateral(user)` - for all users 82 | * `MaximumLein(user) <= Collateral(user)` - for all users 83 | * `Leins(user).map(|x| x.creditor).isUnique()` - for all users 84 | 85 | ### Transitions 86 | 87 | **Provide Collateral** 88 | 89 | Any user may deposit native tokens to the vault contract, 90 | thus increasing their collateral as stored in this contract. 91 | 92 | **Withdraw Collateral** 93 | 94 | Any user may withdraw any _Free Collateral_ credited to their account. 95 | Their collateral is reduced by this amount and these native tokens are 96 | immediately transferred to their account. 97 | 98 | **Provide Lein** 99 | 100 | TODO. Promise collateral as slashable to some creditor. 101 | Args `(creditor, amount, slashable)`. 102 | This is updated locally 103 | 104 | **Release Lein** 105 | 106 | TODO 107 | 108 | **Slash** 109 | 110 | TODO 111 | 112 | * Increase Slashing(user, creditor)? 113 | 114 | ## Footnotes 115 | 116 | For MVP, Slashable Collateral and Maximum Lein can be up to 100% of total Collateral. 117 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | build: 2 | cargo build 3 | 4 | test: 5 | cargo test 6 | 7 | lint: 8 | cargo +nightly clippy --all-targets -- -D warnings 9 | 10 | gen: build gen-schema gen-typescript 11 | 12 | gen-schema: 13 | ./scripts/schema.sh 14 | 15 | gen-typescript: 16 | git checkout typescript/contracts # Clear out any old or invalid state. 17 | yarn --cwd ./typescript install --frozen-lockfile 18 | yarn --cwd ./typescript build 19 | yarn --cwd ./typescript codegen 20 | 21 | optimize: 22 | cargo install cw-optimizoor || true 23 | cargo cw-optimizoor Cargo.toml 24 | 25 | workspace-optimize: 26 | docker run --rm -v "$(pwd)":/code \ 27 | --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ 28 | --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ 29 | --platform linux/amd64 \ 30 | cosmwasm/workspace-optimizer:0.12.11 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "workspaces": ["typescript", "tests"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/mesh-apis/.cargo/config: -------------------------------------------------------------------------------- 1 | [alias] 2 | unit-test = "test --lib" 3 | schema = "run --example schema" 4 | -------------------------------------------------------------------------------- /packages/mesh-apis/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mesh-apis" 3 | 4 | version = { workspace = true } 5 | edition = { workspace = true } 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | cosmwasm-schema = { workspace = true } 11 | cosmwasm-std = { workspace = true, features = ["ibc3"] } 12 | schemars = { workspace = true } 13 | serde = { workspace = true } 14 | thiserror = { workspace = true } 15 | -------------------------------------------------------------------------------- /packages/mesh-apis/src/claims.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::cw_serde; 2 | use cosmwasm_std::Uint128; 3 | 4 | #[cw_serde] 5 | pub enum ClaimReceiverMsg { 6 | /// This gives the receiver access to slash part up to this much claim 7 | /// Validator specifies where the owner is directing his claim 8 | ReceiveClaim { 9 | owner: String, 10 | amount: Uint128, 11 | validator: String, 12 | }, 13 | } 14 | 15 | #[cw_serde] 16 | pub enum ClaimProviderMsg { 17 | /// This releases a previously received claim without slashing it 18 | ReleaseClaim { 19 | owner: String, 20 | amount: Uint128, 21 | }, 22 | SlashClaim { 23 | owner: String, 24 | amount: Uint128, 25 | }, 26 | } 27 | -------------------------------------------------------------------------------- /packages/mesh-apis/src/consumer_execute.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::cw_serde; 2 | 3 | #[cw_serde] 4 | pub enum ConsumerExecuteMsg { 5 | MeshConsumerRecieveRewardsMsg { validator: String }, 6 | } 7 | -------------------------------------------------------------------------------- /packages/mesh-apis/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod claims; 2 | mod consumer_execute; 3 | mod slash; 4 | mod staking_execute; 5 | 6 | pub use claims::{ClaimProviderMsg, ClaimReceiverMsg}; 7 | pub use consumer_execute::ConsumerExecuteMsg; 8 | pub use slash::SlashMsg; 9 | pub use staking_execute::{StakingExecuteMsg, StakingSudoMsg}; 10 | -------------------------------------------------------------------------------- /packages/mesh-apis/src/slash.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::cw_serde; 2 | use cosmwasm_std::Decimal; 3 | 4 | #[cw_serde] 5 | pub enum SlashMsg { 6 | Slash { 7 | /// which validator to slash 8 | validator: String, 9 | /// what percentage we should slash all stakers 10 | percentage: Decimal, 11 | /// do we forcibly unbond this validator on the provider side, 12 | /// regardless of the behavior of the consumer? 13 | force_unbond: bool, 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /packages/mesh-apis/src/staking_execute.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::cw_serde; 2 | use cosmwasm_std::{Coin, Uint128}; 3 | 4 | #[cw_serde] 5 | pub enum StakingExecuteMsg { 6 | /// This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). 7 | /// `delegator_address` is automatically filled with the current contract's address. 8 | Delegate { 9 | validator: String, 10 | amount: Uint128, 11 | }, 12 | /// This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). 13 | /// `delegator_address` is automatically filled with the current contract's address. 14 | Undelegate { 15 | validator: String, 16 | amount: Uint128, 17 | }, 18 | /// This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). 19 | /// `delegator_address` is automatically filled with the current contract's address. 20 | WithdrawDelegatorReward { 21 | /// The `validator_address` 22 | validator: String, 23 | }, 24 | WithdrawToCostumer { 25 | consumer: String, 26 | validator: String, 27 | }, 28 | /// Use for now, only admin can call - later we can remove if x/gov calls SudoMsg directly 29 | Sudo(StakingSudoMsg), 30 | } 31 | 32 | #[cw_serde] 33 | pub enum StakingSudoMsg { 34 | /// HACK temporary workaround for the proof of concepy. 35 | /// Governance will fund the meta-staking contract with native tokens. 36 | /// In production, this would be accomplished by something like 37 | /// a generic version of the Superfluid staking module which would 38 | /// be in charge of minting and burning synthetic tokens. 39 | /// Update list of consumers 40 | AddConsumer { 41 | consumer_address: String, 42 | funds_available_for_staking: Coin, 43 | }, 44 | RemoveConsumer { 45 | consumer_address: String, 46 | }, 47 | } 48 | -------------------------------------------------------------------------------- /packages/mesh-ibc/.cargo/config: -------------------------------------------------------------------------------- 1 | [alias] 2 | unit-test = "test --lib" 3 | schema = "run --example schema" 4 | -------------------------------------------------------------------------------- /packages/mesh-ibc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mesh-ibc" 3 | 4 | version = { workspace = true } 5 | edition = { workspace = true } 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | cosmwasm-schema = { workspace = true } 11 | cosmwasm-std = { workspace = true } 12 | schemars = { workspace = true } 13 | serde = { workspace = true } 14 | thiserror = { workspace = true } 15 | -------------------------------------------------------------------------------- /packages/mesh-ibc/src/ack.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::cw_serde; 2 | use serde::{de::DeserializeOwned, Serialize}; 3 | 4 | use cosmwasm_std::{from_slice, to_binary, Binary}; 5 | 6 | /// This is a generic ICS acknowledgement format. 7 | /// Proto defined here: https://github.com/cosmos/cosmos-sdk/blob/v0.42.0/proto/ibc/core/channel/v1/channel.proto#L141-L147 8 | /// If ibc_receive_packet returns Err(), then x/wasm runtime will rollback the state and return an error message in this format 9 | #[cw_serde] 10 | pub enum StdAck { 11 | Result(Binary), 12 | Error(String), 13 | } 14 | 15 | impl StdAck { 16 | // create a serialized success message 17 | pub fn success(data: impl Serialize) -> Binary { 18 | let res = to_binary(&data).unwrap(); 19 | StdAck::Result(res).ack() 20 | } 21 | 22 | // create a serialized error message 23 | pub fn fail(err: String) -> Binary { 24 | StdAck::Error(err).ack() 25 | } 26 | 27 | pub fn ack(&self) -> Binary { 28 | to_binary(self).unwrap() 29 | } 30 | 31 | pub fn unwrap(self) -> Binary { 32 | match self { 33 | StdAck::Result(data) => data, 34 | StdAck::Error(err) => panic!("{}", err), 35 | } 36 | } 37 | 38 | pub fn unwrap_into(self) -> T { 39 | from_slice(&self.unwrap()).unwrap() 40 | } 41 | 42 | pub fn unwrap_err(self) -> String { 43 | match self { 44 | StdAck::Result(_) => panic!("not an error"), 45 | StdAck::Error(err) => err, 46 | } 47 | } 48 | 49 | pub fn is_err(&self) -> bool { 50 | matches!(self, StdAck::Error(_)) 51 | } 52 | 53 | pub fn is_ok(&self) -> bool { 54 | matches!(self, StdAck::Result(_)) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/mesh-ibc/src/checks.rs: -------------------------------------------------------------------------------- 1 | pub use crate::{APP_ORDER, IBC_APP_VERSION}; 2 | use cosmwasm_std::IbcOrder; 3 | 4 | use thiserror::Error; 5 | 6 | #[derive(Error, Debug, PartialEq, Eq)] 7 | pub enum MeshSecurityError { 8 | #[error("Only supports unordered channels")] 9 | InvalidChannelOrder, 10 | 11 | #[error("Counterparty version must be '{0}'")] 12 | InvalidChannelVersion(&'static str), 13 | } 14 | 15 | pub fn check_order(order: &IbcOrder) -> Result<(), MeshSecurityError> { 16 | if order != &APP_ORDER { 17 | Err(MeshSecurityError::InvalidChannelOrder) 18 | } else { 19 | Ok(()) 20 | } 21 | } 22 | 23 | pub fn check_version(version: &str) -> Result<(), MeshSecurityError> { 24 | if version != IBC_APP_VERSION { 25 | Err(MeshSecurityError::InvalidChannelVersion(IBC_APP_VERSION)) 26 | } else { 27 | Ok(()) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/mesh-ibc/src/ibc_msg.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::cw_serde; 2 | 3 | use cosmwasm_std::{Coin, Uint128}; 4 | 5 | /// These are messages sent from the provider to the consumer 6 | #[cw_serde] 7 | pub enum ProviderMsg { 8 | /// Returns the current validator set. The first time this is called, it will 9 | /// initiate a stream of `UpdateValidator` messages to be sent through the channel. 10 | ListValidators {}, 11 | Stake { 12 | /// Which validator to stake to 13 | validator: String, 14 | /// How much to stake with this validator 15 | amount: Uint128, 16 | /// A unique key for this request set by the caller, to be used to 17 | /// properly handle ack and timeout messages (not used by consumer) 18 | key: String, 19 | }, 20 | Unstake { 21 | /// Which validator to unstake from 22 | validator: String, 23 | /// How much to unstake from this validator 24 | amount: Uint128, 25 | /// A unique key for this request set by the caller, to be used to 26 | /// properly handle ack and timeout messages (not used by consumer) 27 | key: String, 28 | }, 29 | } 30 | 31 | /// These are messages sent from the consumer to the provider 32 | #[cw_serde] 33 | pub enum ConsumerMsg { 34 | /// Shows a diffs of valid addresses to stake to, based on changes in the active set. 35 | /// Calling ListValidators and adding every UpdateValidators call will gives you the 36 | /// full set of current validators that can be staked to. 37 | UpdateValidators { 38 | added: Vec, 39 | removed: Vec, 40 | }, 41 | Rewards { 42 | validator: String, 43 | total_funds: Coin, 44 | }, 45 | } 46 | 47 | /// List the current validator set. 48 | #[cw_serde] 49 | pub struct ListValidatorsResponse { 50 | pub validators: Vec, 51 | } 52 | 53 | /// TODO: any data we want when incrementing stake 54 | #[cw_serde] 55 | pub struct StakeResponse {} 56 | 57 | /// TODO: any data we want when decrementing stake 58 | #[cw_serde] 59 | pub struct UnstakeResponse {} 60 | 61 | /// This is an event stream, doesn't ever get a response, but we include it here for clarity 62 | #[cw_serde] 63 | pub struct UpdateValidatorsResponse {} 64 | 65 | /// TODO: any data we want after delivering rewards 66 | #[cw_serde] 67 | pub struct RewardsResponse {} 68 | -------------------------------------------------------------------------------- /packages/mesh-ibc/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod ack; 2 | mod checks; 3 | mod ibc_msg; 4 | 5 | use cosmwasm_std::IbcOrder; 6 | 7 | pub use crate::ack::StdAck; 8 | pub use crate::checks::{check_order, check_version, MeshSecurityError}; 9 | pub use crate::ibc_msg::{ 10 | ConsumerMsg, ListValidatorsResponse, ProviderMsg, RewardsResponse, StakeResponse, 11 | UnstakeResponse, UpdateValidatorsResponse, 12 | }; 13 | 14 | pub const IBC_APP_VERSION: &str = "mesh-security-v0.1"; 15 | pub const APP_ORDER: IbcOrder = IbcOrder::Unordered; 16 | // we use this for tests to ensure it is rejected 17 | pub const BAD_APP_ORDER: IbcOrder = IbcOrder::Ordered; 18 | -------------------------------------------------------------------------------- /packages/mesh-testing/.cargo/config: -------------------------------------------------------------------------------- 1 | [alias] 2 | unit-test = "test --lib" 3 | schema = "run --example schema" 4 | -------------------------------------------------------------------------------- /packages/mesh-testing/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mesh-testing" 3 | 4 | version = { workspace = true } 5 | edition = { workspace = true } 6 | 7 | # This crate depends on multi-test and rand. These are not features in 8 | # wasm builds of cosmwasm. Despite this crate only being used as a dev 9 | # dependency, because it is part of the workspace it will always be 10 | # compiled. There is no good way to remove a member from a workspace 11 | # conditionally. As such, we don't compile anything here if we're 12 | # targeting wasm. 13 | # 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 16 | cw-multi-test = { workspace = true } 17 | cosmwasm-schema = { workspace = true } 18 | cosmwasm-std = { workspace = true } 19 | cosmwasm-storage = { workspace = true } 20 | schemars = { workspace = true } 21 | serde = { workspace = true } 22 | thiserror = { workspace = true } 23 | meta-staking = { workspace = true } 24 | mesh-ibc = { workspace = true } 25 | mesh-consumer = { workspace = true } 26 | mesh-provider = { workspace = true } 27 | mesh-slasher = { workspace = true } 28 | -------------------------------------------------------------------------------- /packages/mesh-testing/src/constants.rs: -------------------------------------------------------------------------------- 1 | pub const VALIDATOR: &str = "validator"; 2 | pub const CREATOR_ADDR: &str = "creater_addr"; 3 | pub const DELEGATOR_ADDR: &str = "delegator_addr"; 4 | pub const NATIVE_DENOM: &str = "native_denom"; 5 | 6 | pub const LOCKUP_ADDR: &str = "lockup_address"; 7 | 8 | //IBC 9 | pub const REWARDS_IBC_DENOM: &str = "rewards_ibc_denom"; 10 | pub const CONTRACT_PORT: &str = "wasm.address1"; 11 | pub const REMOTE_PORT: &str = "stars.address1"; 12 | pub const CONNECTION_ID: &str = "connection-1"; 13 | pub const CHANNEL_ID: &str = "channel-1"; 14 | pub const ICS20_CHANNEL_ID: &str = "channel-2"; 15 | 16 | pub const RELAYER_ADDR: &str = "relayer"; 17 | pub const DEFAULT_TIMEOUT: u64 = 60; 18 | -------------------------------------------------------------------------------- /packages/mesh-testing/src/contracts.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::Empty; 2 | use cw_multi_test::{Contract, ContractWrapper}; 3 | 4 | pub fn meta_staking_contract() -> Box> { 5 | let contract = ContractWrapper::new( 6 | meta_staking::contract::execute, 7 | meta_staking::contract::instantiate, 8 | meta_staking::contract::query, 9 | ) 10 | .with_sudo(meta_staking::contract::sudo); 11 | Box::new(contract) 12 | } 13 | 14 | pub fn mesh_consumer_contract() -> Box> { 15 | let contract = ContractWrapper::new( 16 | mesh_consumer::contract::execute, 17 | mesh_consumer::contract::instantiate, 18 | mesh_consumer::contract::query, 19 | ); 20 | Box::new(contract) 21 | } 22 | 23 | pub fn mesh_provider_contract() -> Box> { 24 | let contract = ContractWrapper::new( 25 | mesh_provider::contract::execute, 26 | mesh_provider::contract::instantiate, 27 | mesh_provider::contract::query, 28 | ) 29 | .with_reply(mesh_provider::contract::reply); 30 | Box::new(contract) 31 | } 32 | 33 | pub fn mesh_slasher_contract() -> Box> { 34 | let contract = ContractWrapper::new( 35 | mesh_slasher::contract::execute, 36 | mesh_slasher::contract::instantiate, 37 | mesh_slasher::contract::query, 38 | ); 39 | Box::new(contract) 40 | } 41 | -------------------------------------------------------------------------------- /packages/mesh-testing/src/ibc_helpers.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use cosmwasm_std::{ 4 | from_binary, to_binary, Binary, IbcChannel, IbcEndpoint, IbcOrder, IbcPacket, IbcTimeout, 5 | Timestamp, 6 | }; 7 | use mesh_ibc::StdAck; 8 | use serde::Deserialize; 9 | 10 | use crate::constants::{CHANNEL_ID, CONNECTION_ID, CONTRACT_PORT, DEFAULT_TIMEOUT, REMOTE_PORT}; 11 | 12 | pub fn mock_channel(channel_id: &str, ibc_version: &str) -> IbcChannel { 13 | IbcChannel::new( 14 | IbcEndpoint { 15 | port_id: CONTRACT_PORT.to_string(), 16 | channel_id: channel_id.to_string(), 17 | }, 18 | IbcEndpoint { 19 | port_id: REMOTE_PORT.to_string(), 20 | channel_id: format!("{}0", channel_id), 21 | }, 22 | IbcOrder::Unordered, 23 | ibc_version, 24 | CONNECTION_ID, 25 | ) 26 | } 27 | 28 | pub fn mock_packet(data: Binary) -> IbcPacket { 29 | IbcPacket::new( 30 | data, 31 | IbcEndpoint { 32 | port_id: REMOTE_PORT.to_string(), 33 | channel_id: CHANNEL_ID.to_string(), 34 | }, 35 | IbcEndpoint { 36 | port_id: CONTRACT_PORT.to_string(), 37 | channel_id: CHANNEL_ID.to_string(), 38 | }, 39 | 1, // Packet sequence number. 40 | IbcTimeout::with_timestamp(Timestamp::from_seconds(DEFAULT_TIMEOUT)), 41 | ) 42 | } 43 | 44 | pub fn ack_unwrap Deserialize<'de> + fmt::Debug + PartialEq>(res: Binary) -> R { 45 | from_binary::(&(from_binary::(&res).unwrap()).unwrap()).unwrap() 46 | } 47 | 48 | pub fn to_ack_success(response: T) -> Binary { 49 | to_binary(&StdAck::Result(to_binary(&response).unwrap())).unwrap() 50 | } 51 | 52 | pub fn to_ack_error(response: &str) -> Binary { 53 | to_binary(&StdAck::Error(response.to_string())).unwrap() 54 | } 55 | -------------------------------------------------------------------------------- /packages/mesh-testing/src/instantiates.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use cosmwasm_std::{to_binary, Addr, Decimal}; 4 | use cw_multi_test::{App, Executor}; 5 | 6 | use crate::{ 7 | addr, 8 | constants::{CONNECTION_ID, CREATOR_ADDR, LOCKUP_ADDR, REWARDS_IBC_DENOM}, 9 | contracts::{ 10 | mesh_consumer_contract, mesh_provider_contract, mesh_slasher_contract, 11 | meta_staking_contract, 12 | }, 13 | }; 14 | 15 | use mesh_provider::msg::InstantiateMsg as ProviderInit; 16 | use mesh_slasher::msg::InstantiateMsg as SlasherInit; 17 | 18 | pub fn get_mesh_slasher_init_msg() -> SlasherInit { 19 | SlasherInit { 20 | owner: CREATOR_ADDR.to_string(), 21 | } 22 | } 23 | 24 | pub fn get_mesh_provider_init_msg(slasher_code_id: u64, lockup_addr: Option<&str>) -> ProviderInit { 25 | let lockup_addr = lockup_addr.unwrap_or(LOCKUP_ADDR); 26 | 27 | ProviderInit { 28 | consumer: mesh_provider::msg::ConsumerInfo { 29 | connection_id: CONNECTION_ID.to_string(), 30 | }, 31 | slasher: mesh_provider::msg::SlasherInfo { 32 | code_id: slasher_code_id, 33 | msg: to_binary(&mesh_slasher::msg::InstantiateMsg { 34 | owner: CREATOR_ADDR.to_string(), 35 | }) 36 | .unwrap(), 37 | }, 38 | lockup: lockup_addr.to_string(), 39 | unbonding_period: 86400 * 14, 40 | rewards_ibc_denom: REWARDS_IBC_DENOM.to_string(), 41 | packet_lifetime: None, 42 | } 43 | } 44 | 45 | pub fn instantiate_mesh_provider( 46 | app: &mut App, 47 | init_msg: Option, 48 | ) -> Addr { 49 | let mesh_provider_id = app.store_code(mesh_provider_contract()); 50 | let mesh_slasher_id = app.store_code(mesh_slasher_contract()); 51 | let init_msg = init_msg.unwrap_or_else(|| get_mesh_provider_init_msg(mesh_slasher_id, None)); 52 | 53 | app.instantiate_contract( 54 | mesh_provider_id, 55 | addr!(CREATOR_ADDR), 56 | &init_msg, 57 | &[], 58 | "mesh-provider", 59 | Some(CREATOR_ADDR.to_string()), 60 | ) 61 | .unwrap() 62 | } 63 | 64 | pub fn instantiate_meta_staking( 65 | app: &mut App, 66 | init_msg: Option, 67 | ) -> Addr { 68 | let meta_staking_id = app.store_code(meta_staking_contract()); 69 | let init_msg = init_msg.unwrap_or(meta_staking::msg::InstantiateMsg {}); 70 | 71 | app.instantiate_contract( 72 | meta_staking_id, 73 | addr!(CREATOR_ADDR), 74 | &init_msg, 75 | &[], 76 | "meta-staking", 77 | Some(CREATOR_ADDR.to_string()), 78 | ) 79 | .unwrap() 80 | } 81 | 82 | pub fn instantiate_mesh_consumer( 83 | app: &mut App, 84 | init_msg: Option, 85 | meta_staking_addr: Option, 86 | ) -> Addr { 87 | let mesh_consumer_id = app.store_code(mesh_consumer_contract()); 88 | let init_msg = init_msg.unwrap_or(mesh_consumer::msg::InstantiateMsg { 89 | provider: mesh_consumer::msg::ProviderInfo { 90 | port_id: "some_port".to_string(), 91 | connection_id: "come_connection".to_string(), 92 | }, 93 | remote_to_local_exchange_rate: Decimal::from_str("0.1").unwrap(), 94 | meta_staking_contract_address: meta_staking_addr.unwrap().to_string(), 95 | ics20_channel: "some_channel".to_string(), 96 | packet_lifetime: None, 97 | }); 98 | 99 | app.instantiate_contract( 100 | mesh_consumer_id, 101 | addr!(CREATOR_ADDR), 102 | &init_msg, 103 | &[], 104 | "mesh-consumer", 105 | Some(CREATOR_ADDR.to_string()), 106 | ) 107 | .unwrap() 108 | } 109 | -------------------------------------------------------------------------------- /packages/mesh-testing/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(target_arch = "wasm32"))] 2 | pub mod contracts; 3 | 4 | #[cfg(not(target_arch = "wasm32"))] 5 | pub mod instantiates; 6 | 7 | #[cfg(not(target_arch = "wasm32"))] 8 | pub mod constants; 9 | 10 | #[cfg(not(target_arch = "wasm32"))] 11 | pub mod macros; 12 | 13 | #[cfg(not(target_arch = "wasm32"))] 14 | pub mod msgs; 15 | 16 | pub mod ibc_helpers; 17 | pub mod multitest_helpers; 18 | -------------------------------------------------------------------------------- /packages/mesh-testing/src/macros.rs: -------------------------------------------------------------------------------- 1 | /// Assert the error is the error we 2 | /// 3 | /// Result === ContractError::Example{} 4 | #[macro_export] 5 | macro_rules! assert_error { 6 | ($x:expr, $e:expr) => { 7 | assert_eq!($x.unwrap_err().root_cause().to_string(), $e.to_string()) 8 | }; 9 | } 10 | 11 | pub use assert_error; 12 | 13 | // Shorthand for an unchecked address. 14 | #[macro_export] 15 | macro_rules! addr { 16 | ($x:expr ) => { 17 | Addr::unchecked($x) 18 | }; 19 | } 20 | 21 | pub use addr; 22 | -------------------------------------------------------------------------------- /packages/mesh-testing/src/msgs.rs: -------------------------------------------------------------------------------- 1 | pub use mesh_slasher::msg::{ConfigResponse as SlasherConfigResponse, QueryMsg as SlasherQueryMsg}; 2 | -------------------------------------------------------------------------------- /packages/mesh-testing/src/multitest_helpers.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_storage::PrefixedStorage; 2 | use cw_multi_test::App; 3 | 4 | /// Helps you add stuff to storage in your multitest tests 5 | pub fn update_storage( 6 | app: &mut App, 7 | address: &[u8], 8 | function: &mut dyn FnMut(&mut PrefixedStorage), 9 | ) { 10 | app.init_modules(|_, _, storage| { 11 | let mut namespace = b"contract_data/".to_vec(); 12 | namespace.extend_from_slice(address); 13 | 14 | let mut prefixed_storage = PrefixedStorage::multilevel(storage, &[b"wasm", &namespace]); 15 | 16 | function(&mut prefixed_storage); 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /scripts/build_integration_wasm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit -o nounset -o pipefail 3 | command -v shellcheck >/dev/null && shellcheck "$0" 4 | 5 | # This file is used for local testing. Once you update Rust files, run this to prepare them for the 6 | # integration tests. Then you can run the integration tests. 7 | 8 | # go to root dir regardless of where it was run 9 | SCRIPT_DIR="$(realpath "$(dirname "$0")")" 10 | cd "${SCRIPT_DIR}/.." 11 | 12 | # compile all contracts 13 | for C in ./contracts/*/ 14 | do 15 | echo "Compiling $(basename "$C")..." 16 | (cd "$C" && RUSTFLAGS='-C link-arg=-s' cargo build --lib --release --target wasm32-unknown-unknown --locked) 17 | done 18 | 19 | # move them to the internal dir inside tests 20 | mkdir -p ./tests/internal 21 | cp ./target/wasm32-unknown-unknown/release/*.wasm ./tests/internal 22 | 23 | ls -l ./tests/internal 24 | -------------------------------------------------------------------------------- /scripts/create_clients.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit -o nounset -o pipefail 3 | command -v shellcheck >/dev/null && shellcheck "$0" 4 | 5 | SCRIPT_DIR="$(realpath "$(dirname "$0")")" 6 | cd "${SCRIPT_DIR}/../tests" 7 | 8 | #Create client for mesh-consumer 9 | cosmwasm-ts-codegen generate \ 10 | --plugin client \ 11 | --schema ls ../contracts/mesh-consumer/schema \ 12 | --out ./src/bindings \ 13 | --name MeshConsumer \ 14 | --no-bundle 15 | 16 | #Create client for mesh-lockup 17 | cosmwasm-ts-codegen generate \ 18 | --plugin client \ 19 | --schema ls ../contracts/mesh-lockup/schema \ 20 | --out ./src/bindings \ 21 | --name MeshLockup \ 22 | --no-bundle 23 | 24 | #Create client for mesh-provider 25 | cosmwasm-ts-codegen generate \ 26 | --plugin client \ 27 | --schema ls ../contracts/mesh-provider/schema \ 28 | --out ./src/bindings \ 29 | --name MeshProvider \ 30 | --no-bundle 31 | 32 | #Create client for mesh-slasher 33 | cosmwasm-ts-codegen generate \ 34 | --plugin client \ 35 | --schema ls ../contracts/mesh-slasher/schema \ 36 | --out ./src/bindings \ 37 | --name MeshSlasher \ 38 | --no-bundle 39 | 40 | #Create client for meeta-staking 41 | cosmwasm-ts-codegen generate \ 42 | --plugin client \ 43 | --schema ls ../contracts/meta-staking/schema \ 44 | --out ./src/bindings \ 45 | --name MetaStaking \ 46 | --no-bundle 47 | -------------------------------------------------------------------------------- /scripts/format_md.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit -o nounset -o pipefail 3 | command -v shellcheck >/dev/null && shellcheck "$0" 4 | 5 | # Running with -c makes the script only validate instead of editing in place. 6 | op="write" 7 | while getopts c option; do 8 | case "${option}" in 9 | c) op="check" ;; 10 | *) ;; 11 | esac 12 | done 13 | 14 | npx prettier@2.2.1 --$op "./**/*.md" 15 | -------------------------------------------------------------------------------- /scripts/format_sh.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit -o nounset -o pipefail 3 | command -v shellcheck >/dev/null && shellcheck "$0" 4 | 5 | # Running with -c makes the script only validate instead of editing in place. 6 | op="w" 7 | while getopts c option; do 8 | case "${option}" in 9 | c) op="d" ;; 10 | *) ;; 11 | esac 12 | done 13 | 14 | shfmt -$op devtools packages 15 | -------------------------------------------------------------------------------- /scripts/format_yml.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit -o nounset -o pipefail 3 | command -v shellcheck >/dev/null && shellcheck "$0" 4 | 5 | # Running with -c makes the script only validate instead of editing in place. 6 | op="write" 7 | while getopts c option; do 8 | case "${option}" in 9 | c) op="check" ;; 10 | *) ;; 11 | esac 12 | done 13 | 14 | npx prettier@2.2.1 --$op "./**/*.yml" 15 | -------------------------------------------------------------------------------- /scripts/optimize.sh: -------------------------------------------------------------------------------- 1 | docker run --rm -v "$(pwd)":/code \ 2 | --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ 3 | --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ 4 | cosmwasm/workspace-optimizer:0.12.9 5 | -------------------------------------------------------------------------------- /scripts/schema.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | START_DIR=$(pwd) 4 | 5 | # ${f <-- from variable f 6 | # ## <-- greedy front trim 7 | # * <-- matches anything 8 | # / <-- until the last '/' 9 | # } 10 | # 11 | 12 | echo "generating schemas mesh-security" 13 | 14 | for f in ./contracts/* 15 | do 16 | echo "generating schema for ${f##*/}" 17 | cd "$f" 18 | CMD="cargo run --bin schema" 19 | eval $CMD > /dev/null 20 | rm -rf ./schema/raw 21 | cd "$START_DIR" 22 | done 23 | -------------------------------------------------------------------------------- /tests/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { "project": "./tsconfig.json" }, 5 | "env": { "es6": true }, 6 | "ignorePatterns": ["node_modules", "build", "coverage"], 7 | "plugins": ["import", "eslint-comments"], 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:eslint-comments/recommended", 11 | "plugin:@typescript-eslint/recommended", 12 | "plugin:import/typescript", 13 | "prettier" 14 | ], 15 | "globals": { "BigInt": true, "console": true, "WebAssembly": true }, 16 | "rules": { 17 | "@typescript-eslint/no-explicit-any": "off", 18 | "@typescript-eslint/no-empty-interface": "off", 19 | "@typescript-eslint/no-empty-pattern": "off", 20 | "@typescript-eslint/explicit-module-boundary-types": "off", 21 | "eslint-comments/disable-enable-pair": [ 22 | "error", 23 | { "allowWholeFile": true } 24 | ], 25 | "eslint-comments/no-unused-disable": "error", 26 | "import/order": [ 27 | "error", 28 | { "newlines-between": "always", "alphabetize": { "order": "asc" } } 29 | ], 30 | "sort-imports": [ 31 | "error", 32 | { "ignoreDeclarationSort": true, "ignoreCase": true } 33 | ], 34 | "@typescript-eslint/no-unused-vars": ["off", { "argsIgnorePattern": "^_" }], 35 | "object-shorthand": ["error", "always"], 36 | "@typescript-eslint/ban-types": [ 37 | "error", 38 | { 39 | "types": { 40 | // un-ban a type that's banned by default 41 | "{}": false 42 | } 43 | } 44 | ] 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | build/ 10 | 11 | # Diagnostic reports (https://nodejs.org/api/report.html) 12 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 13 | 14 | # Runtime data 15 | pids 16 | *.pid 17 | *.seed 18 | *.pid.lock 19 | 20 | # Directory for instrumented libs generated by jscoverage/JSCover 21 | lib-cov 22 | 23 | # Coverage directory used by tools like istanbul 24 | coverage 25 | *.lcov 26 | 27 | # nyc test coverage 28 | .nyc_output 29 | 30 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 31 | .grunt 32 | 33 | # Bower dependency directory (https://bower.io/) 34 | bower_components 35 | 36 | # node-waf configuration 37 | .lock-wscript 38 | 39 | # Compiled binary addons (https://nodejs.org/api/addons.html) 40 | build/Release 41 | 42 | # Dependency directories 43 | node_modules/ 44 | jspm_packages/ 45 | 46 | # TypeScript v1 declaration files 47 | typings/ 48 | 49 | # TypeScript cache 50 | *.tsbuildinfo 51 | 52 | # Optional npm cache directory 53 | .npm 54 | 55 | # Optional eslint cache 56 | .eslintcache 57 | 58 | # Microbundle cache 59 | .rpt2_cache/ 60 | .rts2_cache_cjs/ 61 | .rts2_cache_es/ 62 | .rts2_cache_umd/ 63 | 64 | # Optional REPL history 65 | .node_repl_history 66 | 67 | # Output of 'npm pack' 68 | *.tgz 69 | 70 | # Yarn Integrity file 71 | .yarn-integrity 72 | 73 | # dotenv environment variables file 74 | .env 75 | .env.test 76 | 77 | # parcel-bundler cache (https://parceljs.org/) 78 | .cache 79 | 80 | # Next.js build output 81 | .next 82 | 83 | # Nuxt.js build / generate output 84 | .nuxt 85 | dist 86 | 87 | # Gatsby files 88 | .cache/ 89 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 90 | # https://nextjs.org/blog/next-9-1#public-directory-support 91 | # public 92 | 93 | # vuepress build output 94 | .vuepress/dist 95 | 96 | # Serverless directories 97 | .serverless/ 98 | 99 | # FuseBox cache 100 | .fusebox/ 101 | 102 | # DynamoDB Local files 103 | .dynamodb/ 104 | 105 | # TernJS port file 106 | .tern-port 107 | -------------------------------------------------------------------------------- /tests/.prettierignore: -------------------------------------------------------------------------------- 1 | # package.json is formatted by package managers, so we ignore it here 2 | package.json 3 | build 4 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # Mesh Security IBC tests 2 | 3 | These tests leverage `ts-relayer` to test mesh-security contract interactions between two real blockchain binaries. 4 | 5 | ## Setup 6 | 7 | Ensure you have node 14+ (16+ recommended): 8 | 9 | ``` 10 | node --version 11 | ``` 12 | 13 | Then install via yarn or npm as typical: 14 | 15 | ``` 16 | yarn 17 | ``` 18 | 19 | ## Testing 20 | 21 | ### Build optimized contract WASM 22 | 23 | Compile the contracts for uploading. 24 | 25 | ```sh 26 | ./scripts/build_integration_wasm.sh 27 | ``` 28 | 29 | **NOTE: you need to run this each time your contract changes.** 30 | 31 | ### Run two chains in docker 32 | 33 | Start `wasmd` and `osmosisd` chains using docker compose: 34 | 35 | ```bash 36 | docker-compose up 37 | ``` 38 | 39 | ### Run tests 40 | 41 | In a separate terminal: 42 | 43 | ``` 44 | yarn test 45 | ``` 46 | 47 | ## Development 48 | 49 | Clean up test code with prettier and eslint: 50 | 51 | ``` 52 | yarn fix 53 | ``` 54 | -------------------------------------------------------------------------------- /tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mesh-security-tests", 3 | "version": "0.2.0", 4 | "description": "Integration tests and deploy scripts for mesh-security", 5 | "browserslist": [ 6 | "maintained node versions" 7 | ], 8 | "author": "Ethan Frey ", 9 | "license": "Apache-2.0", 10 | "private": false, 11 | "scripts": { 12 | "build": "tsc -p tsconfig.json", 13 | "fix": "run-s fix:*", 14 | "fix:prettier": "prettier \"**/*.{ts,md}\" --write", 15 | "fix:lint": "eslint src --ext .ts --fix", 16 | "test": "run-s build test:*", 17 | "test:lint": "eslint src --ext .ts", 18 | "test:prettier": "prettier \"**/*.{ts,md}\" --list-different", 19 | "test:unit": "nyc --silent ava --serial", 20 | "deploy": "run-s build deploy:*", 21 | "deploy:contracts": "node build/scripts/deploy-contracts.js" 22 | }, 23 | "dependencies": { 24 | "@confio/relayer": "^0.7.0", 25 | "@ledgerhq/hw-transport": "^5.51.1" 26 | }, 27 | "devDependencies": { 28 | "@ava/typescript": "^3.0.1", 29 | "@istanbuljs/nyc-config-typescript": "^1.0.2", 30 | "@types/ledgerhq__hw-transport": "^4.21.4", 31 | "@types/node": "^18.0.6", 32 | "@types/sinon": "^10.0.13", 33 | "@typescript-eslint/eslint-plugin": "^5.30.7", 34 | "@typescript-eslint/parser": "^5.30.7", 35 | "ava": "^4.3.1", 36 | "eslint": "^8.20.0", 37 | "eslint-config-prettier": "^8.5.0", 38 | "eslint-plugin-eslint-comments": "^3.2.0", 39 | "eslint-plugin-import": "^2.26.0", 40 | "npm-run-all": "^4.1.5", 41 | "nyc": "^15.1.0", 42 | "prettier": "^2.7.1", 43 | "sinon": "^14.0.0", 44 | "ts-node": "^10.9.1", 45 | "typescript": "^4.7.4" 46 | }, 47 | "ava": { 48 | "failFast": true, 49 | "timeout": "120s", 50 | "extensions": [ 51 | "ts", 52 | "js", 53 | "cjs", 54 | "mjs" 55 | ], 56 | "require": [ 57 | "ts-node/register" 58 | ] 59 | }, 60 | "nyc": { 61 | "extends": "@istanbuljs/nyc-config-typescript", 62 | "exclude": [ 63 | "**/*.spec.js" 64 | ] 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/src/scripts/config.ts: -------------------------------------------------------------------------------- 1 | export const osmoConfig = { 2 | meshLockupAddr: "osmo1kf88a6ftgt57t2hg48jfc9679l50tq6jqwsdheuld6467jls2hxstnvnal", 3 | meshProviderAddr: "osmo19kx57u0nrhqas34qvyhu2ydt4a9rlj6n37l88jqwfc2eesuk9tuq6c6ywj", 4 | meshProviderPort: "wasm.osmo19kx57u0nrhqas34qvyhu2ydt4a9rlj6n37l88jqwfc2eesuk9tuq6c6ywj", 5 | meshSlasherAddr: "osmo1fewayl23e89rkejx932ljxf0ylsmtqc49r4myzzsvxcpweptmu0szshpkv", 6 | }; 7 | 8 | export const junoConfig = { 9 | metaStakingAddr: "juno15tuw2fdyl666j7569ynt9dpdqykwzqw4d4sxrf0smcezudeafedsqu7n97", 10 | meshConsumerAddr: "juno12e2p5rgcmcgu2t63mwystxzlfwf50kpgn9gjlnz2sf6q76pnkcjqrna22u", 11 | meshConsumerPort: "wasm.juno12e2p5rgcmcgu2t63mwystxzlfwf50kpgn9gjlnz2sf6q76pnkcjqrna22u", 12 | }; 13 | -------------------------------------------------------------------------------- /tests/src/scripts/helpers.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from "fs"; 2 | import { env } from "process"; 3 | 4 | import { makeCosmoshubPath } from "@cosmjs/amino"; 5 | import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate"; 6 | import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing"; 7 | 8 | import { Network } from "./networks"; 9 | 10 | export const pprint = (x: unknown) => console.log(JSON.stringify(x, undefined, 2)); 11 | 12 | // Check "MNEMONIC" env variable and ensure it is set to a reasonable value 13 | export function getMnemonic(): string { 14 | const mnemonic = env["MNEMONIC"]; 15 | if (!mnemonic || mnemonic.length < 48) { 16 | throw new Error("Must set MNEMONIC to a 12 word phrase"); 17 | } 18 | return mnemonic; 19 | } 20 | 21 | export async function connect(mnemonic: string, network: Network) { 22 | const { prefix, gasPrice, feeToken, rpcEndpoint } = network; 23 | const hdPath = makeCosmoshubPath(0); 24 | 25 | // Setup signer 26 | const offlineSigner = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, { prefix, hdPaths: [hdPath] }); 27 | const { address } = (await offlineSigner.getAccounts())[0]; 28 | console.log(`Connected to ${address}`); 29 | 30 | // Init SigningCosmWasmClient client 31 | const client = await SigningCosmWasmClient.connectWithSigner(rpcEndpoint, offlineSigner, { 32 | prefix, 33 | gasPrice, 34 | }); 35 | const balance = await client.getBalance(address, feeToken); 36 | console.log(`Balance: ${balance.amount} ${balance.denom}`); 37 | 38 | const chainId = await client.getChainId(); 39 | 40 | if (chainId !== network.chainId) { 41 | throw Error("Given ChainId doesn't match the clients ChainID!"); 42 | } 43 | 44 | return { client, address }; 45 | } 46 | 47 | export async function setupContracts( 48 | client: SigningCosmWasmClient, 49 | signer: string, 50 | contracts: Record 51 | ): Promise> { 52 | const results: Record = {}; 53 | 54 | for (const name in contracts) { 55 | const path = contracts[name]; 56 | console.info(`Storing ${name} from ${path}...`); 57 | const wasm = await readFileSync(path); 58 | const receipt = await client.upload(signer, wasm, "auto", `Upload ${name}`); 59 | console.debug(`Upload ${name} with CodeID: ${receipt.codeId}`); 60 | results[name] = receipt.codeId; 61 | } 62 | 63 | return results; 64 | } 65 | -------------------------------------------------------------------------------- /tests/src/scripts/networks.ts: -------------------------------------------------------------------------------- 1 | // data from https://github.com/cosmos/chain-registry/tree/master/testnets 2 | import { GasPrice } from "@cosmjs/stargate"; 3 | 4 | export interface Network { 5 | chainId: string; 6 | rpcEndpoint: string; 7 | prefix: string; 8 | gasPrice: GasPrice; 9 | feeToken: string; 10 | } 11 | 12 | export const junoTestConfig: Network = { 13 | chainId: "uni-5", 14 | rpcEndpoint: "https://juno-testnet-rpc.polkachu.com:443", 15 | prefix: "juno", 16 | gasPrice: GasPrice.fromString("0.05ujunox"), 17 | feeToken: "ujunox", 18 | }; 19 | 20 | export const osmoTestConfig: Network = { 21 | chainId: "osmo-test-4", 22 | rpcEndpoint: "https://osmosis-testnet-rpc.allthatnode.com:26657", 23 | prefix: "osmo", 24 | gasPrice: GasPrice.fromString("0.025uosmo"), 25 | feeToken: "uosmo", 26 | }; 27 | 28 | export const starTestConfig: Network = { 29 | chainId: "elfagar-1", 30 | rpcEndpoint: "https://rpc.elgafar-1.stargaze-apis.com", 31 | prefix: "stars", 32 | gasPrice: GasPrice.fromString("0.04ustars"), 33 | feeToken: "ustars", 34 | }; 35 | 36 | // map from (chainId, chainId) to live existing connection 37 | export const connections = { 38 | [junoTestConfig.chainId]: { 39 | [osmoTestConfig.chainId]: "connection-24", 40 | }, 41 | [osmoTestConfig.chainId]: { 42 | [junoTestConfig.chainId]: "connection-2211", 43 | }, 44 | }; 45 | -------------------------------------------------------------------------------- /tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "lib": ["es2020"], 6 | "moduleResolution": "node", 7 | "incremental": true, 8 | "tsBuildInfoFile": "build/.tsbuildinfo", 9 | "inlineSourceMap": true, 10 | "declaration": true, 11 | "outDir": "build", 12 | "rootDir": "src", 13 | "strict": true, 14 | "noFallthroughCasesInSwitch": true, 15 | "noImplicitReturns": true, 16 | "noUnusedLocals": false, 17 | "noUnusedParameters": true, 18 | "esModuleInterop": true, 19 | "resolveJsonModule": true, 20 | "pretty": true, 21 | }, 22 | "include": ["src/**/*.ts"] 23 | } 24 | -------------------------------------------------------------------------------- /typescript/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | 3 | 4 | # Created by https://www.toptal.com/developers/gitignore/api/node,macos 5 | # Edit at https://www.toptal.com/developers/gitignore?templates=node,macos 6 | 7 | ### macOS ### 8 | # General 9 | .DS_Store 10 | .AppleDouble 11 | .LSOverride 12 | 13 | # Icon must end with two \r 14 | Icon 15 | 16 | # Thumbnails 17 | ._* 18 | 19 | # Files that might appear in the root of a volume 20 | .DocumentRevisions-V100 21 | .fseventsd 22 | .Spotlight-V100 23 | .TemporaryItems 24 | .Trashes 25 | .VolumeIcon.icns 26 | .com.apple.timemachine.donotpresent 27 | 28 | # Directories potentially created on remote AFP share 29 | .AppleDB 30 | .AppleDesktop 31 | Network Trash Folder 32 | Temporary Items 33 | .apdisk 34 | 35 | ### Node ### 36 | # Logs 37 | logs 38 | *.log 39 | npm-debug.log* 40 | yarn-debug.log* 41 | yarn-error.log* 42 | lerna-debug.log* 43 | .pnpm-debug.log* 44 | 45 | # Diagnostic reports (https://nodejs.org/api/report.html) 46 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 47 | 48 | # Runtime data 49 | pids 50 | *.pid 51 | *.seed 52 | *.pid.lock 53 | 54 | # Directory for instrumented libs generated by jscoverage/JSCover 55 | lib-cov 56 | 57 | # Coverage directory used by tools like istanbul 58 | coverage 59 | *.lcov 60 | 61 | # nyc test coverage 62 | .nyc_output 63 | 64 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 65 | .grunt 66 | 67 | # Bower dependency directory (https://bower.io/) 68 | bower_components 69 | 70 | # node-waf configuration 71 | .lock-wscript 72 | 73 | # Compiled binary addons (https://nodejs.org/api/addons.html) 74 | build/Release 75 | 76 | # Dependency directories 77 | node_modules/ 78 | jspm_packages/ 79 | 80 | # Snowpack dependency directory (https://snowpack.dev/) 81 | web_modules/ 82 | 83 | # TypeScript cache 84 | *.tsbuildinfo 85 | 86 | # Optional npm cache directory 87 | .npm 88 | 89 | # Optional eslint cache 90 | .eslintcache 91 | 92 | # Microbundle cache 93 | .rpt2_cache/ 94 | .rts2_cache_cjs/ 95 | .rts2_cache_es/ 96 | .rts2_cache_umd/ 97 | 98 | # Optional REPL history 99 | .node_repl_history 100 | 101 | # Output of 'npm pack' 102 | *.tgz 103 | 104 | # Yarn Integrity file 105 | .yarn-integrity 106 | 107 | # dotenv environment variables file 108 | .env 109 | .env.test 110 | .env.production 111 | 112 | # parcel-bundler cache (https://parceljs.org/) 113 | .cache 114 | .parcel-cache 115 | 116 | # Next.js build output 117 | .next 118 | out 119 | 120 | # Nuxt.js build / generate output 121 | .nuxt 122 | dist 123 | 124 | # Gatsby files 125 | .cache/ 126 | # Comment in the public line in if your project uses Gatsby and not Next.js 127 | # https://nextjs.org/blog/next-9-1#public-directory-support 128 | # public 129 | 130 | # vuepress build output 131 | .vuepress/dist 132 | 133 | # Serverless directories 134 | .serverless/ 135 | 136 | # FuseBox cache 137 | .fusebox/ 138 | 139 | # DynamoDB Local files 140 | .dynamodb/ 141 | 142 | # TernJS port file 143 | .tern-port 144 | 145 | # Stores VSCode versions used for testing VSCode extensions 146 | .vscode-test 147 | 148 | # yarn v2 149 | .yarn/cache 150 | .yarn/unplugged 151 | .yarn/build-state.yml 152 | .yarn/install-state.gz 153 | .pnp.* 154 | 155 | ### Node Patch ### 156 | # Serverless Webpack directories 157 | .webpack/ 158 | 159 | # Optional stylelint cache 160 | .stylelintcache 161 | 162 | # SvelteKit build / generate output 163 | .svelte-kit 164 | 165 | # End of https://www.toptal.com/developers/gitignore/api/node,macos 166 | -------------------------------------------------------------------------------- /typescript/contracts/mesh-consumer/MeshConsumer.client.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. 3 | * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, 4 | * and run the @cosmwasm/ts-codegen generate command to regenerate this file. 5 | */ 6 | 7 | import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from "@cosmjs/cosmwasm-stargate"; 8 | import { Coin, StdFee } from "@cosmjs/amino"; 9 | import { Decimal, InstantiateMsg, ProviderInfo, ExecuteMsg, QueryMsg, Addr, Config } from "./MeshConsumer.types"; 10 | export interface MeshConsumerReadOnlyInterface { 11 | contractAddress: string; 12 | config: () => Promise; 13 | } 14 | export class MeshConsumerQueryClient implements MeshConsumerReadOnlyInterface { 15 | client: CosmWasmClient; 16 | contractAddress: string; 17 | 18 | constructor(client: CosmWasmClient, contractAddress: string) { 19 | this.client = client; 20 | this.contractAddress = contractAddress; 21 | this.config = this.config.bind(this); 22 | } 23 | 24 | config = async (): Promise => { 25 | return this.client.queryContractSmart(this.contractAddress, { 26 | config: {} 27 | }); 28 | }; 29 | } 30 | export interface MeshConsumerInterface extends MeshConsumerReadOnlyInterface { 31 | contractAddress: string; 32 | sender: string; 33 | meshConsumerRecieveRewardsMsg: ({ 34 | validator 35 | }: { 36 | validator: string; 37 | }, fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]) => Promise; 38 | } 39 | export class MeshConsumerClient extends MeshConsumerQueryClient implements MeshConsumerInterface { 40 | client: SigningCosmWasmClient; 41 | sender: string; 42 | contractAddress: string; 43 | 44 | constructor(client: SigningCosmWasmClient, sender: string, contractAddress: string) { 45 | super(client, contractAddress); 46 | this.client = client; 47 | this.sender = sender; 48 | this.contractAddress = contractAddress; 49 | this.meshConsumerRecieveRewardsMsg = this.meshConsumerRecieveRewardsMsg.bind(this); 50 | } 51 | 52 | meshConsumerRecieveRewardsMsg = async ({ 53 | validator 54 | }: { 55 | validator: string; 56 | }, fee: number | StdFee | "auto" = "auto", memo?: string, funds?: Coin[]): Promise => { 57 | return await this.client.execute(this.sender, this.contractAddress, { 58 | mesh_consumer_recieve_rewards_msg: { 59 | validator 60 | } 61 | }, fee, memo, funds); 62 | }; 63 | } -------------------------------------------------------------------------------- /typescript/contracts/mesh-consumer/MeshConsumer.types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. 3 | * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, 4 | * and run the @cosmwasm/ts-codegen generate command to regenerate this file. 5 | */ 6 | 7 | export type Decimal = string; 8 | export interface InstantiateMsg { 9 | ics20_channel: string; 10 | meta_staking_contract_address: string; 11 | packet_lifetime?: number | null; 12 | provider: ProviderInfo; 13 | remote_to_local_exchange_rate: Decimal; 14 | } 15 | export interface ProviderInfo { 16 | connection_id: string; 17 | port_id: string; 18 | } 19 | export type ExecuteMsg = { 20 | mesh_consumer_recieve_rewards_msg: { 21 | validator: string; 22 | }; 23 | }; 24 | export type QueryMsg = { 25 | config: {}; 26 | }; 27 | export type Addr = string; 28 | export interface Config { 29 | ics20_channel: string; 30 | meta_staking_contract_address: Addr; 31 | provider: ProviderInfo; 32 | remote_to_local_exchange_rate: Decimal; 33 | } -------------------------------------------------------------------------------- /typescript/contracts/mesh-consumer/bundle.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. 3 | * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, 4 | * and run the @cosmwasm/ts-codegen generate command to regenerate this file. 5 | */ 6 | 7 | import * as _0 from "./MeshConsumer.types"; 8 | import * as _1 from "./MeshConsumer.client"; 9 | export namespace contracts { 10 | export const MeshConsumer = { ..._0, 11 | ..._1 12 | }; 13 | } -------------------------------------------------------------------------------- /typescript/contracts/mesh-lockup/MeshLockup.client.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. 3 | * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, 4 | * and run the @cosmwasm/ts-codegen generate command to regenerate this file. 5 | */ 6 | 7 | import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from "@cosmjs/cosmwasm-stargate"; 8 | import { Coin, StdFee } from "@cosmjs/amino"; 9 | import { InstantiateMsg, ExecuteMsg, Uint128, QueryMsg, BalanceResponse, Lein } from "./MeshLockup.types"; 10 | export interface MeshLockupReadOnlyInterface { 11 | contractAddress: string; 12 | balance: ({ 13 | account 14 | }: { 15 | account: string; 16 | }) => Promise; 17 | } 18 | export class MeshLockupQueryClient implements MeshLockupReadOnlyInterface { 19 | client: CosmWasmClient; 20 | contractAddress: string; 21 | 22 | constructor(client: CosmWasmClient, contractAddress: string) { 23 | this.client = client; 24 | this.contractAddress = contractAddress; 25 | this.balance = this.balance.bind(this); 26 | } 27 | 28 | balance = async ({ 29 | account 30 | }: { 31 | account: string; 32 | }): Promise => { 33 | return this.client.queryContractSmart(this.contractAddress, { 34 | balance: { 35 | account 36 | } 37 | }); 38 | }; 39 | } 40 | export interface MeshLockupInterface extends MeshLockupReadOnlyInterface { 41 | contractAddress: string; 42 | sender: string; 43 | bond: (fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]) => Promise; 44 | unbond: ({ 45 | amount 46 | }: { 47 | amount: Uint128; 48 | }, fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]) => Promise; 49 | grantClaim: ({ 50 | amount, 51 | leinholder, 52 | validator 53 | }: { 54 | amount: Uint128; 55 | leinholder: string; 56 | validator: string; 57 | }, fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]) => Promise; 58 | releaseClaim: ({ 59 | amount, 60 | owner 61 | }: { 62 | amount: Uint128; 63 | owner: string; 64 | }, fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]) => Promise; 65 | slashClaim: ({ 66 | amount, 67 | owner 68 | }: { 69 | amount: Uint128; 70 | owner: string; 71 | }, fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]) => Promise; 72 | } 73 | export class MeshLockupClient extends MeshLockupQueryClient implements MeshLockupInterface { 74 | client: SigningCosmWasmClient; 75 | sender: string; 76 | contractAddress: string; 77 | 78 | constructor(client: SigningCosmWasmClient, sender: string, contractAddress: string) { 79 | super(client, contractAddress); 80 | this.client = client; 81 | this.sender = sender; 82 | this.contractAddress = contractAddress; 83 | this.bond = this.bond.bind(this); 84 | this.unbond = this.unbond.bind(this); 85 | this.grantClaim = this.grantClaim.bind(this); 86 | this.releaseClaim = this.releaseClaim.bind(this); 87 | this.slashClaim = this.slashClaim.bind(this); 88 | } 89 | 90 | bond = async (fee: number | StdFee | "auto" = "auto", memo?: string, funds?: Coin[]): Promise => { 91 | return await this.client.execute(this.sender, this.contractAddress, { 92 | bond: {} 93 | }, fee, memo, funds); 94 | }; 95 | unbond = async ({ 96 | amount 97 | }: { 98 | amount: Uint128; 99 | }, fee: number | StdFee | "auto" = "auto", memo?: string, funds?: Coin[]): Promise => { 100 | return await this.client.execute(this.sender, this.contractAddress, { 101 | unbond: { 102 | amount 103 | } 104 | }, fee, memo, funds); 105 | }; 106 | grantClaim = async ({ 107 | amount, 108 | leinholder, 109 | validator 110 | }: { 111 | amount: Uint128; 112 | leinholder: string; 113 | validator: string; 114 | }, fee: number | StdFee | "auto" = "auto", memo?: string, funds?: Coin[]): Promise => { 115 | return await this.client.execute(this.sender, this.contractAddress, { 116 | grant_claim: { 117 | amount, 118 | leinholder, 119 | validator 120 | } 121 | }, fee, memo, funds); 122 | }; 123 | releaseClaim = async ({ 124 | amount, 125 | owner 126 | }: { 127 | amount: Uint128; 128 | owner: string; 129 | }, fee: number | StdFee | "auto" = "auto", memo?: string, funds?: Coin[]): Promise => { 130 | return await this.client.execute(this.sender, this.contractAddress, { 131 | release_claim: { 132 | amount, 133 | owner 134 | } 135 | }, fee, memo, funds); 136 | }; 137 | slashClaim = async ({ 138 | amount, 139 | owner 140 | }: { 141 | amount: Uint128; 142 | owner: string; 143 | }, fee: number | StdFee | "auto" = "auto", memo?: string, funds?: Coin[]): Promise => { 144 | return await this.client.execute(this.sender, this.contractAddress, { 145 | slash_claim: { 146 | amount, 147 | owner 148 | } 149 | }, fee, memo, funds); 150 | }; 151 | } -------------------------------------------------------------------------------- /typescript/contracts/mesh-lockup/MeshLockup.types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. 3 | * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, 4 | * and run the @cosmwasm/ts-codegen generate command to regenerate this file. 5 | */ 6 | 7 | export interface InstantiateMsg { 8 | denom: string; 9 | } 10 | export type ExecuteMsg = { 11 | bond: {}; 12 | } | { 13 | unbond: { 14 | amount: Uint128; 15 | }; 16 | } | { 17 | grant_claim: { 18 | amount: Uint128; 19 | leinholder: string; 20 | validator: string; 21 | }; 22 | } | { 23 | release_claim: { 24 | amount: Uint128; 25 | owner: string; 26 | }; 27 | } | { 28 | slash_claim: { 29 | amount: Uint128; 30 | owner: string; 31 | }; 32 | }; 33 | export type Uint128 = string; 34 | export type QueryMsg = { 35 | balance: { 36 | account: string; 37 | }; 38 | }; 39 | export interface BalanceResponse { 40 | bonded: Uint128; 41 | claims: Lein[]; 42 | free: Uint128; 43 | } 44 | export interface Lein { 45 | amount: Uint128; 46 | leinholder: string; 47 | } -------------------------------------------------------------------------------- /typescript/contracts/mesh-lockup/bundle.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. 3 | * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, 4 | * and run the @cosmwasm/ts-codegen generate command to regenerate this file. 5 | */ 6 | 7 | import * as _2 from "./MeshLockup.types"; 8 | import * as _3 from "./MeshLockup.client"; 9 | export namespace contracts { 10 | export const MeshLockup = { ..._2, 11 | ..._3 12 | }; 13 | } -------------------------------------------------------------------------------- /typescript/contracts/mesh-provider/MeshProvider.types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. 3 | * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, 4 | * and run the @cosmwasm/ts-codegen generate command to regenerate this file. 5 | */ 6 | 7 | export type Binary = string; 8 | export interface InstantiateMsg { 9 | consumer: ConsumerInfo; 10 | lockup: string; 11 | packet_lifetime?: number | null; 12 | rewards_ibc_denom: string; 13 | slasher: SlasherInfo; 14 | unbonding_period: number; 15 | } 16 | export interface ConsumerInfo { 17 | connection_id: string; 18 | } 19 | export interface SlasherInfo { 20 | code_id: number; 21 | msg: Binary; 22 | } 23 | export type ExecuteMsg = { 24 | slash: { 25 | force_unbond: boolean; 26 | percentage: Decimal; 27 | validator: string; 28 | }; 29 | } | { 30 | receive_claim: { 31 | amount: Uint128; 32 | owner: string; 33 | validator: string; 34 | }; 35 | } | { 36 | unstake: { 37 | amount: Uint128; 38 | validator: string; 39 | }; 40 | } | { 41 | unbond: {}; 42 | } | { 43 | claim_rewards: { 44 | validator: string; 45 | }; 46 | }; 47 | export type Decimal = string; 48 | export type Uint128 = string; 49 | export type QueryMsg = { 50 | config: {}; 51 | } | { 52 | account: { 53 | address: string; 54 | }; 55 | } | { 56 | validator: { 57 | address: string; 58 | }; 59 | } | { 60 | list_validators: { 61 | limit?: number | null; 62 | start_after?: string | null; 63 | }; 64 | }; 65 | export interface AccountResponse { 66 | staked: StakeInfo[]; 67 | } 68 | export interface StakeInfo { 69 | slashed: Uint128; 70 | tokens: Uint128; 71 | validator: string; 72 | } 73 | export interface ConfigResponse { 74 | consumer: ConsumerInfo; 75 | slasher?: string | null; 76 | } 77 | export type ValStatus = "active" | "removed" | "tombstoned"; 78 | export interface ListValidatorsResponse { 79 | validators: ValidatorResponse[]; 80 | } 81 | export interface ValidatorResponse { 82 | address: string; 83 | multiplier: Decimal; 84 | status: ValStatus; 85 | tokens: Uint128; 86 | } -------------------------------------------------------------------------------- /typescript/contracts/mesh-provider/bundle.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. 3 | * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, 4 | * and run the @cosmwasm/ts-codegen generate command to regenerate this file. 5 | */ 6 | 7 | import * as _6 from "./MeshProvider.types"; 8 | import * as _7 from "./MeshProvider.client"; 9 | export namespace contracts { 10 | export const MeshProvider = { ..._6, 11 | ..._7 12 | }; 13 | } -------------------------------------------------------------------------------- /typescript/contracts/mesh-slasher/MeshSlasher.client.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. 3 | * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, 4 | * and run the @cosmwasm/ts-codegen generate command to regenerate this file. 5 | */ 6 | 7 | import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from "@cosmjs/cosmwasm-stargate"; 8 | import { Coin, StdFee } from "@cosmjs/amino"; 9 | import { InstantiateMsg, ExecuteMsg, Decimal, QueryMsg, ConfigResponse } from "./MeshSlasher.types"; 10 | export interface MeshSlasherReadOnlyInterface { 11 | contractAddress: string; 12 | config: () => Promise; 13 | } 14 | export class MeshSlasherQueryClient implements MeshSlasherReadOnlyInterface { 15 | client: CosmWasmClient; 16 | contractAddress: string; 17 | 18 | constructor(client: CosmWasmClient, contractAddress: string) { 19 | this.client = client; 20 | this.contractAddress = contractAddress; 21 | this.config = this.config.bind(this); 22 | } 23 | 24 | config = async (): Promise => { 25 | return this.client.queryContractSmart(this.contractAddress, { 26 | config: {} 27 | }); 28 | }; 29 | } 30 | export interface MeshSlasherInterface extends MeshSlasherReadOnlyInterface { 31 | contractAddress: string; 32 | sender: string; 33 | submitEvidence: ({ 34 | amount, 35 | validator 36 | }: { 37 | amount: Decimal; 38 | validator: string; 39 | }, fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]) => Promise; 40 | } 41 | export class MeshSlasherClient extends MeshSlasherQueryClient implements MeshSlasherInterface { 42 | client: SigningCosmWasmClient; 43 | sender: string; 44 | contractAddress: string; 45 | 46 | constructor(client: SigningCosmWasmClient, sender: string, contractAddress: string) { 47 | super(client, contractAddress); 48 | this.client = client; 49 | this.sender = sender; 50 | this.contractAddress = contractAddress; 51 | this.submitEvidence = this.submitEvidence.bind(this); 52 | } 53 | 54 | submitEvidence = async ({ 55 | amount, 56 | validator 57 | }: { 58 | amount: Decimal; 59 | validator: string; 60 | }, fee: number | StdFee | "auto" = "auto", memo?: string, funds?: Coin[]): Promise => { 61 | return await this.client.execute(this.sender, this.contractAddress, { 62 | submit_evidence: { 63 | amount, 64 | validator 65 | } 66 | }, fee, memo, funds); 67 | }; 68 | } -------------------------------------------------------------------------------- /typescript/contracts/mesh-slasher/MeshSlasher.types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. 3 | * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, 4 | * and run the @cosmwasm/ts-codegen generate command to regenerate this file. 5 | */ 6 | 7 | export interface InstantiateMsg { 8 | owner: string; 9 | } 10 | export type ExecuteMsg = { 11 | submit_evidence: { 12 | amount: Decimal; 13 | validator: string; 14 | }; 15 | }; 16 | export type Decimal = string; 17 | export type QueryMsg = { 18 | config: {}; 19 | }; 20 | export interface ConfigResponse { 21 | owner: string; 22 | slashee: string; 23 | } -------------------------------------------------------------------------------- /typescript/contracts/mesh-slasher/bundle.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. 3 | * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, 4 | * and run the @cosmwasm/ts-codegen generate command to regenerate this file. 5 | */ 6 | 7 | import * as _4 from "./MeshSlasher.types"; 8 | import * as _5 from "./MeshSlasher.client"; 9 | export namespace contracts { 10 | export const MeshSlasher = { ..._4, 11 | ..._5 12 | }; 13 | } -------------------------------------------------------------------------------- /typescript/contracts/meta-staking/MetaStaking.types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. 3 | * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, 4 | * and run the @cosmwasm/ts-codegen generate command to regenerate this file. 5 | */ 6 | 7 | export interface InstantiateMsg {} 8 | export type ExecuteMsg = { 9 | delegate: { 10 | amount: Uint128; 11 | validator: string; 12 | }; 13 | } | { 14 | undelegate: { 15 | amount: Uint128; 16 | validator: string; 17 | }; 18 | } | { 19 | withdraw_delegator_reward: { 20 | validator: string; 21 | }; 22 | } | { 23 | withdraw_to_costumer: { 24 | consumer: string; 25 | validator: string; 26 | }; 27 | } | { 28 | sudo: StakingSudoMsg; 29 | }; 30 | export type Uint128 = string; 31 | export type StakingSudoMsg = { 32 | add_consumer: { 33 | consumer_address: string; 34 | funds_available_for_staking: Coin; 35 | }; 36 | } | { 37 | remove_consumer: { 38 | consumer_address: string; 39 | }; 40 | }; 41 | export interface Coin { 42 | amount: Uint128; 43 | denom: string; 44 | [k: string]: unknown; 45 | } 46 | export type QueryMsg = { 47 | all_delegations: { 48 | consumer: string; 49 | }; 50 | } | { 51 | consumer: { 52 | address: string; 53 | }; 54 | } | { 55 | consumers: { 56 | limit?: number | null; 57 | start?: string | null; 58 | }; 59 | } | { 60 | delegation: { 61 | consumer: string; 62 | validator: string; 63 | }; 64 | } | { 65 | all_validators: { 66 | consumer: string; 67 | limit?: number | null; 68 | start?: string | null; 69 | }; 70 | }; 71 | export type ArrayOfDelegation = Delegation[]; 72 | export interface Delegation { 73 | delegation: Uint128; 74 | validator: string; 75 | } 76 | export type ArrayOfString = string[]; 77 | export type Decimal = string; 78 | export interface ConsumerInfo { 79 | available_funds: Uint128; 80 | rewards: ConsumerRewards; 81 | total_staked: Uint128; 82 | } 83 | export interface ConsumerRewards { 84 | paid_rewards_per_token: Decimal; 85 | pending: Decimal; 86 | } 87 | export type Addr = string; 88 | export type ArrayOfAddr = Addr[]; -------------------------------------------------------------------------------- /typescript/contracts/meta-staking/bundle.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. 3 | * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, 4 | * and run the @cosmwasm/ts-codegen generate command to regenerate this file. 5 | */ 6 | 7 | import * as _8 from "./MetaStaking.types"; 8 | import * as _9 from "./MetaStaking.client"; 9 | export namespace contracts { 10 | export const MetaStaking = { ..._8, 11 | ..._9 12 | }; 13 | } -------------------------------------------------------------------------------- /typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mesh-security", 3 | "version": "0.1.0", 4 | "description": "", 5 | "main": "build/codegen.js", 6 | "scripts": { 7 | "prepare": "tsc", 8 | "build": "tsc", 9 | "codegen": "ts-node src/codegen.ts" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "husky": { 15 | "hooks": { 16 | "pre-commit": "pretty-quick --staged" 17 | } 18 | }, 19 | "devDependencies": { 20 | "@types/node": "^15.14.9", 21 | "@cosmwasm/ts-codegen": "0.24.0", 22 | "husky": "^7.0.4", 23 | "prettier": "^2.5.0", 24 | "pretty-quick": "^3.1.2", 25 | "ts-node": "^10.4.0", 26 | "typescript": "^4.5.2" 27 | }, 28 | "dependencies": { 29 | "@cosmjs/amino": "^0.29.2", 30 | "@cosmjs/cosmwasm-stargate": "^0.29.2", 31 | "@cosmjs/proto-signing": "^0.29.2", 32 | "@cosmjs/stargate": "^0.29.2", 33 | "@cosmjs/stream": "^0.29.2", 34 | "@cosmjs/tendermint-rpc": "^0.29.2", 35 | "dotenv": "^10.0.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /typescript/src/codegen.ts: -------------------------------------------------------------------------------- 1 | import codegen from "@cosmwasm/ts-codegen"; 2 | import path from "path"; 3 | import fs from "fs"; 4 | 5 | enum OutputType { 6 | contracts = "contracts", 7 | packages = "packages", 8 | proposal = "proposal", 9 | staking = "staking", 10 | voting = "voting", 11 | "pre-propose" = "pre-propose", 12 | external = "external", 13 | } 14 | 15 | export type CompilationSpec = { 16 | contractName: string; 17 | schemaDir: string; 18 | outputPath: string; 19 | outputType: OutputType; 20 | }; 21 | 22 | const CONTRACTS_OUTPUT_DIR = "."; 23 | const DEFAULT_CONFIG = { 24 | schemaRoots: [ 25 | { 26 | name: OutputType.contracts, 27 | paths: [`../${OutputType.contracts}`], 28 | outputName: OutputType.contracts, 29 | outputDir: CONTRACTS_OUTPUT_DIR, 30 | }, 31 | { 32 | name: OutputType.packages, 33 | paths: [`../${OutputType.packages}`], 34 | outputName: OutputType.packages, 35 | outputDir: CONTRACTS_OUTPUT_DIR, 36 | }, 37 | ], 38 | }; 39 | 40 | async function generateTs(spec: CompilationSpec): Promise { 41 | const out = `${spec.outputPath}/${spec.outputType}/${spec.contractName}`; 42 | const name = spec.contractName; 43 | console.log(spec.schemaDir); 44 | return await codegen({ 45 | contracts: [ 46 | { 47 | name: `${name}`, 48 | dir: `${spec.schemaDir}`, 49 | }, 50 | ], 51 | outPath: `./${OutputType.contracts}/${name}`, 52 | }).then(() => { 53 | console.log(`${name} done!`); 54 | }); 55 | } 56 | 57 | function getSchemaDirectories(rootDir: string): Promise { 58 | return new Promise((resolve, _reject) => { 59 | const directories: string[][] = []; 60 | // get all the schema directories in all the root dir 61 | fs.readdir(rootDir, (err: any, dirEntries: any[]) => { 62 | if (err) { 63 | console.error(err); 64 | return; 65 | } 66 | if (!dirEntries) { 67 | console.warn(`no entries found in ${rootDir}`); 68 | resolve([]); 69 | return; 70 | } 71 | dirEntries.forEach((entry) => { 72 | try { 73 | const schemaDir = path.resolve(rootDir, entry, "schema"); 74 | if (fs.existsSync(schemaDir) && fs.lstatSync(schemaDir).isDirectory()) { 75 | directories.push([schemaDir.replaceAll("\\", "/"), entry]); 76 | } 77 | } catch (e) { 78 | console.warn(e); 79 | } 80 | }); 81 | resolve(directories); 82 | }); 83 | }); 84 | } 85 | 86 | async function main() { 87 | let config = { 88 | ...DEFAULT_CONFIG, 89 | }; 90 | 91 | const compilationSpecs: CompilationSpec[] = []; 92 | console.log("Calculating generation specs..."); 93 | for (const root of config.schemaRoots) { 94 | const { name, paths, outputName, outputDir } = root; 95 | for (const path of paths) { 96 | const schemaDirectories = await getSchemaDirectories(path); 97 | for (const [directory, contractName] of schemaDirectories) { 98 | compilationSpecs.push({ 99 | contractName: contractName, 100 | schemaDir: directory, 101 | outputPath: outputDir, 102 | outputType: outputName, 103 | }); 104 | } 105 | } 106 | } 107 | console.log(`code generating for ${compilationSpecs?.length ?? 0} specs...`); 108 | 109 | const codegenResponses: Promise[] = []; 110 | for (const spec of compilationSpecs) { 111 | codegenResponses.push(generateTs(spec)); 112 | } 113 | await Promise.all(codegenResponses); 114 | 115 | console.log(`code generation complete`); 116 | } 117 | 118 | main(); 119 | --------------------------------------------------------------------------------