From dd6aafa5a6437ad2a9a44ac75022b606cb878a45 Mon Sep 17 00:00:00 2001 From: Buckram Date: Wed, 23 Oct 2024 14:09:40 +0300 Subject: [PATCH 1/4] add ibc boilerplate. Admin calls fails --- Cargo.toml | 5 +- cargo-generate.toml | 5 +- contracts/{{app_name}}/Cargo.toml | 5 +- .../tests/interchain_integration.rs | 122 ++++++++++++++++++ 4 files changed, 134 insertions(+), 3 deletions(-) create mode 100644 contracts/{{app_name}}/tests/interchain_integration.rs diff --git a/Cargo.toml b/Cargo.toml index 69fdeea..5698776 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,10 @@ abstract-adapter = { version = "0.24.1-beta.2" } abstract-standalone = { version = "0.24.1-beta.2" } abstract-interface = { version = "0.24.1-beta.2" } abstract-client = { version = "0.24.1-beta.2" } -cw-orch = { version = "0.25.1" } +cw-orch = { version = "0.26.0" } +{% if with_ibc %} +cw-orch-interchain = { version = "0.7.0" } +{% endif %} lazy_static = "1.4.0" const_format = "0.2.33" diff --git a/cargo-generate.toml b/cargo-generate.toml index d85149a..9e3f210 100644 --- a/cargo-generate.toml +++ b/cargo-generate.toml @@ -30,4 +30,7 @@ ignore = ["contracts/{{adapter_name}}"] ignore = ["contracts/{{standalone_name}}"] [conditional.'!with_ibc'] -ignore = ["contracts/{{app_name}}/src/ibc"] +ignore = [ + "contracts/{{app_name}}/src/ibc", + "contracts/{{app_name}}/tests/interchain_integration.rs", +] diff --git a/contracts/{{app_name}}/Cargo.toml b/contracts/{{app_name}}/Cargo.toml index bc3c774..bee8c11 100644 --- a/contracts/{{app_name}}/Cargo.toml +++ b/contracts/{{app_name}}/Cargo.toml @@ -49,6 +49,9 @@ const_format = { workspace = true } # Dependencies for interface cw-orch = { workspace = true } abstract-interface = { workspace = true } +{% if with_ibc %} +cw-orch-interchain = { workspace = true } +{% endif %} # Dependencies for bins clap = { workspace = true, optional = true, features = ["derive"] } @@ -58,5 +61,5 @@ env_logger = { workspace = true, optional = true } [dev-dependencies] {{app_name | kebab_case}} = { workspace = true } -abstract-client = { workspace = true } +abstract-client = { workspace = true{%if with_ibc %}, features = ["interchain"]{% endif %} } abstract-app = { workspace = true, features = ["test-utils"] } diff --git a/contracts/{{app_name}}/tests/interchain_integration.rs b/contracts/{{app_name}}/tests/interchain_integration.rs new file mode 100644 index 0000000..d73b215 --- /dev/null +++ b/contracts/{{app_name}}/tests/interchain_integration.rs @@ -0,0 +1,122 @@ +use {{app_name | snake_case}}::{ + contract::interface::{{app_name | upper_camel_case}}Interface, + msg::{ + ConfigResponse, CountResponse, {{app_name | upper_camel_case}}ExecuteMsg, {{app_name | upper_camel_case}}InstantiateMsg, {{app_name | upper_camel_case}}QueryMsgFns, + }, + {{app_name | upper_camel_case}}Error, TESTGEN_LOCAL_NAMESPACE, +}; + +use abstract_app::objects::namespace::Namespace; +use abstract_client::{AbstractInterchainClient, Environment, RemoteApplication}; +use cw_controllers::AdminError; +// Use prelude to get all the necessary imports +use cw_orch::{anyhow, prelude::*}; +use cw_orch_interchain::prelude::*; + +const JUNO_CHAIN_ID: &str = "juno-1"; +const OSMOSIS_CHAIN_ID: &str = "osmosis-1"; + +struct TestInterchainEnv> { + abs: AbstractInterchainClient, + remote_app: RemoteApplication>, +} + +impl TestInterchainEnv { + /// Set up the test environment with an Account that has the App installed and remote account with the same app installed + fn setup() -> anyhow::Result> { + // Create mock interchain env + let mock_interchain = + MockBech32InterchainEnv::new(vec![(JUNO_CHAIN_ID, "juno"), (OSMOSIS_CHAIN_ID, "osmo")]); + + // You can set up interchain Abstract with a helper + let abs = AbstractInterchainClient::deploy_mock(&mock_interchain)?; + let abs_juno = abs.client(JUNO_CHAIN_ID).unwrap(); + let abs_osmosis = abs.client(OSMOSIS_CHAIN_ID).unwrap(); + + // Publish on remote chain + let namespace = Namespace::new(TESTGEN_LOCAL_NAMESPACE)?; + let publisher_osmosis = abs_osmosis.publisher_builder(namespace).build()?; + publisher_osmosis.publish_app::<{{app_name | upper_camel_case}}Interface<_>>()?; + + // To create remote account you need to enable ibc by installing ibc-client on account + let account_juno = abs_juno.account_builder().build()?; + account_juno.set_ibc_status(true)?; + // Create remote account with app installed + let remote_account = account_juno + .remote_account_builder(mock_interchain.clone(), &abs_osmosis) + .install_app::<{{app_name | upper_camel_case}}Interface>(&{{app_name | upper_camel_case}}InstantiateMsg { count: 0 })? + .build()?; + let remote_app = remote_account.application::<{{app_name | upper_camel_case}}Interface<_>>()?; + + Ok(TestInterchainEnv { abs, remote_app }) + } +} + +#[test] +fn successful_install() -> anyhow::Result<()> { + // Start by deploying abstract completely + let env = TestInterchainEnv::setup()?; + let remote_app = env.remote_app; + + let config = remote_app.config()?; + assert_eq!(config, ConfigResponse {}); + Ok(()) +} + +#[test] +fn successful_increment() -> anyhow::Result<()> { + let env = TestInterchainEnv::setup()?; + let remote_app = env.remote_app; + + let app_msg = {{app_name | upper_camel_case}}ExecuteMsg::Increment {}.into(); + remote_app.execute(&app_msg, vec![])?; + let count: CountResponse = remote_app.count()?; + assert_eq!(count.count, 1); + Ok(()) +} + +#[test] +fn successful_reset() -> anyhow::Result<()> { + let env = TestInterchainEnv::setup()?; + let remote_app = env.remote_app; + + let app_msg = {{app_name | upper_camel_case}}ExecuteMsg::Reset { count: 42 }.into(); + remote_app.execute(&app_msg, vec![])?; + let count: CountResponse = remote_app.count()?; + assert_eq!(count.count, 42); + Ok(()) +} + +#[test] +fn failed_reset() -> anyhow::Result<()> { + let env = TestInterchainEnv::setup()?; + let remote_app = env.remote_app; + let remote_chain = env.abs.client(OSMOSIS_CHAIN_ID).unwrap().environment(); + + // Only your account can execute on your remote application + let remote_app_address = remote_app.address()?; + let err: {{app_name | upper_camel_case}}Error = remote_chain + .execute( + &{{app_name | snake_case}}::msg::ExecuteMsg::from({{app_name | upper_camel_case}}ExecuteMsg::Reset { count: 9 }), + &[], + &remote_app_address, + ) + .unwrap_err() + .downcast() + .unwrap(); + assert_eq!(err, {{app_name | upper_camel_case}}Error::Admin(AdminError::NotAdmin {})); + Ok(()) +} + +#[test] +fn update_config() -> anyhow::Result<()> { + let env = TestInterchainEnv::setup()?; + let remote_app = env.remote_app; + + let app_msg = {{app_name | upper_camel_case}}ExecuteMsg::UpdateConfig {}.into(); + remote_app.execute(&app_msg, vec![])?; + let config = remote_app.config()?; + let expected_response = {{app_name | snake_case}}::msg::ConfigResponse {}; + assert_eq!(config, expected_response); + Ok(()) +} From e2051c7929b3ec500f640ee54a6642f565605781 Mon Sep 17 00:00:00 2001 From: Buckram Date: Tue, 29 Oct 2024 14:09:23 +0200 Subject: [PATCH 2/4] update abstract version --- Cargo.toml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5698776..93195a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,14 +22,14 @@ thiserror = { version = "1.0.64" } schemars = "0.8" cw-asset = { version = "4.0.0" } -abstract-app = { version = "0.24.1-beta.2" } -abstract-adapter = { version = "0.24.1-beta.2" } -abstract-standalone = { version = "0.24.1-beta.2" } -abstract-interface = { version = "0.24.1-beta.2" } -abstract-client = { version = "0.24.1-beta.2" } -cw-orch = { version = "0.26.0" } +abstract-app = { version = "0.24.1" } +abstract-adapter = { version = "0.24.1" } +abstract-standalone = { version = "0.24.1" } +abstract-interface = { version = "0.24.1" } +abstract-client = { version = "0.24.1" } +cw-orch = { version = "0.27.0" } {% if with_ibc %} -cw-orch-interchain = { version = "0.7.0" } +cw-orch-interchain = { version = "0.8.0" } {% endif %} lazy_static = "1.4.0" From 601c538a896e0b558276cf4ed5696d50baf5646b Mon Sep 17 00:00:00 2001 From: Buckram Date: Wed, 30 Oct 2024 18:00:55 +0200 Subject: [PATCH 3/4] update and un-ibc app template --- Cargo.toml | 7 +- cargo-generate.toml | 14 +- contracts/{{adapter_name}}/src/bin/publish.rs | 6 +- .../{{adapter_name}}/tests/integration.rs | 12 +- contracts/{{app_name}}/Cargo.toml | 5 +- contracts/{{app_name}}/src/bin/publish.rs | 6 +- contracts/{{app_name}}/src/contract.rs | 4 +- contracts/{{app_name}}/src/ibc/callback.rs | 19 --- contracts/{{app_name}}/src/ibc/mod.rs | 5 - contracts/{{app_name}}/src/ibc/module.rs | 21 --- contracts/{{app_name}}/src/lib.rs | 2 - contracts/{{app_name}}/src/msg.rs | 11 -- contracts/{{app_name}}/tests/integration.rs | 6 +- .../tests/interchain_integration.rs | 122 ------------------ .../{{standalone_name}}/src/bin/publish.rs | 6 +- .../{{standalone_name}}/tests/integration.rs | 6 +- 16 files changed, 45 insertions(+), 207 deletions(-) delete mode 100644 contracts/{{app_name}}/src/ibc/callback.rs delete mode 100644 contracts/{{app_name}}/src/ibc/mod.rs delete mode 100644 contracts/{{app_name}}/src/ibc/module.rs delete mode 100644 contracts/{{app_name}}/tests/interchain_integration.rs diff --git a/Cargo.toml b/Cargo.toml index 93195a0..4884070 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ abstract-standalone = { version = "0.24.1" } abstract-interface = { version = "0.24.1" } abstract-client = { version = "0.24.1" } cw-orch = { version = "0.27.0" } -{% if with_ibc %} +{% if include_ibc_app %} cw-orch-interchain = { version = "0.8.0" } {% endif %} @@ -48,3 +48,8 @@ clap = { version = "4.5.19" } {% if include_standalone %} {{standalone_name | kebab_case}} = { path = "contracts/{{standalone_name}}" } {% endif %} + +[patch.crates-io] +abstract-client = { git = "https://github.com/AbstractSDK/abstract.git", branch = "buckram/fix-abstract-client-fetch_or_build_account" } +abstract-std = { git = "https://github.com/AbstractSDK/abstract.git", branch = "buckram/fix-abstract-client-fetch_or_build_account" } +abstract-interface = { git = "https://github.com/AbstractSDK/abstract.git", branch = "buckram/fix-abstract-client-fetch_or_build_account" } diff --git a/cargo-generate.toml b/cargo-generate.toml index 9e3f210..d549a89 100644 --- a/cargo-generate.toml +++ b/cargo-generate.toml @@ -9,10 +9,9 @@ post = ["post-script.rhai"] include_app = { prompt = "Include App Module", default = true, type = "bool" } include_adapter = { prompt = "Include Adapter Module", default = true, type = "bool" } include_standalone = { prompt = "Include Standalone Module", default = true, type = "bool" } +include_ibc_app = { prompt = "Include IBC App Module", default = true, type = "bool" } [conditional.'include_app'.placeholders] -# App can have IBC -with_ibc = { prompt = "Would you like to include IBC template in the app?", default = false, type = "bool" } app_name = { prompt = "Enter App Name", default = "my_app", type = "string" } [conditional.'include_adapter'.placeholders] @@ -21,6 +20,9 @@ adapter_name = { prompt = "Enter Adapter Name", default = "my_adapter", type = " [conditional.'include_standalone'.placeholders] standalone_name = { prompt = "Enter Standalone Name", default = "my_standalone", type = "string" } +[conditional.'include_ibc_app'.placeholders] +ibc_app_name = { prompt = "Enter IBC App Name", default = "my_ibc_app", type = "string" } + # Ignore files if not included [conditional.'!include_app'] ignore = ["contracts/{{app_name}}"] @@ -28,9 +30,5 @@ ignore = ["contracts/{{app_name}}"] ignore = ["contracts/{{adapter_name}}"] [conditional.'!include_standalone'] ignore = ["contracts/{{standalone_name}}"] - -[conditional.'!with_ibc'] -ignore = [ - "contracts/{{app_name}}/src/ibc", - "contracts/{{app_name}}/tests/interchain_integration.rs", -] +[conditional.'!include_ibc_app'] +ignore = ["contracts/{{ibc_app_name}}"] diff --git a/contracts/{{adapter_name}}/src/bin/publish.rs b/contracts/{{adapter_name}}/src/bin/publish.rs index a83351a..22aa14d 100644 --- a/contracts/{{adapter_name}}/src/bin/publish.rs +++ b/contracts/{{adapter_name}}/src/bin/publish.rs @@ -32,8 +32,10 @@ fn publish(networks: Vec) -> anyhow::Result<()> { // Get the [`Publisher`] that owns the namespace, otherwise create a new one and claim the namespace let publisher: Publisher<_> = abstract_client - .publisher_builder(adapter_namespace) - .build()?; + .account_builder() + .namespace(adapter_namespace) + .build()? + .publisher()?; if publisher.account().owner()? != chain.sender_addr() { panic!("The current sender can not publish to this namespace. Please use the wallet that owns the Account that owns the Namespace.") diff --git a/contracts/{{adapter_name}}/tests/integration.rs b/contracts/{{adapter_name}}/tests/integration.rs index ea926cd..061299b 100644 --- a/contracts/{{adapter_name}}/tests/integration.rs +++ b/contracts/{{adapter_name}}/tests/integration.rs @@ -31,7 +31,11 @@ impl TestEnv { abs_client.set_balance(&sender, &coins(123, "ucosm"))?; // Publish the adapter - let publisher = abs_client.publisher_builder(namespace).build()?; + let publisher = abs_client + .account_builder() + .namespace(namespace) + .build()? + .publisher()?; publisher.publish_adapter::<{{adapter_name | upper_camel_case}}InstantiateMsg, {{adapter_name | upper_camel_case}}Interface<_>>( {{adapter_name | upper_camel_case}}InstantiateMsg {}, )?; @@ -67,8 +71,8 @@ fn update_config() -> anyhow::Result<()> { // Note that it's not a requirement to have it installed in this case let publisher_account = env .abs - .publisher_builder(Namespace::new({{project-name | shouty_snake_case}}_NAMESPACE).unwrap()) - .build()?; + .fetch_account(Namespace::new({{project-name | shouty_snake_case}}_NAMESPACE).unwrap())? + .publisher()?; adapter.execute( &AdapterRequestMsg { @@ -109,7 +113,7 @@ fn set_status() -> anyhow::Result<()> { let new_account = env .abs .account_builder() - .install_adapter::<{{adapter_name | upper_camel_case}}Interface>()? + .install_adapter::<{{adapter_name | upper_camel_case}}Interface>() .build()?; new_account.as_ref().execute_on_module( diff --git a/contracts/{{app_name}}/Cargo.toml b/contracts/{{app_name}}/Cargo.toml index bee8c11..bc3c774 100644 --- a/contracts/{{app_name}}/Cargo.toml +++ b/contracts/{{app_name}}/Cargo.toml @@ -49,9 +49,6 @@ const_format = { workspace = true } # Dependencies for interface cw-orch = { workspace = true } abstract-interface = { workspace = true } -{% if with_ibc %} -cw-orch-interchain = { workspace = true } -{% endif %} # Dependencies for bins clap = { workspace = true, optional = true, features = ["derive"] } @@ -61,5 +58,5 @@ env_logger = { workspace = true, optional = true } [dev-dependencies] {{app_name | kebab_case}} = { workspace = true } -abstract-client = { workspace = true{%if with_ibc %}, features = ["interchain"]{% endif %} } +abstract-client = { workspace = true } abstract-app = { workspace = true, features = ["test-utils"] } diff --git a/contracts/{{app_name}}/src/bin/publish.rs b/contracts/{{app_name}}/src/bin/publish.rs index 2c8f361..f6b77cf 100644 --- a/contracts/{{app_name}}/src/bin/publish.rs +++ b/contracts/{{app_name}}/src/bin/publish.rs @@ -30,7 +30,11 @@ fn publish(networks: Vec) -> anyhow::Result<()> { let abstract_client: AbstractClient = AbstractClient::new(chain.clone())?; // Get the [`Publisher`] that owns the namespace, otherwise create a new one and claim the namespace - let publisher: Publisher<_> = abstract_client.publisher_builder(app_namespace).build()?; + let publisher: Publisher<_> = abstract_client + .account_builder() + .namespace(app_namespace) + .build()? + .publisher()?; if publisher.account().owner()? != chain.sender_addr() { panic!("The current sender can not publish to this namespace. Please use the wallet that owns the Account that owns the Namespace.") diff --git a/contracts/{{app_name}}/src/contract.rs b/contracts/{{app_name}}/src/contract.rs index d552ef1..89cc677 100644 --- a/contracts/{{app_name}}/src/contract.rs +++ b/contracts/{{app_name}}/src/contract.rs @@ -23,9 +23,7 @@ const APP: {{app_name | upper_camel_case}} = {{app_name | upper_camel_case}}::ne .with_execute(handlers::execute_handler) .with_query(handlers::query_handler) .with_migrate(handlers::migrate_handler) - .with_replies(&[(INSTANTIATE_REPLY_ID, replies::instantiate_reply)]){% if with_ibc %} - .with_ibc_callback(crate::ibc::ibc_callback) - .with_module_ibc(crate::ibc::receive_module_ibc){% endif %} + .with_replies(&[(INSTANTIATE_REPLY_ID, replies::instantiate_reply)]) .with_dependencies(&[]); // Export handlers diff --git a/contracts/{{app_name}}/src/ibc/callback.rs b/contracts/{{app_name}}/src/ibc/callback.rs deleted file mode 100644 index c2fd002..0000000 --- a/contracts/{{app_name}}/src/ibc/callback.rs +++ /dev/null @@ -1,19 +0,0 @@ -use abstract_app::{ - sdk::AbstractResponse, - std::ibc::{Callback, IbcResult}, -}; -use cosmwasm_std::{DepsMut, Env}; - -use crate::contract::{ - {{app_name | upper_camel_case}}, {{app_name | upper_camel_case}}Result -}; - -pub fn ibc_callback( - _deps: DepsMut, - _env: Env, - module: {{app_name | upper_camel_case}}, - _callback: Callback, - _result: IbcResult, -) -> {{app_name | upper_camel_case}}Result { - Ok(module.response("callback")) -} \ No newline at end of file diff --git a/contracts/{{app_name}}/src/ibc/mod.rs b/contracts/{{app_name}}/src/ibc/mod.rs deleted file mode 100644 index 879cb2d..0000000 --- a/contracts/{{app_name}}/src/ibc/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod callback; -mod module; - -pub use callback::ibc_callback; -pub use module::receive_module_ibc; diff --git a/contracts/{{app_name}}/src/ibc/module.rs b/contracts/{{app_name}}/src/ibc/module.rs deleted file mode 100644 index 7817711..0000000 --- a/contracts/{{app_name}}/src/ibc/module.rs +++ /dev/null @@ -1,21 +0,0 @@ -use abstract_app::std::ibc::ModuleIbcInfo; -use cosmwasm_std::{Binary, DepsMut, Env, Response, from_json}; - -use crate::{ - contract::{ - {{app_name | upper_camel_case}}, {{app_name | upper_camel_case}}Result - }, - msg::IbcMsg, -}; - -pub fn receive_module_ibc( - _deps: DepsMut, - _env: Env, - _module: {{app_name | upper_camel_case}}, - _source_module: ModuleIbcInfo, - msg: Binary, -) -> {{app_name | upper_camel_case}}Result { - let _msg: IbcMsg = from_json(&msg)?; - // do something with received _msg - Ok(Response::new()) -} diff --git a/contracts/{{app_name}}/src/lib.rs b/contracts/{{app_name}}/src/lib.rs index 54edede..70e15d2 100644 --- a/contracts/{{app_name}}/src/lib.rs +++ b/contracts/{{app_name}}/src/lib.rs @@ -5,8 +5,6 @@ pub mod msg; mod replies; pub mod state; -{% if with_ibc %}pub mod ibc;{% endif %} - pub use error::{{app_name | upper_camel_case}}Error; /// The version of your app diff --git a/contracts/{{app_name}}/src/msg.rs b/contracts/{{app_name}}/src/msg.rs index 804f569..9d3d756 100644 --- a/contracts/{{app_name}}/src/msg.rs +++ b/contracts/{{app_name}}/src/msg.rs @@ -45,14 +45,3 @@ pub struct ConfigResponse {} pub struct CountResponse { pub count: i32, } - -{% if with_ibc %} -#[cosmwasm_schema::cw_serde] -pub enum IbcCallbackMsg { - Empty {}, -} - -#[cosmwasm_schema::cw_serde] -pub struct IbcMsg { -} -{% endif %} \ No newline at end of file diff --git a/contracts/{{app_name}}/tests/integration.rs b/contracts/{{app_name}}/tests/integration.rs index 22ec346..e25a77b 100644 --- a/contracts/{{app_name}}/tests/integration.rs +++ b/contracts/{{app_name}}/tests/integration.rs @@ -32,7 +32,11 @@ impl TestEnv { abs_client.set_balance(&sender, &coins(123, "ucosm"))?; // Publish the app - let publisher = abs_client.publisher_builder(namespace).build()?; + let publisher = abs_client + .account_builder() + .namespace(namespace) + .build()? + .publisher()?; publisher.publish_app::<{{app_name | upper_camel_case}}Interface<_>>()?; let app = publisher diff --git a/contracts/{{app_name}}/tests/interchain_integration.rs b/contracts/{{app_name}}/tests/interchain_integration.rs deleted file mode 100644 index d73b215..0000000 --- a/contracts/{{app_name}}/tests/interchain_integration.rs +++ /dev/null @@ -1,122 +0,0 @@ -use {{app_name | snake_case}}::{ - contract::interface::{{app_name | upper_camel_case}}Interface, - msg::{ - ConfigResponse, CountResponse, {{app_name | upper_camel_case}}ExecuteMsg, {{app_name | upper_camel_case}}InstantiateMsg, {{app_name | upper_camel_case}}QueryMsgFns, - }, - {{app_name | upper_camel_case}}Error, TESTGEN_LOCAL_NAMESPACE, -}; - -use abstract_app::objects::namespace::Namespace; -use abstract_client::{AbstractInterchainClient, Environment, RemoteApplication}; -use cw_controllers::AdminError; -// Use prelude to get all the necessary imports -use cw_orch::{anyhow, prelude::*}; -use cw_orch_interchain::prelude::*; - -const JUNO_CHAIN_ID: &str = "juno-1"; -const OSMOSIS_CHAIN_ID: &str = "osmosis-1"; - -struct TestInterchainEnv> { - abs: AbstractInterchainClient, - remote_app: RemoteApplication>, -} - -impl TestInterchainEnv { - /// Set up the test environment with an Account that has the App installed and remote account with the same app installed - fn setup() -> anyhow::Result> { - // Create mock interchain env - let mock_interchain = - MockBech32InterchainEnv::new(vec![(JUNO_CHAIN_ID, "juno"), (OSMOSIS_CHAIN_ID, "osmo")]); - - // You can set up interchain Abstract with a helper - let abs = AbstractInterchainClient::deploy_mock(&mock_interchain)?; - let abs_juno = abs.client(JUNO_CHAIN_ID).unwrap(); - let abs_osmosis = abs.client(OSMOSIS_CHAIN_ID).unwrap(); - - // Publish on remote chain - let namespace = Namespace::new(TESTGEN_LOCAL_NAMESPACE)?; - let publisher_osmosis = abs_osmosis.publisher_builder(namespace).build()?; - publisher_osmosis.publish_app::<{{app_name | upper_camel_case}}Interface<_>>()?; - - // To create remote account you need to enable ibc by installing ibc-client on account - let account_juno = abs_juno.account_builder().build()?; - account_juno.set_ibc_status(true)?; - // Create remote account with app installed - let remote_account = account_juno - .remote_account_builder(mock_interchain.clone(), &abs_osmosis) - .install_app::<{{app_name | upper_camel_case}}Interface>(&{{app_name | upper_camel_case}}InstantiateMsg { count: 0 })? - .build()?; - let remote_app = remote_account.application::<{{app_name | upper_camel_case}}Interface<_>>()?; - - Ok(TestInterchainEnv { abs, remote_app }) - } -} - -#[test] -fn successful_install() -> anyhow::Result<()> { - // Start by deploying abstract completely - let env = TestInterchainEnv::setup()?; - let remote_app = env.remote_app; - - let config = remote_app.config()?; - assert_eq!(config, ConfigResponse {}); - Ok(()) -} - -#[test] -fn successful_increment() -> anyhow::Result<()> { - let env = TestInterchainEnv::setup()?; - let remote_app = env.remote_app; - - let app_msg = {{app_name | upper_camel_case}}ExecuteMsg::Increment {}.into(); - remote_app.execute(&app_msg, vec![])?; - let count: CountResponse = remote_app.count()?; - assert_eq!(count.count, 1); - Ok(()) -} - -#[test] -fn successful_reset() -> anyhow::Result<()> { - let env = TestInterchainEnv::setup()?; - let remote_app = env.remote_app; - - let app_msg = {{app_name | upper_camel_case}}ExecuteMsg::Reset { count: 42 }.into(); - remote_app.execute(&app_msg, vec![])?; - let count: CountResponse = remote_app.count()?; - assert_eq!(count.count, 42); - Ok(()) -} - -#[test] -fn failed_reset() -> anyhow::Result<()> { - let env = TestInterchainEnv::setup()?; - let remote_app = env.remote_app; - let remote_chain = env.abs.client(OSMOSIS_CHAIN_ID).unwrap().environment(); - - // Only your account can execute on your remote application - let remote_app_address = remote_app.address()?; - let err: {{app_name | upper_camel_case}}Error = remote_chain - .execute( - &{{app_name | snake_case}}::msg::ExecuteMsg::from({{app_name | upper_camel_case}}ExecuteMsg::Reset { count: 9 }), - &[], - &remote_app_address, - ) - .unwrap_err() - .downcast() - .unwrap(); - assert_eq!(err, {{app_name | upper_camel_case}}Error::Admin(AdminError::NotAdmin {})); - Ok(()) -} - -#[test] -fn update_config() -> anyhow::Result<()> { - let env = TestInterchainEnv::setup()?; - let remote_app = env.remote_app; - - let app_msg = {{app_name | upper_camel_case}}ExecuteMsg::UpdateConfig {}.into(); - remote_app.execute(&app_msg, vec![])?; - let config = remote_app.config()?; - let expected_response = {{app_name | snake_case}}::msg::ConfigResponse {}; - assert_eq!(config, expected_response); - Ok(()) -} diff --git a/contracts/{{standalone_name}}/src/bin/publish.rs b/contracts/{{standalone_name}}/src/bin/publish.rs index 414d5a0..5a0deb2 100644 --- a/contracts/{{standalone_name}}/src/bin/publish.rs +++ b/contracts/{{standalone_name}}/src/bin/publish.rs @@ -31,8 +31,10 @@ fn publish(networks: Vec) -> anyhow::Result<()> { // Get the [`Publisher`] that owns the namespace, otherwise create a new one and claim the namespace let publisher: Publisher<_> = abstract_client - .publisher_builder(standalone_namespace) - .build()?; + .account_builder() + .namespace(standalone_namespace) + .build()? + .publisher()?; if publisher.account().owner()? != chain.sender_addr() { panic!("The current sender can not publish to this namespace. Please use the wallet that owns the Account that owns the Namespace.") diff --git a/contracts/{{standalone_name}}/tests/integration.rs b/contracts/{{standalone_name}}/tests/integration.rs index e86ec09..82a63f5 100644 --- a/contracts/{{standalone_name}}/tests/integration.rs +++ b/contracts/{{standalone_name}}/tests/integration.rs @@ -32,7 +32,11 @@ impl TestEnv { abs_client.set_balance(&sender, &coins(123, "ucosm"))?; // Publish the standalone - let publisher = abs_client.publisher_builder(namespace).build()?; + let publisher = abs_client + .account_builder() + .namespace(namespace) + .build()? + .publisher()?; publisher.publish_standalone::<{{standalone_name | upper_camel_case}}Interface<_>>()?; let standalone = publisher From 66ea405ea7f7b63951a29e31c89ef5855a0a9bbc Mon Sep 17 00:00:00 2001 From: Buckram Date: Thu, 31 Oct 2024 14:53:37 +0200 Subject: [PATCH 4/4] simplified ping-pong --- Cargo.toml | 1 - contracts/{{adapter_name}}/Cargo.toml | 1 - contracts/{{app_name}}/Cargo.toml | 1 - contracts/{{app_name}}/src/contract.rs | 2 +- contracts/{{ibc_app_name}}/Cargo.toml | 50 ++++++ contracts/{{ibc_app_name}}/metadata.json | 9 + contracts/{{ibc_app_name}}/src/bin/publish.rs | 65 +++++++ contracts/{{ibc_app_name}}/src/bin/schema.rs | 13 ++ contracts/{{ibc_app_name}}/src/contract.rs | 60 +++++++ contracts/{{ibc_app_name}}/src/error.rs | 32 ++++ .../{{ibc_app_name}}/src/handlers/execute.rs | 49 ++++++ .../src/handlers/instantiate.rs | 16 ++ .../{{ibc_app_name}}/src/handlers/mod.rs | 7 + .../{{ibc_app_name}}/src/handlers/query.rs | 34 ++++ .../{{ibc_app_name}}/src/ibc/callback.rs | 41 +++++ contracts/{{ibc_app_name}}/src/ibc/mod.rs | 5 + contracts/{{ibc_app_name}}/src/ibc/module.rs | 34 ++++ contracts/{{ibc_app_name}}/src/lib.rs | 17 ++ contracts/{{ibc_app_name}}/src/msg.rs | 46 +++++ contracts/{{ibc_app_name}}/src/state.rs | 5 + .../{{ibc_app_name}}/tests/ping_pong_tests.rs | 165 ++++++++++++++++++ contracts/{{standalone_name}}/Cargo.toml | 1 - .../{{standalone_name}}/src/interface.rs | 4 +- 23 files changed, 651 insertions(+), 7 deletions(-) create mode 100644 contracts/{{ibc_app_name}}/Cargo.toml create mode 100644 contracts/{{ibc_app_name}}/metadata.json create mode 100644 contracts/{{ibc_app_name}}/src/bin/publish.rs create mode 100644 contracts/{{ibc_app_name}}/src/bin/schema.rs create mode 100644 contracts/{{ibc_app_name}}/src/contract.rs create mode 100644 contracts/{{ibc_app_name}}/src/error.rs create mode 100644 contracts/{{ibc_app_name}}/src/handlers/execute.rs create mode 100644 contracts/{{ibc_app_name}}/src/handlers/instantiate.rs create mode 100644 contracts/{{ibc_app_name}}/src/handlers/mod.rs create mode 100644 contracts/{{ibc_app_name}}/src/handlers/query.rs create mode 100644 contracts/{{ibc_app_name}}/src/ibc/callback.rs create mode 100644 contracts/{{ibc_app_name}}/src/ibc/mod.rs create mode 100644 contracts/{{ibc_app_name}}/src/ibc/module.rs create mode 100644 contracts/{{ibc_app_name}}/src/lib.rs create mode 100644 contracts/{{ibc_app_name}}/src/msg.rs create mode 100644 contracts/{{ibc_app_name}}/src/state.rs create mode 100644 contracts/{{ibc_app_name}}/tests/ping_pong_tests.rs diff --git a/Cargo.toml b/Cargo.toml index 4884070..fb24c7c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,6 @@ cw-asset = { version = "4.0.0" } abstract-app = { version = "0.24.1" } abstract-adapter = { version = "0.24.1" } abstract-standalone = { version = "0.24.1" } -abstract-interface = { version = "0.24.1" } abstract-client = { version = "0.24.1" } cw-orch = { version = "0.27.0" } {% if include_ibc_app %} diff --git a/contracts/{{adapter_name}}/Cargo.toml b/contracts/{{adapter_name}}/Cargo.toml index cc336f2..6379def 100644 --- a/contracts/{{adapter_name}}/Cargo.toml +++ b/contracts/{{adapter_name}}/Cargo.toml @@ -48,7 +48,6 @@ const_format = { workspace = true } # Dependencies for interface cw-orch = { workspace = true } -abstract-interface = { workspace = true } # Dependencies for bins clap = { workspace = true, optional = true, features = ["derive"] } diff --git a/contracts/{{app_name}}/Cargo.toml b/contracts/{{app_name}}/Cargo.toml index bc3c774..106f4c4 100644 --- a/contracts/{{app_name}}/Cargo.toml +++ b/contracts/{{app_name}}/Cargo.toml @@ -48,7 +48,6 @@ const_format = { workspace = true } # Dependencies for interface cw-orch = { workspace = true } -abstract-interface = { workspace = true } # Dependencies for bins clap = { workspace = true, optional = true, features = ["derive"] } diff --git a/contracts/{{app_name}}/src/contract.rs b/contracts/{{app_name}}/src/contract.rs index 89cc677..88c825f 100644 --- a/contracts/{{app_name}}/src/contract.rs +++ b/contracts/{{app_name}}/src/contract.rs @@ -35,7 +35,7 @@ abstract_app::cw_orch_interface!(APP, {{app_name | upper_camel_case}}, {{app_nam // TODO: add to docmuentation // https://linear.app/abstract-sdk/issue/ABS-414/add-documentation-on-dependencycreation-trait #[cfg(not(target_arch = "wasm32"))] -impl abstract_interface::DependencyCreation +impl abstract_app::abstract_interface::DependencyCreation for crate::{{app_name | upper_camel_case}}Interface { type DependenciesConfig = cosmwasm_std::Empty; diff --git a/contracts/{{ibc_app_name}}/Cargo.toml b/contracts/{{ibc_app_name}}/Cargo.toml new file mode 100644 index 0000000..182be37 --- /dev/null +++ b/contracts/{{ibc_app_name}}/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "ping-pong" +version = "0.1.0" +edition = "2021" + +[features] +default = ["export"] +export = [] +daemon-bin = [ + "cw-orch/daemon", + "dep:clap", + "dep:abstract-client", + "dep:dotenv", + "dep:env_logger", +] +schema = ["abstract-app/schema"] + +[lib] +crate-type = ["cdylib", "rlib"] +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cosmwasm-std = { workspace = true } +cosmwasm-schema = { workspace = true } +cw-controllers = { workspace = true } +cw-storage-plus = { workspace = true } +thiserror = { workspace = true } +schemars = { workspace = true } +cw-asset = { workspace = true } +abstract-app = { workspace = true } +abstract-ibc-client = { version = "0.24.1", default-features = false } +const_format = { workspace = true } + +# Dependencies for interface +cw-orch = { workspace = true } +abstract-interface = { workspace = true } + +# Dependencies for bins +clap = { workspace = true, optional = true, features = ["derive"] } +abstract-client = { workspace = true, features = [ + "interchain", +], optional = true } +dotenv = { workspace = true, optional = true } +env_logger = { workspace = true, optional = true } + +[dev-dependencies] +cw-orch-interchain = { workspace = true, features = ["daemon"] } +cw-orch = { workspace = true, features = ["daemon"] } +abstract-client = { workspace = true, features = ["interchain"] } +env_logger = "0.11" diff --git a/contracts/{{ibc_app_name}}/metadata.json b/contracts/{{ibc_app_name}}/metadata.json new file mode 100644 index 0000000..544344a --- /dev/null +++ b/contracts/{{ibc_app_name}}/metadata.json @@ -0,0 +1,9 @@ +{ + "name": "MyIbcApp", + "description": "IBC app description.", + "website": "", + "docs": "", + "type": "app", + "icon": "GiTrade", + "enabled": true +} diff --git a/contracts/{{ibc_app_name}}/src/bin/publish.rs b/contracts/{{ibc_app_name}}/src/bin/publish.rs new file mode 100644 index 0000000..b67ff50 --- /dev/null +++ b/contracts/{{ibc_app_name}}/src/bin/publish.rs @@ -0,0 +1,65 @@ +//! Publishes the module to the Abstract platform by uploading it and registering it on the app store. +//! +//! Info: The mnemonic used to register the module must be the same as the owner of the account that claimed the namespace. +//! +//! ## Example +//! +//! ```bash +//! $ just publish test-app uni-6 osmo-test-5 +//! ``` +use ping_pong::PING_PONG_ID; + +use abstract_app::objects::namespace::Namespace; +use abstract_client::{AbstractClient, Publisher}; +use clap::Parser; +use cw_orch::{anyhow, daemon::networks::parse_network, prelude::*, tokio::runtime::Runtime}; +use ping_pong::PingPongInterface; + +fn publish(networks: Vec) -> anyhow::Result<()> { + // run for each requested network + for network in networks { + // Setup + let rt = Runtime::new()?; + let chain = DaemonBuilder::new(network).handle(rt.handle()).build()?; + + let app_namespace = Namespace::from_id(PING_PONG_ID)?; + + // Create an [`AbstractClient`] + let abstract_client: AbstractClient = AbstractClient::new(chain.clone())?; + + // Get the [`Publisher`] that owns the namespace, otherwise create a new one and claim the namespace + let publisher: Publisher<_> = abstract_client + .account_builder() + .namespace(app_namespace) + .build()? + .publisher()?; + + if publisher.account().owner()? != chain.sender_addr() { + panic!("The current sender can not publish to this namespace. Please use the wallet that owns the Account that owns the Namespace.") + } + + // Publish the App to the Abstract Platform + publisher.publish_app::>()?; + } + Ok(()) +} + +#[derive(Parser, Default, Debug)] +#[command(author, version, about, long_about = None)] +struct Arguments { + /// Network Id to publish on + #[arg(short, long, value_delimiter = ' ', num_args = 1..)] + network_ids: Vec, +} + +fn main() { + dotenv::dotenv().ok(); + env_logger::init(); + let args = Arguments::parse(); + let networks = args + .network_ids + .iter() + .map(|n| parse_network(n).unwrap()) + .collect(); + publish(networks).unwrap(); +} diff --git a/contracts/{{ibc_app_name}}/src/bin/schema.rs b/contracts/{{ibc_app_name}}/src/bin/schema.rs new file mode 100644 index 0000000..3ab47ac --- /dev/null +++ b/contracts/{{ibc_app_name}}/src/bin/schema.rs @@ -0,0 +1,13 @@ +use cosmwasm_schema::remove_schemas; +use ping_pong::contract::PingPong; +use std::env::current_dir; +use std::fs::create_dir_all; + +fn main() { + let mut out_dir = current_dir().unwrap(); + out_dir.push("schema"); + create_dir_all(&out_dir).unwrap(); + remove_schemas(&out_dir).unwrap(); + + PingPong::export_schema(&out_dir); +} diff --git a/contracts/{{ibc_app_name}}/src/contract.rs b/contracts/{{ibc_app_name}}/src/contract.rs new file mode 100644 index 0000000..89baed0 --- /dev/null +++ b/contracts/{{ibc_app_name}}/src/contract.rs @@ -0,0 +1,60 @@ +use abstract_app::{objects::dependency::StaticDependency, std::IBC_CLIENT, AppContract}; +use cosmwasm_std::Response; + +use crate::{ + error::PingPongError, + handlers, ibc, + msg::{AppMigrateMsg, PingPongExecuteMsg, PingPongInstantiateMsg, PingPongQueryMsg}, + APP_VERSION, PING_PONG_ID, +}; + +/// The type of the result returned by your app's entry points. +pub type PingPongResult = Result; + +/// The type of the app that is used to build your app and access the Abstract SDK features. +pub type PingPong = AppContract< + PingPongError, + PingPongInstantiateMsg, + PingPongExecuteMsg, + PingPongQueryMsg, + AppMigrateMsg, +>; + +const APP: PingPong = PingPong::new(PING_PONG_ID, APP_VERSION, None) + .with_instantiate(handlers::instantiate_handler) + .with_execute(handlers::execute_handler) + .with_query(handlers::query_handler) + .with_dependencies(&[StaticDependency::new( + IBC_CLIENT, + &[abstract_ibc_client::contract::CONTRACT_VERSION], + )]) + .with_module_ibc(ibc::receive_module_ibc) + .with_ibc_callback(ibc::ibc_callback); + +// Export handlers +#[cfg(feature = "export")] +abstract_app::export_endpoints!(APP, PingPong); + +abstract_app::cw_orch_interface!(APP, PingPong, PingPongInterface); + +#[cfg(not(target_arch = "wasm32"))] +use abstract_app::std::account::ModuleInstallConfig; +#[cfg(not(target_arch = "wasm32"))] +impl abstract_app::abstract_interface::DependencyCreation + for crate::PingPongInterface +{ + type DependenciesConfig = cosmwasm_std::Empty; + + fn dependency_install_configs( + _configuration: Self::DependenciesConfig, + ) -> Result, abstract_app::abstract_interface::AbstractInterfaceError> + { + Ok(vec![ModuleInstallConfig::new( + abstract_app::objects::module::ModuleInfo::from_id( + IBC_CLIENT, + abstract_ibc_client::contract::CONTRACT_VERSION.into(), + )?, + None, + )]) + } +} diff --git a/contracts/{{ibc_app_name}}/src/error.rs b/contracts/{{ibc_app_name}}/src/error.rs new file mode 100644 index 0000000..e5b069c --- /dev/null +++ b/contracts/{{ibc_app_name}}/src/error.rs @@ -0,0 +1,32 @@ +use abstract_app::{ + objects::module::ModuleInfo, sdk::AbstractSdkError, std::AbstractError, + AppError as AbstractAppError, +}; +use cosmwasm_std::StdError; +use cw_asset::AssetError; +use cw_controllers::AdminError; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum PingPongError { + #[error(transparent)] + Std(#[from] StdError), + + #[error(transparent)] + Abstract(#[from] AbstractError), + + #[error(transparent)] + AbstractSdk(#[from] AbstractSdkError), + + #[error(transparent)] + Asset(#[from] AssetError), + + #[error(transparent)] + Admin(#[from] AdminError), + + #[error(transparent)] + DappError(#[from] AbstractAppError), + + #[error("Caller module is not a PingPong: {source_module}")] + UnauthorizedIbc { source_module: ModuleInfo }, +} diff --git a/contracts/{{ibc_app_name}}/src/handlers/execute.rs b/contracts/{{ibc_app_name}}/src/handlers/execute.rs new file mode 100644 index 0000000..e84309d --- /dev/null +++ b/contracts/{{ibc_app_name}}/src/handlers/execute.rs @@ -0,0 +1,49 @@ +use abstract_app::{ + objects::TruncatedChainId, + sdk::{IbcClient, IbcInterface}, + std::ibc::Callback, + traits::AbstractResponse, +}; +use cosmwasm_std::{CosmosMsg, DepsMut, Env, MessageInfo}; + +use crate::{ + contract::{PingPong, PingPongResult}, + msg::{PingPongCallbackMsg, PingPongExecuteMsg, PingPongIbcMsg}, +}; + +pub fn execute_handler( + deps: DepsMut, + env: Env, + _info: MessageInfo, + module: PingPong, + msg: PingPongExecuteMsg, +) -> PingPongResult { + match msg { + PingPongExecuteMsg::Ping { opponent_chain } => { + ping_pong(deps, &env, opponent_chain, module) + } + } +} + +pub(crate) fn ping_pong( + deps: DepsMut, + env: &Env, + opponent_chain: TruncatedChainId, + module: PingPong, +) -> PingPongResult { + // # ANCHOR: ibc_client + let self_module_info = module.module_info()?; + let ibc_client: IbcClient<_> = module.ibc_client(deps.as_ref(), env); + let ibc_action: CosmosMsg = ibc_client.module_ibc_action( + opponent_chain.clone(), + self_module_info, + // Start by playing a Ping + &PingPongIbcMsg::Ping {}, + Some(Callback::new(&PingPongCallbackMsg::Pinged { + opponent_chain, + })?), + )?; + // # ANCHOR_END: ibc_client + + Ok(module.response("ping").add_message(ibc_action)) +} diff --git a/contracts/{{ibc_app_name}}/src/handlers/instantiate.rs b/contracts/{{ibc_app_name}}/src/handlers/instantiate.rs new file mode 100644 index 0000000..9bad31e --- /dev/null +++ b/contracts/{{ibc_app_name}}/src/handlers/instantiate.rs @@ -0,0 +1,16 @@ +use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; + +use crate::{ + contract::{PingPong, PingPongResult}, + msg::PingPongInstantiateMsg, +}; + +pub fn instantiate_handler( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + _module: PingPong, + _msg: PingPongInstantiateMsg, +) -> PingPongResult { + Ok(Response::new()) +} diff --git a/contracts/{{ibc_app_name}}/src/handlers/mod.rs b/contracts/{{ibc_app_name}}/src/handlers/mod.rs new file mode 100644 index 0000000..d27bf0a --- /dev/null +++ b/contracts/{{ibc_app_name}}/src/handlers/mod.rs @@ -0,0 +1,7 @@ +pub mod execute; +pub mod instantiate; +pub mod query; + +pub use crate::handlers::{ + execute::execute_handler, instantiate::instantiate_handler, query::query_handler, +}; diff --git a/contracts/{{ibc_app_name}}/src/handlers/query.rs b/contracts/{{ibc_app_name}}/src/handlers/query.rs new file mode 100644 index 0000000..f7e8dbf --- /dev/null +++ b/contracts/{{ibc_app_name}}/src/handlers/query.rs @@ -0,0 +1,34 @@ +use cosmwasm_std::{to_json_binary, Binary, Deps, Env, StdResult}; + +use crate::{ + contract::{PingPong, PingPongResult}, + msg::{PingPongQueryMsg, StatusResponse}, + state::{PINGS, PONGS}, +}; + +pub fn query_handler( + deps: Deps, + _env: Env, + _module: &PingPong, + msg: PingPongQueryMsg, +) -> PingPongResult { + match msg { + PingPongQueryMsg::Status {} => to_json_binary(&query_status(deps)?), + } + .map_err(Into::into) +} + +fn query_status(deps: Deps) -> StdResult { + // Sum pings + let pings = PINGS + .range(deps.storage, None, None, cosmwasm_std::Order::Ascending) + .map(|result| result.map(|(_k, v)| v).unwrap_or_default()) + .sum(); + // Sum pongs + let pongs = PONGS + .range(deps.storage, None, None, cosmwasm_std::Order::Ascending) + .map(|result| result.map(|(_k, v)| v).unwrap_or_default()) + .sum(); + + Ok(StatusResponse { pings, pongs }) +} diff --git a/contracts/{{ibc_app_name}}/src/ibc/callback.rs b/contracts/{{ibc_app_name}}/src/ibc/callback.rs new file mode 100644 index 0000000..aa4703b --- /dev/null +++ b/contracts/{{ibc_app_name}}/src/ibc/callback.rs @@ -0,0 +1,41 @@ +use abstract_app::{ + sdk::AbstractResponse, + std::{ + ibc::{Callback, IbcResult}, + ABSTRACT_EVENT_TYPE, + }, +}; +use cosmwasm_std::{from_json, DepsMut, Env}; + +use crate::{ + contract::{PingPong, PingPongResult}, + msg::PingPongCallbackMsg, + state::PINGS, +}; + +pub fn ibc_callback( + deps: DepsMut, + _env: Env, + module: PingPong, + callback: Callback, + result: IbcResult, +) -> PingPongResult { + match from_json(callback.msg)? { + PingPongCallbackMsg::Pinged { opponent_chain } => { + let exec_events = result.get_execute_events()?; + + let pong = exec_events.into_iter().any(|e| { + e.ty == ABSTRACT_EVENT_TYPE + && e.attributes + .iter() + .any(|a| a.key == "action" && a.value == "pong") + }); + if pong { + PINGS.update(deps.storage, &opponent_chain, |l| { + PingPongResult::Ok(l.unwrap_or_default() + 1) + })?; + } + Ok(module.response("pong")) + } + } +} diff --git a/contracts/{{ibc_app_name}}/src/ibc/mod.rs b/contracts/{{ibc_app_name}}/src/ibc/mod.rs new file mode 100644 index 0000000..879cb2d --- /dev/null +++ b/contracts/{{ibc_app_name}}/src/ibc/mod.rs @@ -0,0 +1,5 @@ +mod callback; +mod module; + +pub use callback::ibc_callback; +pub use module::receive_module_ibc; diff --git a/contracts/{{ibc_app_name}}/src/ibc/module.rs b/contracts/{{ibc_app_name}}/src/ibc/module.rs new file mode 100644 index 0000000..6c74358 --- /dev/null +++ b/contracts/{{ibc_app_name}}/src/ibc/module.rs @@ -0,0 +1,34 @@ +use abstract_app::{sdk::AbstractResponse, std::ibc::ModuleIbcInfo}; +use cosmwasm_std::{ensure_eq, from_json, Binary, DepsMut, Env, Response}; + +use crate::{ + contract::{PingPong, PingPongResult}, + error::PingPongError, + msg::PingPongIbcMsg, + state::PONGS, +}; + +pub fn receive_module_ibc( + deps: DepsMut, + _env: Env, + module: PingPong, + source_module: ModuleIbcInfo, + msg: Binary, +) -> PingPongResult { + let this_module_info = module.module_info()?; + ensure_eq!( + source_module.module, + this_module_info, + PingPongError::UnauthorizedIbc { + source_module: source_module.module.clone() + } + ); + let ibc_msg: PingPongIbcMsg = from_json(msg)?; + match ibc_msg { + PingPongIbcMsg::Ping {} => PONGS.update(deps.storage, &source_module.chain, |pongs| { + PingPongResult::Ok(pongs.unwrap_or_default() + 1) + })?, + }; + + Ok(module.response("pong")) +} diff --git a/contracts/{{ibc_app_name}}/src/lib.rs b/contracts/{{ibc_app_name}}/src/lib.rs new file mode 100644 index 0000000..633b89a --- /dev/null +++ b/contracts/{{ibc_app_name}}/src/lib.rs @@ -0,0 +1,17 @@ +pub mod contract; +pub mod error; +mod handlers; +mod ibc; +pub mod msg; +pub mod state; + +pub use contract::interface::PingPongInterface; +pub use msg::{PingPongExecuteMsgFns, PingPongQueryMsgFns}; + +/// The version of your app +pub const APP_VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub const TESTGEN_LOCAL_NAMESPACE: &str = "testgen-local"; +pub const PING_PONG_NAME: &str = "ping-pong"; +pub const PING_PONG_ID: &str = + const_format::concatcp!(TESTGEN_LOCAL_NAMESPACE, ":", PING_PONG_NAME); diff --git a/contracts/{{ibc_app_name}}/src/msg.rs b/contracts/{{ibc_app_name}}/src/msg.rs new file mode 100644 index 0000000..be5b0a0 --- /dev/null +++ b/contracts/{{ibc_app_name}}/src/msg.rs @@ -0,0 +1,46 @@ +use abstract_app::objects::TruncatedChainId; +use cosmwasm_schema::QueryResponses; + +use crate::contract::PingPong; + +// This is used for type safety and re-exporting the contract endpoint structs. +abstract_app::app_msg_types!(PingPong, PingPongExecuteMsg, PingPongQueryMsg); + +/// App instantiate message +#[cosmwasm_schema::cw_serde] +pub struct PingPongInstantiateMsg {} + +/// App execute messages +#[cosmwasm_schema::cw_serde] +#[derive(cw_orch::ExecuteFns)] +pub enum PingPongExecuteMsg { + /// Increment ping in this module and pong on its counterpart on another chain. + Ping { opponent_chain: TruncatedChainId }, +} + +/// App query messages +#[cosmwasm_schema::cw_serde] +#[derive(QueryResponses, cw_orch::QueryFns)] +pub enum PingPongQueryMsg { + #[returns(StatusResponse)] + Status {}, +} + +#[cosmwasm_schema::cw_serde] +pub enum PingPongIbcMsg { + Ping {}, +} + +#[cosmwasm_schema::cw_serde] +pub enum PingPongCallbackMsg { + Pinged { opponent_chain: TruncatedChainId }, +} + +#[cosmwasm_schema::cw_serde] +pub struct AppMigrateMsg {} + +#[cosmwasm_schema::cw_serde] +pub struct StatusResponse { + pub pings: u32, + pub pongs: u32, +} diff --git a/contracts/{{ibc_app_name}}/src/state.rs b/contracts/{{ibc_app_name}}/src/state.rs new file mode 100644 index 0000000..722c9b1 --- /dev/null +++ b/contracts/{{ibc_app_name}}/src/state.rs @@ -0,0 +1,5 @@ +use abstract_app::objects::TruncatedChainId; +use cw_storage_plus::Map; + +pub const PINGS: Map<&TruncatedChainId, u32> = Map::new("pings"); +pub const PONGS: Map<&TruncatedChainId, u32> = Map::new("pongs"); diff --git a/contracts/{{ibc_app_name}}/tests/ping_pong_tests.rs b/contracts/{{ibc_app_name}}/tests/ping_pong_tests.rs new file mode 100644 index 0000000..c34aad6 --- /dev/null +++ b/contracts/{{ibc_app_name}}/tests/ping_pong_tests.rs @@ -0,0 +1,165 @@ +use abstract_app::objects::namespace::Namespace; + +use abstract_client::{AbstractClient, AbstractInterchainClient, Application, RemoteAccount}; + +use abstract_app::std::objects::TruncatedChainId; + +use cw_orch::{anyhow, prelude::*}; +use cw_orch_interchain::prelude::*; + +use ping_pong::msg::{PingPongInstantiateMsg, StatusResponse}; +use ping_pong::PING_PONG_ID; +use ping_pong::{PingPongExecuteMsgFns, PingPongInterface, PingPongQueryMsgFns}; + +const JUNO: &str = "juno-1"; +const STARGAZE: &str = "stargaze-1"; + +#[allow(unused)] +struct PingPong> { + abs_juno: AbstractClient, + abs_stargaze: AbstractClient, + ping_pong: Application>, + remote_account: RemoteAccount, + mock_interchain: IbcEnv, +} + +impl PingPong { + /// Set up the test environment with two Accounts that has the App installed + fn setup() -> anyhow::Result> { + // Logger + let _ = env_logger::builder().is_test(true).try_init(); + + // Create a sender and mock env + let mock_interchain = + MockBech32InterchainEnv::new(vec![(JUNO, "juno"), (STARGAZE, "stargaze")]); + + let interchain_abstract = AbstractInterchainClient::deploy_mock(&mock_interchain)?; + + let abs_juno = interchain_abstract.client(JUNO)?; + let abs_stargaze = interchain_abstract.client(STARGAZE)?; + + let namespace = Namespace::from_id(PING_PONG_ID)?; + // Publish and install on both chains + let publisher_juno = abs_juno + .account_builder() + .namespace(namespace.clone()) + .build()? + .publisher()?; + publisher_juno.publish_app::>()?; + let app = publisher_juno + .account() + .install_app_with_dependencies::>( + &PingPongInstantiateMsg {}, + Empty {}, + &[], + )?; + + let publisher_stargaze = abs_stargaze + .account_builder() + .namespace(namespace) + .build()? + .publisher()?; + publisher_stargaze.publish_app::>()?; + + let remote_account = app + .account() + .remote_account_builder(mock_interchain.clone(), &abs_stargaze) + .install_app_with_dependencies::>( + &PingPongInstantiateMsg {}, + Empty {}, + )? + .build()?; + Ok(PingPong { + abs_juno, + abs_stargaze, + ping_pong: app, + remote_account, + mock_interchain, + }) + } +} + +#[test] +fn successful_install() -> anyhow::Result<()> { + let env = PingPong::setup()?; + let app1 = env.ping_pong; + + let status = app1.status()?; + assert_eq!(status, StatusResponse { pings: 0, pongs: 0 }); + + let app2 = env.remote_account.application::>()?; + + let status: StatusResponse = app2.status()?; + + assert_eq!(status, StatusResponse { pings: 0, pongs: 0 }); + Ok(()) +} + +#[test] +fn successful_ping() -> anyhow::Result<()> { + // Create a sender and mock env + let env = PingPong::setup()?; + let app = env.ping_pong; + let remote_app = env.remote_account.application::>()?; + + let status = app.status()?; + assert_eq!(status, StatusResponse { pings: 0, pongs: 0 }); + let status = remote_app.status()?; + assert_eq!(status, StatusResponse { pings: 0, pongs: 0 }); + + // juno pings stargaze + let pp = app.ping(TruncatedChainId::from_chain_id(STARGAZE))?; + env.mock_interchain.await_and_check_packets(JUNO, pp)?; + + // juno pinged, stargaze ponged. + let status = app.status()?; + assert_eq!(status, StatusResponse { pings: 1, pongs: 0 }); + let status = remote_app.status()?; + assert_eq!(status, StatusResponse { pings: 0, pongs: 1 }); + + // repeat + let pp = app.ping(TruncatedChainId::from_chain_id(STARGAZE))?; + env.mock_interchain.await_and_check_packets(JUNO, pp)?; + + let status = app.status()?; + assert_eq!(status, StatusResponse { pings: 2, pongs: 0 }); + let status = remote_app.status()?; + assert_eq!(status, StatusResponse { pings: 0, pongs: 2 }); + + Ok(()) +} + +#[test] +fn successful_ping_to_home_chain() -> anyhow::Result<()> { + // Create a sender and mock env + let env = PingPong::setup()?; + let app = env.ping_pong; + let remote_app = env.remote_account.application::>()?; + + // stargaze pings juno + // Note that `RemoteApplication` takes care of waiting for ibc + remote_app.execute( + &ping_pong::msg::PingPongExecuteMsg::Ping { + opponent_chain: TruncatedChainId::from_chain_id(JUNO), + } + .into(), + vec![], + )?; + + // stargaze pinged, juno ponged + let status = app.status()?; + assert_eq!(status, StatusResponse { pings: 0, pongs: 1 }); + let status = remote_app.status()?; + assert_eq!(status, StatusResponse { pings: 1, pongs: 0 }); + + // juno ping, stargaze pong + let pp = app.ping(TruncatedChainId::from_chain_id(STARGAZE))?; + env.mock_interchain.await_and_check_packets(JUNO, pp)?; + + let status = app.status()?; + assert_eq!(status, StatusResponse { pings: 1, pongs: 1 }); + let status = remote_app.status()?; + assert_eq!(status, StatusResponse { pings: 1, pongs: 1 }); + + Ok(()) +} diff --git a/contracts/{{standalone_name}}/Cargo.toml b/contracts/{{standalone_name}}/Cargo.toml index 7bcab67..1964af7 100644 --- a/contracts/{{standalone_name}}/Cargo.toml +++ b/contracts/{{standalone_name}}/Cargo.toml @@ -48,7 +48,6 @@ const_format = { version = "0.2.32" } # Dependencies for interface cw-orch = { workspace = true } -abstract-interface = { workspace = true } # Dependencies for bins clap = { workspace = true, optional = true, features = ["derive"] } diff --git a/contracts/{{standalone_name}}/src/interface.rs b/contracts/{{standalone_name}}/src/interface.rs index 0f5f8bc..f7b9e10 100644 --- a/contracts/{{standalone_name}}/src/interface.rs +++ b/contracts/{{standalone_name}}/src/interface.rs @@ -15,13 +15,13 @@ use crate::{ )] pub struct {{standalone_name | upper_camel_case}}Interface; -impl abstract_interface::DependencyCreation +impl abstract_standalone::abstract_interface::DependencyCreation for {{standalone_name | upper_camel_case}}Interface { type DependenciesConfig = cosmwasm_std::Empty; } -impl abstract_interface::RegisteredModule +impl abstract_standalone::abstract_interface::RegisteredModule for {{standalone_name | upper_camel_case}}Interface { type InitMsg = <{{standalone_name | upper_camel_case}}Interface as InstantiableContract>::InstantiateMsg;