From 2bb9932f4c10d67b4dc909c86efc7265a58021b1 Mon Sep 17 00:00:00 2001 From: Kayanski Date: Tue, 7 Jan 2025 14:25:46 +0100 Subject: [PATCH 1/6] Added token factory minimal integration + simple test --- Cargo.lock | 239 +++++++++++++- Cargo.toml | 13 +- src/lib.rs | 9 + src/tokenfactory/mod.rs | 237 ++++++++++++++ src/tokenfactory/osmosis.rs | 392 +++++++++++++++++++++++ src/tokenfactory/serde.rs | 104 ++++++ src/tokenfactory/shim.rs | 284 ++++++++++++++++ tests/mod.rs | 2 + tests/test_token_factory/mod.rs | 1 + tests/test_token_factory/test_osmosis.rs | 62 ++++ 10 files changed, 1335 insertions(+), 8 deletions(-) create mode 100644 src/tokenfactory/mod.rs create mode 100644 src/tokenfactory/osmosis.rs create mode 100644 src/tokenfactory/serde.rs create mode 100644 src/tokenfactory/shim.rs create mode 100644 tests/test_token_factory/mod.rs create mode 100644 tests/test_token_factory/test_osmosis.rs diff --git a/Cargo.lock b/Cargo.lock index 696c1fc4..2ffa7ef3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -35,6 +35,21 @@ version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anyhow" version = "1.0.89" @@ -219,6 +234,12 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e31ea183f6ee62ac8b8a8cf7feddd766317adfb13ff469de57ce033efd6a790" +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + [[package]] name = "byteorder" version = "1.5.0" @@ -231,18 +252,47 @@ version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +[[package]] +name = "cc" +version = "1.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a012a0df96dd6d06ba9a1b29d6402d1a5d77c6befd2566afdc26e10603dc93d7" +dependencies = [ + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets", +] + [[package]] name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cosmwasm-core" version = "2.1.4" @@ -419,6 +469,7 @@ version = "2.2.0" dependencies = [ "anyhow", "bech32", + "chrono", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus", @@ -427,9 +478,12 @@ dependencies = [ "hex-literal", "itertools 0.13.0", "once_cell", - "prost", + "osmosis-std-derive", + "prost 0.13.4", + "prost-types 0.13.4", "schemars", "serde", + "serde-cw-value", "sha2", "thiserror", ] @@ -674,6 +728,29 @@ dependencies = [ "digest", ] +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "itertools" version = "0.10.5" @@ -698,6 +775,16 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "js-sys" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "k256" version = "0.13.4" @@ -716,6 +803,12 @@ version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + [[package]] name = "memchr" version = "2.7.4" @@ -774,6 +867,19 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "osmosis-std-derive" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b0240fd030a4bbc79fa6cbea0b3eb0260a4b79075ebc039b93e2652bff8655b" +dependencies = [ + "itertools 0.10.5", + "proc-macro2", + "prost-types 0.11.9", + "quote", + "syn 1.0.109", +] + [[package]] name = "p256" version = "0.13.2" @@ -821,19 +927,42 @@ dependencies = [ [[package]] name = "prost" -version = "0.13.3" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f" +checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" dependencies = [ "bytes", - "prost-derive", + "prost-derive 0.11.9", +] + +[[package]] +name = "prost" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c0fef6c4230e4ccf618a35c59d7ede15dea37de8427500f50aff708806e42ec" +dependencies = [ + "bytes", + "prost-derive 0.13.4", ] [[package]] name = "prost-derive" -version = "0.13.3" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5" +checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" +dependencies = [ + "anyhow", + "itertools 0.10.5", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "prost-derive" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "157c5a9d7ea5c2ed2d9fb8f495b64759f7816c7eaea54ba3978f0d63000162e3" dependencies = [ "anyhow", "itertools 0.13.0", @@ -842,6 +971,24 @@ dependencies = [ "syn 2.0.77", ] +[[package]] +name = "prost-types" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" +dependencies = [ + "prost 0.11.9", +] + +[[package]] +name = "prost-types" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2f1e56baa61e93533aebc21af4d2134b70f66275e0fcdf3cbe43d77ff7e8fc" +dependencies = [ + "prost 0.13.4", +] + [[package]] name = "quote" version = "1.0.37" @@ -983,6 +1130,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-cw-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75d32da6b8ed758b7d850b6c3c08f1d7df51a4df3cb201296e63e34a78e99d4" +dependencies = [ + "serde", +] + [[package]] name = "serde-json-wasm" version = "1.0.1" @@ -1037,6 +1193,12 @@ dependencies = [ "digest", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signature" version = "2.2.0" @@ -1131,6 +1293,69 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.77", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-targets" version = "0.52.6" diff --git a/Cargo.toml b/Cargo.toml index 4b190537..fbb7b919 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "cw-multi-test" version = "2.2.0" authors = [ "Ethan Frey ", - "Dariusz Depta " + "Dariusz Depta ", ] description = "Testing tools for multi-contract interactions" repository = "https://github.com/CosmWasm/cw-multi-test" @@ -25,18 +25,29 @@ cosmwasm_1_3 = ["cosmwasm_1_2", "cosmwasm-std/cosmwasm_1_3"] cosmwasm_1_4 = ["cosmwasm_1_3", "cosmwasm-std/cosmwasm_1_4"] cosmwasm_2_0 = ["cosmwasm_1_4", "cosmwasm-std/cosmwasm_2_0"] cosmwasm_2_1 = ["cosmwasm_2_0", "cosmwasm-std/cosmwasm_2_1"] +tokenfactory = [ + "dep:osmosis-std-derive", + "dep:chrono", + "dep:prost-types", + "dep:serde-cw-value", +] + [dependencies] anyhow = "1.0.89" bech32 = "0.11.0" +chrono = { version = "0.4.39", optional = true } cosmwasm-schema = "2.1.3" cosmwasm-std = "2.1.4" cw-storage-plus = "2.0.0" cw-utils = "2.0.0" itertools = "0.13.0" +osmosis-std-derive = { version = "0.26.0", optional = true } prost = "0.13.3" +prost-types = { version = "0.13.4", optional = true } schemars = "0.8.21" serde = "1.0.210" +serde-cw-value = { version = "0.7.0", optional = true } sha2 = "0.10.8" thiserror = "1.0.64" diff --git a/src/lib.rs b/src/lib.rs index 76f1413b..d384c24f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -147,6 +147,12 @@ mod tests; mod transactions; mod wasm; +#[cfg(feature = "tokenfactory")] +/// TokenFactory integration for cw-multi-test +pub mod tokenfactory; +#[cfg(feature = "tokenfactory")] +pub(crate) use tokenfactory::shim; + pub use crate::addresses::{ AddressGenerator, IntoAddr, IntoBech32, IntoBech32m, SimpleAddressGenerator, }; @@ -168,3 +174,6 @@ pub use crate::staking::{ }; pub use crate::stargate::{Stargate, StargateAccepting, StargateFailing}; pub use crate::wasm::{ContractData, Wasm, WasmKeeper, WasmSudo}; + +#[cfg(feature = "tokenfactory")] +pub use tokenfactory::{denom, TokenFactoryStargate}; diff --git a/src/tokenfactory/mod.rs b/src/tokenfactory/mod.rs new file mode 100644 index 00000000..2c0dbd62 --- /dev/null +++ b/src/tokenfactory/mod.rs @@ -0,0 +1,237 @@ +use anyhow::{bail, Result as AnyResult}; +use cosmwasm_schema::serde::de::DeserializeOwned; +use cosmwasm_std::{ + to_json_binary, Addr, AnyMsg, Api, Binary, BlockInfo, CosmosMsg, CustomMsg, CustomQuery, Empty, + GrpcQuery, Querier, Storage, +}; +use cw_storage_plus::Map; +use prost::Message; + +use crate::{AppResponse, CosmosRouter, Stargate}; + +pub mod osmosis; +pub(crate) mod serde; +pub use shim::Coin; + +pub(crate) mod shim; + +/// Always accepting handler for `Stargate`/`Any` message variants and `Stargate`/`Grpc` queries. +pub struct TokenFactoryStargate; + +const TOKENFACTORY_DENOMS: Map<(&String, &String), ()> = Map::new("tokenfactory_denoms"); + +/// Merge sender and subdenom to create the denom +pub fn denom(sender: &str, subdenom: &str) -> String { + format!("{sender}/{subdenom}") +} + +fn split_denom(denom: &str) -> AnyResult<(String, String)> { + let split_result: Vec<&str> = denom.splitn(2, "/").collect(); + + if split_result.len() != 2 { + bail!("Error decoding denom {denom} into sender/subdenom") + } + + Ok((split_result[0].to_string(), split_result[1].to_string())) +} + +impl Stargate for TokenFactoryStargate { + fn execute_stargate( + &self, + api: &dyn Api, + storage: &mut dyn Storage, + router: &dyn CosmosRouter, + block: &BlockInfo, + sender: Addr, + type_url: String, + value: Binary, + ) -> AnyResult + where + ExecC: CustomMsg + DeserializeOwned + 'static, + QueryC: CustomQuery + DeserializeOwned + 'static, + { + // We match the type url + match type_url.as_str() { + osmosis::MsgCreateDenom::TYPE_URL => { + let msg = osmosis::MsgCreateDenom::decode(value.as_slice())?; + if sender != api.addr_validate(&msg.sender)? { + bail!("Sender field should match the message sender") + } + self.create_denom(storage, msg.sender, msg.subdenom) + } + osmosis::MsgMint::TYPE_URL => { + let msg = osmosis::MsgMint::decode(value.as_slice())?; + if sender != api.addr_validate(&msg.sender)? { + bail!("Sender field should match the message sender") + } + self.mint( + api, + storage, + router, + block, + msg.sender, + msg.amount, + msg.mint_to_address, + ) + } + osmosis::MsgBurn::TYPE_URL => { + let msg = osmosis::MsgBurn::decode(value.as_slice())?; + if sender != api.addr_validate(&msg.sender)? { + bail!("Sender field should match the message sender") + } + self.burn( + api, + storage, + router, + block, + msg.sender, + msg.amount, + msg.burn_from_address, + ) + } + _ => { + bail!("type url {} is not supported", type_url) + } + } + } + + fn query_stargate( + &self, + _api: &dyn Api, + _storage: &dyn Storage, + _querier: &dyn Querier, + _block: &BlockInfo, + _path: String, + _data: Binary, + ) -> AnyResult { + to_json_binary(&Empty {}).map_err(Into::into) + } + + fn execute_any( + &self, + api: &dyn Api, + storage: &mut dyn Storage, + router: &dyn CosmosRouter, + block: &BlockInfo, + sender: Addr, + msg: AnyMsg, + ) -> AnyResult + where + ExecC: CustomMsg + DeserializeOwned + 'static, + QueryC: CustomQuery + DeserializeOwned + 'static, + { + self.execute_stargate(api, storage, router, block, sender, msg.type_url, msg.value) + } + + fn query_grpc( + &self, + api: &dyn Api, + storage: &dyn Storage, + querier: &dyn Querier, + block: &BlockInfo, + request: GrpcQuery, + ) -> AnyResult { + self.query_stargate(api, storage, querier, block, request.path, request.data) + } +} + +impl TokenFactoryStargate { + fn create_denom( + &self, + storage: &mut dyn Storage, + sender: String, + subdenom: String, + ) -> AnyResult { + if TOKENFACTORY_DENOMS.has(storage, (&sender, &subdenom)) { + bail!("Subdenom {subdenom} by sender {sender} already exists") + } + + TOKENFACTORY_DENOMS.save(storage, (&sender, &subdenom), &())?; + + Ok(AppResponse::default()) + } + fn mint( + &self, + api: &dyn Api, + storage: &mut dyn Storage, + router: &dyn CosmosRouter, + block: &BlockInfo, + sender: String, + amount: Option, + mint_to_address: String, + ) -> AnyResult + where + ExecC: CustomMsg + DeserializeOwned + 'static, + QueryC: CustomQuery + DeserializeOwned + 'static, + { + let amount = if let Some(amount) = amount { + amount + } else { + bail!("Can't mint nothing, please specify an amount") + }; + + // We verify the denom exists + let (creator, subdenom) = split_denom(&amount.denom)?; + if creator != sender { + bail!("Only creator can mint token factory tokens") + } + if !TOKENFACTORY_DENOMS.has(storage, (&sender, &subdenom)) { + bail!("Subdenom {subdenom} by sender {sender} doesn't exist") + } + + // We mint. No need for transactional cache here, if this fails, the tokenfactory mint fails + router.sudo( + api, + storage, + block, + crate::SudoMsg::Bank(crate::BankSudo::Mint { + to_address: mint_to_address, + amount: vec![amount.try_into()?], + }), + )?; + + Ok(AppResponse::default()) + } + + fn burn( + &self, + api: &dyn Api, + storage: &mut dyn Storage, + router: &dyn CosmosRouter, + block: &BlockInfo, + sender: String, + amount: Option, + burn_from_address: String, + ) -> AnyResult + where + ExecC: CustomMsg + DeserializeOwned + 'static, + QueryC: CustomQuery + DeserializeOwned + 'static, + { + let amount = if let Some(amount) = amount { + amount + } else { + bail!("Can't burn nothing, please specify an amount") + }; + let (creator, subdenom) = split_denom(&amount.denom)?; + if creator != sender { + bail!("Only creator can burn token factory tokens from any address") + } + // We verify the denom exists + if !TOKENFACTORY_DENOMS.has(storage, (&sender, &subdenom)) { + bail!("Subdenom {subdenom} by sender {sender} doesn't exist") + } + + // We mint. No need for transactional cache here, if this fails, the tokenfactory mint fails + router.execute( + api, + storage, + block, + api.addr_validate(&burn_from_address)?, + CosmosMsg::Bank(cosmwasm_std::BankMsg::Burn { + amount: vec![amount.try_into()?], + }), + )?; + + Ok(AppResponse::default()) + } +} diff --git a/src/tokenfactory/osmosis.rs b/src/tokenfactory/osmosis.rs new file mode 100644 index 00000000..e4caa02a --- /dev/null +++ b/src/tokenfactory/osmosis.rs @@ -0,0 +1,392 @@ +#![allow(missing_docs)] + +use osmosis_std_derive::CosmwasmExt; + +use super::shim::Coin; + +/// QueryDenomsFromCreatorRequest defines the request structure for the +/// DenomsFromCreator gRPC query. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + ::serde::Serialize, + ::serde::Deserialize, + ::schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/osmosis.tokenfactory.v1beta1.QueryDenomsFromCreatorRequest")] +#[proto_query( + path = "/osmosis.tokenfactory.v1beta1.Query/DenomsFromCreator", + response_type = QueryDenomsFromCreatorResponse +)] +pub struct QueryDenomsFromCreatorRequest { + #[prost(string, tag = "1")] + pub creator: ::prost::alloc::string::String, +} +/// QueryDenomsFromCreatorRequest defines the response structure for the +/// DenomsFromCreator gRPC query. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + ::serde::Serialize, + ::serde::Deserialize, + ::schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/osmosis.tokenfactory.v1beta1.QueryDenomsFromCreatorResponse")] +pub struct QueryDenomsFromCreatorResponse { + #[prost(string, repeated, tag = "1")] + pub denoms: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + ::serde::Serialize, + ::serde::Deserialize, + ::schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/osmosis.tokenfactory.v1beta1.QueryBeforeSendHookAddressRequest")] +#[proto_query( + path = "/osmosis.tokenfactory.v1beta1.Query/BeforeSendHookAddress", + response_type = QueryBeforeSendHookAddressResponse +)] +pub struct QueryBeforeSendHookAddressRequest { + #[prost(string, tag = "1")] + pub denom: ::prost::alloc::string::String, +} +/// QueryBeforeSendHookAddressResponse defines the response structure for the +/// DenomBeforeSendHook gRPC query. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + ::serde::Serialize, + ::serde::Deserialize, + ::schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/osmosis.tokenfactory.v1beta1.QueryBeforeSendHookAddressResponse")] +pub struct QueryBeforeSendHookAddressResponse { + #[prost(string, tag = "1")] + pub cosmwasm_address: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + ::serde::Serialize, + ::serde::Deserialize, + ::schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/osmosis.tokenfactory.v1beta1.QueryAllBeforeSendHooksAddressesRequest")] +#[proto_query( + path = "/osmosis.tokenfactory.v1beta1.Query/AllBeforeSendHooksAddresses", + response_type = QueryAllBeforeSendHooksAddressesResponse +)] +pub struct QueryAllBeforeSendHooksAddressesRequest {} +/// QueryAllBeforeSendHooksAddressesResponse defines the response structure for +/// the AllBeforeSendHooksAddresses gRPC query. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + ::serde::Serialize, + ::serde::Deserialize, + ::schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message( + type_url = "/osmosis.tokenfactory.v1beta1.QueryAllBeforeSendHooksAddressesResponse" +)] +pub struct QueryAllBeforeSendHooksAddressesResponse { + #[prost(string, repeated, tag = "1")] + pub denoms: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(string, repeated, tag = "2")] + pub before_send_hook_addresses: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +/// MsgCreateDenom defines the message structure for the CreateDenom gRPC service +/// method. It allows an account to create a new denom. It requires a sender +/// address and a sub denomination. The (sender_address, sub_denomination) tuple +/// must be unique and cannot be re-used. +/// +/// The resulting denom created is defined as +/// . The resulting denom's admin is +/// originally set to be the creator, but this can be changed later. The token +/// denom does not indicate the current admin. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + ::serde::Serialize, + ::serde::Deserialize, + ::schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/osmosis.tokenfactory.v1beta1.MsgCreateDenom")] +pub struct MsgCreateDenom { + #[prost(string, tag = "1")] + pub sender: ::prost::alloc::string::String, + /// subdenom can be up to 44 "alphanumeric" characters long. + #[prost(string, tag = "2")] + pub subdenom: ::prost::alloc::string::String, +} +/// MsgCreateDenomResponse is the return value of MsgCreateDenom +/// It returns the full string of the newly created denom +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + ::serde::Serialize, + ::serde::Deserialize, + ::schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/osmosis.tokenfactory.v1beta1.MsgCreateDenomResponse")] +pub struct MsgCreateDenomResponse { + #[prost(string, tag = "1")] + pub new_token_denom: ::prost::alloc::string::String, +} +/// MsgMint is the sdk.Msg type for allowing an admin account to mint +/// more of a token. +/// Only the admin of the token factory denom has permission to mint unless +/// the denom does not have any admin. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + ::serde::Serialize, + ::serde::Deserialize, + ::schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/osmosis.tokenfactory.v1beta1.MsgMint")] +pub struct MsgMint { + #[prost(string, tag = "1")] + pub sender: ::prost::alloc::string::String, + #[prost(message, optional, tag = "2")] + pub amount: ::core::option::Option, + #[prost(string, tag = "3")] + pub mint_to_address: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + ::serde::Serialize, + ::serde::Deserialize, + ::schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/osmosis.tokenfactory.v1beta1.MsgMintResponse")] +pub struct MsgMintResponse {} +/// MsgBurn is the sdk.Msg type for allowing an admin account to burn +/// a token. +/// Only the admin of the token factory denom has permission to burn unless +/// the denom does not have any admin. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + ::serde::Serialize, + ::serde::Deserialize, + ::schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/osmosis.tokenfactory.v1beta1.MsgBurn")] +pub struct MsgBurn { + #[prost(string, tag = "1")] + pub sender: ::prost::alloc::string::String, + #[prost(message, optional, tag = "2")] + pub amount: ::core::option::Option, + #[prost(string, tag = "3")] + pub burn_from_address: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + ::serde::Serialize, + ::serde::Deserialize, + ::schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/osmosis.tokenfactory.v1beta1.MsgBurnResponse")] +pub struct MsgBurnResponse {} +/// MsgChangeAdmin is the sdk.Msg type for allowing an admin account to reassign +/// adminship of a denom to a new account +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + ::serde::Serialize, + ::serde::Deserialize, + ::schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/osmosis.tokenfactory.v1beta1.MsgChangeAdmin")] +pub struct MsgChangeAdmin { + #[prost(string, tag = "1")] + pub sender: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub denom: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub new_admin: ::prost::alloc::string::String, +} +/// MsgChangeAdminResponse defines the response structure for an executed +/// MsgChangeAdmin message. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + ::serde::Serialize, + ::serde::Deserialize, + ::schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/osmosis.tokenfactory.v1beta1.MsgChangeAdminResponse")] +pub struct MsgChangeAdminResponse {} +/// MsgSetBeforeSendHook is the sdk.Msg type for allowing an admin account to +/// assign a CosmWasm contract to call with a BeforeSend hook +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + ::serde::Serialize, + ::serde::Deserialize, + ::schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/osmosis.tokenfactory.v1beta1.MsgSetBeforeSendHook")] +pub struct MsgSetBeforeSendHook { + #[prost(string, tag = "1")] + pub sender: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub denom: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub cosmwasm_address: ::prost::alloc::string::String, +} +/// MsgSetBeforeSendHookResponse defines the response structure for an executed +/// MsgSetBeforeSendHook message. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + ::serde::Serialize, + ::serde::Deserialize, + ::schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/osmosis.tokenfactory.v1beta1.MsgSetBeforeSendHookResponse")] +pub struct MsgSetBeforeSendHookResponse {} +/// MsgSetDenomMetadataResponse defines the response structure for an executed +/// MsgSetDenomMetadata message. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + ::serde::Serialize, + ::serde::Deserialize, + ::schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/osmosis.tokenfactory.v1beta1.MsgSetDenomMetadataResponse")] +pub struct MsgSetDenomMetadataResponse {} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + ::serde::Serialize, + ::serde::Deserialize, + ::schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/osmosis.tokenfactory.v1beta1.MsgForceTransfer")] +pub struct MsgForceTransfer { + #[prost(string, tag = "1")] + pub sender: ::prost::alloc::string::String, + #[prost(message, optional, tag = "2")] + pub amount: ::core::option::Option, + #[prost(string, tag = "3")] + pub transfer_from_address: ::prost::alloc::string::String, + #[prost(string, tag = "4")] + pub transfer_to_address: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + ::serde::Serialize, + ::serde::Deserialize, + ::schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/osmosis.tokenfactory.v1beta1.MsgForceTransferResponse")] +pub struct MsgForceTransferResponse {} +pub struct TokenfactoryQuerier<'a, Q: cosmwasm_std::CustomQuery> { + querier: &'a cosmwasm_std::QuerierWrapper<'a, Q>, +} +impl<'a, Q: cosmwasm_std::CustomQuery> TokenfactoryQuerier<'a, Q> { + pub fn new(querier: &'a cosmwasm_std::QuerierWrapper<'a, Q>) -> Self { + Self { querier } + } + pub fn denoms_from_creator( + &self, + creator: ::prost::alloc::string::String, + ) -> Result { + QueryDenomsFromCreatorRequest { creator }.query(self.querier) + } + pub fn before_send_hook_address( + &self, + denom: ::prost::alloc::string::String, + ) -> Result { + QueryBeforeSendHookAddressRequest { denom }.query(self.querier) + } + pub fn all_before_send_hooks_addresses( + &self, + ) -> Result { + QueryAllBeforeSendHooksAddressesRequest {}.query(self.querier) + } +} diff --git a/src/tokenfactory/serde.rs b/src/tokenfactory/serde.rs new file mode 100644 index 00000000..2713b7f8 --- /dev/null +++ b/src/tokenfactory/serde.rs @@ -0,0 +1,104 @@ +pub mod as_str { + use serde::{de, Deserialize, Deserializer, Serializer}; + use std::{fmt::Display, str::FromStr}; + + pub fn deserialize<'de, T, D>(deserializer: D) -> Result + where + T: FromStr, + T::Err: Display, + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + T::from_str(&s).map_err(de::Error::custom) + } + + pub fn serialize(value: &T, serializer: S) -> Result + where + S: Serializer, + T: Display, + { + serializer.serialize_str(&value.to_string()) + } +} + +pub mod as_str_vec { + use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; + use std::{fmt::Display, str::FromStr}; + + pub fn deserialize<'de, T, D>(deserializer: D) -> Result, D::Error> + where + T: FromStr, + T::Err: Display, + D: Deserializer<'de>, + { + let vec_of_strings: Vec = Vec::deserialize(deserializer)?; + vec_of_strings + .into_iter() + .map(|s| T::from_str(&s).map_err(de::Error::custom)) + .collect() + } + + pub fn serialize(values: &[T], serializer: S) -> Result + where + S: Serializer, + T: Display, + { + let vec_of_strings: Vec = values.iter().map(|value| value.to_string()).collect(); + vec_of_strings.serialize(serializer) + } +} + +pub mod as_base64_encoded_string { + use cosmwasm_std::Binary; + use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let encoded_string = String::deserialize(deserializer)?; + Binary::from_base64(&encoded_string) + .map(|b| b.to_vec()) + .map_err(de::Error::custom) + } + + pub fn serialize(values: &[u8], serializer: S) -> Result + where + S: Serializer, + { + Binary::new(values.to_vec()) + .to_base64() + .serialize(serializer) + } +} + +pub mod as_option_base64_encoded_string { + use cosmwasm_std::Binary; + use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; + + pub fn deserialize<'de, D>(deserializer: D) -> Result>, D::Error> + where + D: Deserializer<'de>, + { + let encoded_string: Option = Option::deserialize(deserializer)?; + match encoded_string { + Some(s) => Binary::from_base64(&s) + .map(|b| Some(b.to_vec())) + .map_err(de::Error::custom), + None => Ok(None), + } + } + + pub fn serialize(value: &Option>, serializer: S) -> Result + where + S: Serializer, + { + match value { + Some(vec) => { + let encoded_string = Binary::new(vec.clone()).to_base64(); + encoded_string.serialize(serializer) + } + None => serializer.serialize_none(), + } + } +} \ No newline at end of file diff --git a/src/tokenfactory/shim.rs b/src/tokenfactory/shim.rs new file mode 100644 index 00000000..623d6ac2 --- /dev/null +++ b/src/tokenfactory/shim.rs @@ -0,0 +1,284 @@ +#![allow(missing_docs)] + +use ::serde::{Deserialize, Deserializer, Serialize, Serializer}; +use chrono::{DateTime, NaiveDateTime, Utc}; +use cosmwasm_std::StdResult; +use osmosis_std_derive::CosmwasmExt; +use serde::de; +use serde::de::Visitor; + +use std::fmt; +use std::str::FromStr; + +#[derive(Clone, PartialEq, Eq, ::prost::Message, schemars::JsonSchema)] +pub struct Timestamp { + /// Represents seconds of UTC time since Unix epoch + /// 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to + /// 9999-12-31T23:59:59Z inclusive. + #[prost(int64, tag = "1")] + pub seconds: i64, + /// Non-negative fractions of a second at nanosecond resolution. Negative + /// second values with fractions must still have non-negative nanos values + /// that count forward in time. Must be from 0 to 999,999,999 + /// inclusive. + #[prost(int32, tag = "2")] + pub nanos: i32, +} + +impl Serialize for Timestamp { + fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> + where + S: Serializer, + { + let mut ts = prost_types::Timestamp { + seconds: self.seconds, + nanos: self.nanos, + }; + ts.normalize(); + let dt = NaiveDateTime::from_timestamp_opt(ts.seconds, ts.nanos as u32) + .expect("invalid or out-of-range datetime"); + let dt: DateTime = DateTime::from_naive_utc_and_offset(dt, Utc); + serializer.serialize_str(format!("{:?}", dt).as_str()) + } +} + +impl<'de> Deserialize<'de> for Timestamp { + fn deserialize(deserializer: D) -> Result>::Error> + where + D: Deserializer<'de>, + { + struct TimestampVisitor; + + impl<'de> Visitor<'de> for TimestampVisitor { + type Value = Timestamp; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("Timestamp in RFC3339 format") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + let utc: DateTime = chrono::DateTime::from_str(value).map_err(|err| { + serde::de::Error::custom(format!( + "Failed to parse {} as datetime: {:?}", + value, err + )) + })?; + let ts = Timestamp::from(utc); + Ok(ts) + } + } + deserializer.deserialize_str(TimestampVisitor) + } +} + +impl From> for Timestamp { + fn from(dt: DateTime) -> Self { + Timestamp { + seconds: dt.timestamp(), + nanos: dt.timestamp_subsec_nanos() as i32, + } + } +} +#[derive(Clone, PartialEq, Eq, ::prost::Message, schemars::JsonSchema)] +pub struct Duration { + /// Signed seconds of the span of time. Must be from -315,576,000,000 + /// to +315,576,000,000 inclusive. Note: these bounds are computed from: + /// 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years + #[prost(int64, tag = "1")] + pub seconds: i64, + /// Signed fractions of a second at nanosecond resolution of the span + /// of time. Durations less than one second are represented with a 0 + /// `seconds` field and a positive or negative `nanos` field. For durations + /// of one second or more, a non-zero value for the `nanos` field must be + /// of the same sign as the `seconds` field. Must be from -999,999,999 + /// to +999,999,999 inclusive. + #[prost(int32, tag = "2")] + pub nanos: i32, +} + +impl Serialize for Duration { + fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> + where + S: Serializer, + { + let mut d = prost_types::Duration::from(self.to_owned()); + d.normalize(); + + serializer.serialize_str(d.to_string().as_str()) + } +} + +impl<'de> Deserialize<'de> for Duration { + fn deserialize(deserializer: D) -> Result>::Error> + where + D: Deserializer<'de>, + { + struct DurationVisitor; + + impl<'de> Visitor<'de> for DurationVisitor { + type Value = Duration; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("Timestamp in RFC3339 format") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + value + .parse::() + .map(Into::into) + .map_err(de::Error::custom) + } + } + deserializer.deserialize_str(DurationVisitor) + } +} + +#[derive(Clone, PartialEq, Eq, ::prost::Message, schemars::JsonSchema)] +pub struct Any { + /// A URL/resource name that uniquely identifies the type of the serialized + /// protocol buffer message. This string must contain at least + /// one "/" character. The last segment of the URL's path must represent + /// the fully qualified name of the type (as in + /// `path/google.protobuf.Duration`). The name should be in a canonical form + /// (e.g., leading "." is not accepted). + /// + /// In practice, teams usually precompile into the binary all types that they + /// expect it to use in the context of Any. However, for URLs which use the + /// scheme `http`, `https`, or no scheme, one can optionally set up a type + /// server that maps type URLs to message definitions as follows: + /// + /// * If no scheme is provided, `https` is assumed. + /// * An HTTP GET on the URL must yield a \[google.protobuf.Type][\] + /// value in binary format, or produce an error. + /// * Applications are allowed to cache lookup results based on the + /// URL, or have them precompiled into a binary to avoid any + /// lookup. Therefore, binary compatibility needs to be preserved + /// on changes to types. (Use versioned type names to manage + /// breaking changes.) + /// + /// Note: this functionality is not currently available in the official + /// protobuf release, and it is not used for type URLs beginning with + /// type.googleapis.com. + /// + /// Schemes other than `http`, `https` (or the empty scheme) might be + /// used with implementation specific semantics. + /// + #[prost(string, tag = "1")] + pub type_url: ::prost::alloc::string::String, + /// Must be a valid serialized protocol buffer of the above specified type. + #[prost(bytes = "vec", tag = "2")] + pub value: ::prost::alloc::vec::Vec, +} + +macro_rules! impl_prost_types_exact_conversion { + ($t:ident | $($arg:ident),*) => { + impl From<$t> for prost_types::$t { + fn from(src: $t) -> Self { + prost_types::$t { + $( + $arg: src.$arg, + )* + } + } + } + + impl From for $t { + fn from(src: prost_types::$t) -> Self { + $t { + $( + $arg: src.$arg, + )* + } + } + } + }; +} + +impl_prost_types_exact_conversion! { Timestamp | seconds, nanos } +impl_prost_types_exact_conversion! { Duration | seconds, nanos } +impl_prost_types_exact_conversion! { Any | type_url, value } +/// Coin defines a token with a denomination and an amount. +/// +/// NOTE: The amount field is an Int which implements the custom method +/// signatures required by gogoproto. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + ::serde::Serialize, + ::serde::Deserialize, + ::schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/cosmos.base.v1beta1.Coin")] +pub struct Coin { + #[prost(string, tag = "1")] + pub denom: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub amount: ::prost::alloc::string::String, +} +impl From for Coin { + fn from(cosmwasm_std::Coin { denom, amount }: cosmwasm_std::Coin) -> Self { + Coin { + denom, + amount: amount.into(), + } + } +} + +impl TryFrom for cosmwasm_std::Coin { + type Error = cosmwasm_std::StdError; + + fn try_from(Coin { denom, amount }: Coin) -> cosmwasm_std::StdResult { + Ok(cosmwasm_std::Coin { + denom, + amount: amount.parse()?, + }) + } +} + +/// Convert a list of `Coin` from osmosis proto generated proto `Coin` type to cosmwasm `Coin` type +pub fn try_proto_to_cosmwasm_coins( + coins: impl IntoIterator, +) -> StdResult> { + coins.into_iter().map(|c| c.try_into()).collect() +} + +/// Convert a list of `Coin` from cosmwasm `Coin` type to osmosis proto generated proto `Coin` type +pub fn cosmwasm_to_proto_coins(coins: impl IntoIterator) -> Vec { + coins.into_iter().map(|c| c.into()).collect() +} + +#[cfg(test)] +mod tests { + use cosmwasm_std::Uint128; + + use super::*; + + #[test] + fn test_coins_conversion() { + let coins = vec![ + cosmwasm_std::Coin { + denom: "uatom".to_string(), + amount: Uint128::new(100), + }, + cosmwasm_std::Coin { + denom: "uosmo".to_string(), + amount: Uint128::new(200), + }, + ]; + + let proto_coins = cosmwasm_to_proto_coins(coins.clone()); + let cosmwasm_coins = try_proto_to_cosmwasm_coins(proto_coins).unwrap(); + + assert_eq!(coins, cosmwasm_coins); + } +} diff --git a/tests/mod.rs b/tests/mod.rs index c7e7a91b..966b5f80 100644 --- a/tests/mod.rs +++ b/tests/mod.rs @@ -10,6 +10,8 @@ mod test_module; mod test_prefixed_storage; #[cfg(feature = "staking")] mod test_staking; +#[cfg(feature = "tokenfactory")] +mod test_token_factory; mod test_wasm; mod test_contracts { diff --git a/tests/test_token_factory/mod.rs b/tests/test_token_factory/mod.rs new file mode 100644 index 00000000..447e092e --- /dev/null +++ b/tests/test_token_factory/mod.rs @@ -0,0 +1 @@ +mod test_osmosis; diff --git a/tests/test_token_factory/test_osmosis.rs b/tests/test_token_factory/test_osmosis.rs new file mode 100644 index 00000000..39d8978f --- /dev/null +++ b/tests/test_token_factory/test_osmosis.rs @@ -0,0 +1,62 @@ +use cosmwasm_std::testing::mock_env; +use cw_multi_test::{denom, AppBuilder, Executor, IntoBech32, TokenFactoryStargate}; + +#[test] +fn create_denom_should_work() { + const SUBDENOM: &str = "subdenom"; + let admin_address = "admin".into_bech32(); + let user_address = "user".into_bech32(); + let mint_amount = 100_000u128; + + // prepare the blockchain configuration + let mut app = AppBuilder::default() + .with_stargate(TokenFactoryStargate) + .build(|_router, _api, _storage| {}); + + // Create Denom + app.execute( + admin_address.clone(), + cosmwasm_std::CosmosMsg::Any(cosmwasm_std::AnyMsg { + type_url: cw_multi_test::tokenfactory::osmosis::MsgCreateDenom::TYPE_URL.to_string(), + value: cw_multi_test::tokenfactory::osmosis::MsgCreateDenom { + sender: admin_address.to_string(), + subdenom: SUBDENOM.to_string(), + } + .to_proto_bytes() + .into(), + }), + ) + .unwrap(); + + // admin should be able to mint tokens + app.execute( + admin_address.clone(), + cosmwasm_std::CosmosMsg::Any(cosmwasm_std::AnyMsg { + type_url: cw_multi_test::tokenfactory::osmosis::MsgMint::TYPE_URL.to_string(), + value: cw_multi_test::tokenfactory::osmosis::MsgMint { + sender: admin_address.to_string(), + amount: Some(cw_multi_test::tokenfactory::Coin { + amount: mint_amount.to_string(), + denom: denom(admin_address.as_str(), SUBDENOM), + }), + mint_to_address: user_address.to_string(), + } + .to_proto_bytes() + .into(), + }), + ) + .unwrap(); + + // there should be no more delegations + let balance = app + .wrap() + .query_balance(user_address, denom(admin_address.as_str(), SUBDENOM)) + .unwrap(); + assert_eq!( + balance, + cosmwasm_std::Coin { + amount: mint_amount.into(), + denom: denom(admin_address.as_str(), SUBDENOM), + } + ); +} From fd94bd5eecd843f91f89a08473af4efe4128a8ab Mon Sep 17 00:00:00 2001 From: Kayanski Date: Tue, 7 Jan 2025 14:34:24 +0100 Subject: [PATCH 2/6] Added factory --- src/tokenfactory/mod.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/tokenfactory/mod.rs b/src/tokenfactory/mod.rs index 2c0dbd62..4754ebc8 100644 --- a/src/tokenfactory/mod.rs +++ b/src/tokenfactory/mod.rs @@ -22,17 +22,20 @@ const TOKENFACTORY_DENOMS: Map<(&String, &String), ()> = Map::new("tokenfactory_ /// Merge sender and subdenom to create the denom pub fn denom(sender: &str, subdenom: &str) -> String { - format!("{sender}/{subdenom}") + format!("factory/{sender}/{subdenom}") } fn split_denom(denom: &str) -> AnyResult<(String, String)> { - let split_result: Vec<&str> = denom.splitn(2, "/").collect(); + let split_result: Vec<&str> = denom.splitn(3, "/").collect(); - if split_result.len() != 2 { - bail!("Error decoding denom {denom} into sender/subdenom") + if split_result.len() != 3 { + bail!("Error decoding denom {denom} into factory/sender/subdenom") + } + if split_result[0] != "factory" { + bail!("Error decoding denom {denom} into factory/sender/subdenom") } - Ok((split_result[0].to_string(), split_result[1].to_string())) + Ok((split_result[1].to_string(), split_result[2].to_string())) } impl Stargate for TokenFactoryStargate { From 5a1446d36f12f63ec4617f8611c998852f872266 Mon Sep 17 00:00:00 2001 From: Kayanski Date: Tue, 7 Jan 2025 14:40:42 +0100 Subject: [PATCH 3/6] Added response --- src/tokenfactory/mod.rs | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/tokenfactory/mod.rs b/src/tokenfactory/mod.rs index 4754ebc8..35f0d890 100644 --- a/src/tokenfactory/mod.rs +++ b/src/tokenfactory/mod.rs @@ -107,7 +107,7 @@ impl Stargate for TokenFactoryStargate { _path: String, _data: Binary, ) -> AnyResult { - to_json_binary(&Empty {}).map_err(Into::into) + bail!("Stargate / any queries, unsupported") } fn execute_any( @@ -151,7 +151,15 @@ impl TokenFactoryStargate { TOKENFACTORY_DENOMS.save(storage, (&sender, &subdenom), &())?; - Ok(AppResponse::default()) + let data = osmosis::MsgCreateDenomResponse { + new_token_denom: denom(&sender, &subdenom), + } + .to_proto_bytes(); + + Ok(AppResponse { + data: Some(data.into()), + events: vec![], + }) } fn mint( &self, @@ -193,7 +201,12 @@ impl TokenFactoryStargate { }), )?; - Ok(AppResponse::default()) + let data = osmosis::MsgMintResponse {}.to_proto_bytes(); + + Ok(AppResponse { + data: Some(data.into()), + events: vec![], + }) } fn burn( @@ -234,7 +247,11 @@ impl TokenFactoryStargate { amount: vec![amount.try_into()?], }), )?; + let data = osmosis::MsgBurnResponse {}.to_proto_bytes(); - Ok(AppResponse::default()) + Ok(AppResponse { + data: Some(data.into()), + events: vec![], + }) } } From bc62a25f9ca9cab373b6822a4decac997e9486d3 Mon Sep 17 00:00:00 2001 From: Kayanski Date: Wed, 8 Jan 2025 16:39:58 +0100 Subject: [PATCH 4/6] More generics gp --- src/ibc/relayer/packet.rs | 62 +++++++++++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/src/ibc/relayer/packet.rs b/src/ibc/relayer/packet.rs index 3b6079f8..7f4cebb6 100644 --- a/src/ibc/relayer/packet.rs +++ b/src/ibc/relayer/packet.rs @@ -11,7 +11,7 @@ use crate::{ types::MockIbcQuery, IbcPacketRelayingMsg, }, - App, AppResponse, Bank, Distribution, Gov, Ibc, Module, Staking, SudoMsg, Wasm, + App, AppResponse, Bank, Distribution, Gov, Ibc, Module, Staking, Stargate, SudoMsg, Wasm, }; use super::{get_all_event_attr_value, get_event_attr_value, has_event}; @@ -46,6 +46,7 @@ pub fn relay_packets_in_tx< DistrT1, IbcT1, GovT1, + StargateT1, BankT2, ApiT2, StorageT2, @@ -55,9 +56,32 @@ pub fn relay_packets_in_tx< DistrT2, IbcT2, GovT2, + StargateT2, >( - app1: &mut App, - app2: &mut App, + app1: &mut App< + BankT1, + ApiT1, + StorageT1, + CustomT1, + WasmT1, + StakingT1, + DistrT1, + IbcT1, + GovT1, + StargateT1, + >, + app2: &mut App< + BankT2, + ApiT2, + StorageT2, + CustomT2, + WasmT2, + StakingT2, + DistrT2, + IbcT2, + GovT2, + StargateT2, + >, app1_tx_response: AppResponse, ) -> AnyResult> where @@ -72,6 +96,7 @@ where DistrT1: Distribution, IbcT1: Ibc, GovT1: Gov, + StargateT1: Stargate, CustomT2::ExecT: CustomMsg + DeserializeOwned + 'static, CustomT2::QueryT: CustomQuery + DeserializeOwned + 'static, @@ -84,6 +109,7 @@ where DistrT2: Distribution, IbcT2: Ibc, GovT2: Gov, + StargateT2: Stargate, { // Find all packets and their data let packets = get_all_event_attr_value(&app1_tx_response, SEND_PACKET_EVENT, "packet_sequence"); @@ -121,6 +147,7 @@ pub fn relay_packet< DistrT1, IbcT1, GovT1, + StargateT1, BankT2, ApiT2, StorageT2, @@ -130,9 +157,32 @@ pub fn relay_packet< DistrT2, IbcT2, GovT2, + StargateT2, >( - app1: &mut App, - app2: &mut App, + app1: &mut App< + BankT1, + ApiT1, + StorageT1, + CustomT1, + WasmT1, + StakingT1, + DistrT1, + IbcT1, + GovT1, + StargateT1, + >, + app2: &mut App< + BankT2, + ApiT2, + StorageT2, + CustomT2, + WasmT2, + StakingT2, + DistrT2, + IbcT2, + GovT2, + StargateT2, + >, src_port_id: String, src_channel_id: String, sequence: u64, @@ -149,6 +199,7 @@ where DistrT1: Distribution, IbcT1: Ibc, GovT1: Gov, + StargateT1: Stargate, CustomT2::ExecT: CustomMsg + DeserializeOwned + 'static, CustomT2::QueryT: CustomQuery + DeserializeOwned + 'static, @@ -161,6 +212,7 @@ where DistrT2: Distribution, IbcT2: Ibc, GovT2: Gov, + StargateT2: Stargate, { let packet: IbcPacket = from_json(app1.ibc_query(MockIbcQuery::SendPacket { channel_id: src_channel_id.clone(), From 512c898dc54c990794286db68a40241b562de39c Mon Sep 17 00:00:00 2001 From: Kayanski Date: Wed, 8 Jan 2025 16:53:51 +0100 Subject: [PATCH 5/6] Added relayer connection and channel --- src/ibc/relayer/channel.rs | 62 +++++++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/src/ibc/relayer/channel.rs b/src/ibc/relayer/channel.rs index 8639f967..5ab0e021 100644 --- a/src/ibc/relayer/channel.rs +++ b/src/ibc/relayer/channel.rs @@ -7,7 +7,7 @@ use crate::{ types::{Connection, MockIbcQuery}, IbcPacketRelayingMsg, }, - App, AppResponse, Bank, Distribution, Gov, Ibc, Module, Staking, Wasm, + App, AppResponse, Bank, Distribution, Gov, Ibc, Module, Staking, Stargate, Wasm, }; use super::get_event_attr_value; @@ -32,6 +32,7 @@ pub fn create_connection< DistrT1, IbcT1, GovT1, + StargateT1, BankT2, ApiT2, StorageT2, @@ -41,9 +42,32 @@ pub fn create_connection< DistrT2, IbcT2, GovT2, + StargateT2, >( - src_app: &mut App, - dst_app: &mut App, + src_app: &mut App< + BankT1, + ApiT1, + StorageT1, + CustomT1, + WasmT1, + StakingT1, + DistrT1, + IbcT1, + GovT1, + StargateT1, + >, + dst_app: &mut App< + BankT2, + ApiT2, + StorageT2, + CustomT2, + WasmT2, + StakingT2, + DistrT2, + IbcT2, + GovT2, + StargateT2, + >, ) -> AnyResult<(String, String)> where CustomT1::ExecT: CustomMsg + DeserializeOwned + 'static, @@ -57,6 +81,7 @@ where DistrT1: Distribution, IbcT1: Ibc, GovT1: Gov, + StargateT1: Stargate, CustomT2::ExecT: CustomMsg + DeserializeOwned + 'static, CustomT2::QueryT: CustomQuery + DeserializeOwned + 'static, @@ -69,6 +94,7 @@ where DistrT2: Distribution, IbcT2: Ibc, GovT2: Gov, + StargateT2: Stargate, { let src_connection_msg = IbcPacketRelayingMsg::CreateConnection { remote_chain_id: dst_app.block_info().chain_id, @@ -107,6 +133,7 @@ pub fn create_channel< DistrT1, IbcT1, GovT1, + StargateT1, BankT2, ApiT2, StorageT2, @@ -116,9 +143,32 @@ pub fn create_channel< DistrT2, IbcT2, GovT2, + StargateT2, >( - src_app: &mut App, - dst_app: &mut App, + src_app: &mut App< + BankT1, + ApiT1, + StorageT1, + CustomT1, + WasmT1, + StakingT1, + DistrT1, + IbcT1, + GovT1, + StargateT1, + >, + dst_app: &mut App< + BankT2, + ApiT2, + StorageT2, + CustomT2, + WasmT2, + StakingT2, + DistrT2, + IbcT2, + GovT2, + StargateT2, + >, src_connection_id: String, src_port: String, dst_port: String, @@ -137,6 +187,7 @@ where DistrT1: Distribution, IbcT1: Ibc, GovT1: Gov, + StargateT1: Stargate, CustomT2::ExecT: CustomMsg + DeserializeOwned + 'static, CustomT2::QueryT: CustomQuery + DeserializeOwned + 'static, @@ -149,6 +200,7 @@ where DistrT2: Distribution, IbcT2: Ibc, GovT2: Gov, + StargateT2: Stargate, { let ibc_init_msg = IbcPacketRelayingMsg::OpenChannel { local_connection_id: src_connection_id.clone(), From ff47638bf8ad2c418ab949b67732137d56331fdd Mon Sep 17 00:00:00 2001 From: Kayanski Date: Wed, 8 Jan 2025 17:05:10 +0100 Subject: [PATCH 6/6] Stargate customize --- src/contracts.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/contracts.rs b/src/contracts.rs index 3ce2222b..1665f016 100644 --- a/src/contracts.rs +++ b/src/contracts.rs @@ -931,6 +931,7 @@ where CosmosMsg::Ibc(ibc) => CosmosMsg::Ibc(ibc), #[cfg(feature = "cosmwasm_2_0")] CosmosMsg::Any(any) => CosmosMsg::Any(any), + CosmosMsg::Stargate { type_url, value } => CosmosMsg::Stargate { type_url, value }, other => panic!("unknown message variant {:?}", other), }, gas_limit: msg.gas_limit,