diff --git a/Cargo.toml b/Cargo.toml index e3a1b1c..0a81f69 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] localic-std = { git = "https://github.com/strangelove-ventures/interchaintest", branch = "main" } cosmwasm-std = "1.5.5" +osmosis-std = "0.25.0" serde = { version = "1.0.204", features = ["derive"] } serde_json = "1.0.120" thiserror = "1.0.61" @@ -14,6 +15,7 @@ log = "0.4.22" astroport = "5.1.0" reqwest = { version = "0.11.20", features = ["rustls-tls"] } sha2 = "0.10.8" +base64 = "0.22.1" [dev-dependencies] env_logger = "0.11.3" diff --git a/README.md b/README.md index 0596d85..dc536d0 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ Note that most `tx_*` helper functions expose a `.with_key(key: &str)` builder f * `.with_msg(msg: serde_json::Value)` * `.with_label(label: &str)` * Notable optional builder calls: - * `.with_chain_name(chain_name: impl Into)` - Should be one of `"osmosis" | "neutron"` or one of the registered chain names from `.with_chain` + * `.with_chain(chain_name: impl Into)` - Should be one of `"osmosis" | "neutron"` or one of the registered chain names from `.with_chain` #### Tokens @@ -115,7 +115,7 @@ Note that most `tx_*` helper functions expose a `.with_key(key: &str)` builder f * Required builder calls: * `.with_subdenom(subdenom: &str)` * Notable optional builder calls: - * `.with_chain_name(chain_name: impl Into)` - Should be one of `"osmosis" | "neutron" | "stride"` or one of the registered chain names from `.with_chain` + * `.with_chain(chain_name: impl Into)` - Should be one of `"osmosis" | "neutron" | "stride"` or one of the registered chain names from `.with_chain` * `.get_tokenfactory_denom(key: &str, subdenom: &str)` - Gets the tokenfactory denom of a tokenfactory token given its subdenom and key * `.build_tx_mint_tokenfactory_token` - Mints a tokenfactory token from `acc0` on Neutron by default. * Required builder calls @@ -124,7 +124,7 @@ Note that most `tx_*` helper functions expose a `.with_key(key: &str)` builder f * Required builder calls for osmosis * `.with_recipient_addr(addr: &str)` - Specifies a recipient of the minted tokens on Osmosis. This builder call does nothing on Neutron. * Notable optional builder calls: - * `.with_chain_name(chain_name: impl Into)` - Specifies which chain to mint the tokens on. See previous notes about chain names. + * `.with_chain(chain_name: impl Into)` - Specifies which chain to mint the tokens on. See previous notes about chain names. #### Auctions diff --git a/examples/chains/neutron_gaia.json b/examples/chains/neutron_gaia.json index 71b2907..4791987 100644 --- a/examples/chains/neutron_gaia.json +++ b/examples/chains/neutron_gaia.json @@ -159,7 +159,7 @@ "binary": "osmosisd", "bech32_prefix": "osmo", "docker_image": { - "version": "v25.0.4", + "version": "v21.0.0", "repository": "ghcr.io/strangelove-ventures/heighliner/osmosis" }, "gas_prices": "0.0025%DENOM%", @@ -177,15 +177,27 @@ "1317": "1319", "9090": "9092" }, + "config_file_overrides": [ + { + "file": "config/app.toml", + "paths": { + "osmosis-mempool.max-gas-wanted-per-tx": "10000000000" + } + } + ], "genesis": { "modify": [ + { + "key": "consensus_params.block.max_gas", + "value": "10000000000" + }, { "key": "app_state.gov.params.voting_period", "value": "3s" }, { "key": "app_state.gov.params.max_deposit_period", - "value": "15s" + "value": "3s" }, { "key": "app_state.gov.params.min_deposit.0.denom", diff --git a/examples/contracts_osmosis/astroport_factory_osmosis.wasm b/examples/contracts_osmosis/astroport_factory_osmosis.wasm new file mode 100644 index 0000000..9088be9 Binary files /dev/null and b/examples/contracts_osmosis/astroport_factory_osmosis.wasm differ diff --git a/examples/contracts_osmosis/astroport_maker_osmosis.wasm b/examples/contracts_osmosis/astroport_maker_osmosis.wasm new file mode 100644 index 0000000..9e82da1 Binary files /dev/null and b/examples/contracts_osmosis/astroport_maker_osmosis.wasm differ diff --git a/examples/contracts_osmosis/astroport_native_coin_registry.wasm b/examples/contracts_osmosis/astroport_native_coin_registry.wasm new file mode 100644 index 0000000..7b7e8d9 Binary files /dev/null and b/examples/contracts_osmosis/astroport_native_coin_registry.wasm differ diff --git a/examples/contracts_osmosis/astroport_pcl_osmo.wasm b/examples/contracts_osmosis/astroport_pcl_osmo.wasm new file mode 100644 index 0000000..e78713c Binary files /dev/null and b/examples/contracts_osmosis/astroport_pcl_osmo.wasm differ diff --git a/examples/contracts_osmosis/auction.wasm b/examples/contracts_osmosis/auction.wasm new file mode 100644 index 0000000..c6b59bf Binary files /dev/null and b/examples/contracts_osmosis/auction.wasm differ diff --git a/examples/contracts_osmosis/auctions_manager.wasm b/examples/contracts_osmosis/auctions_manager.wasm new file mode 100644 index 0000000..2837aa7 Binary files /dev/null and b/examples/contracts_osmosis/auctions_manager.wasm differ diff --git a/examples/contracts_osmosis/cw1_whitelist.wasm b/examples/contracts_osmosis/cw1_whitelist.wasm new file mode 100644 index 0000000..c722e16 Binary files /dev/null and b/examples/contracts_osmosis/cw1_whitelist.wasm differ diff --git a/examples/contracts_osmosis/cw20_base.wasm b/examples/contracts_osmosis/cw20_base.wasm new file mode 100644 index 0000000..95d9f2a Binary files /dev/null and b/examples/contracts_osmosis/cw20_base.wasm differ diff --git a/examples/contracts_osmosis/price_oracle.wasm b/examples/contracts_osmosis/price_oracle.wasm new file mode 100644 index 0000000..fe14ead Binary files /dev/null and b/examples/contracts_osmosis/price_oracle.wasm differ diff --git a/examples/neutron.rs b/examples/neutron.rs index 2d301e7..ac99bff 100644 --- a/examples/neutron.rs +++ b/examples/neutron.rs @@ -9,8 +9,8 @@ const ARTIFACTS_DIR: &str = "contracts"; const ACC_0_ADDR: &str = "neutron1hj5fveer5cjtn4wd6wstzugjfdxzl0xpznmsky"; const LOCAL_CODE_ID_CACHE_PATH: &str = "code_id_cache.json"; -const TEST_TOKEN_1_NAME: &str = "bruhtoken3"; -const TEST_TOKEN_2_NAME: &str = "amoguscoin3"; +const TEST_TOKEN_1_NAME: &str = "bruhtoken"; +const TEST_TOKEN_2_NAME: &str = "amoguscoin"; /// Demonstrates using localic-utils for neutron. fn main() -> Result<(), Box> { @@ -39,8 +39,16 @@ fn main() -> Result<(), Box> { .with_subdenom(TEST_TOKEN_2_NAME) .send()?; - let bruhtoken = ctx.get_tokenfactory_denom(ACC_0_ADDR, TEST_TOKEN_1_NAME); - let amoguscoin = ctx.get_tokenfactory_denom(ACC_0_ADDR, TEST_TOKEN_2_NAME); + let bruhtoken = ctx + .get_tokenfactory_denom() + .creator(ACC_0_ADDR) + .subdenom(TEST_TOKEN_1_NAME.to_owned()) + .get(); + let amoguscoin = ctx + .get_tokenfactory_denom() + .creator(ACC_0_ADDR) + .subdenom(TEST_TOKEN_2_NAME.to_owned()) + .get(); // Deploy valence auctions ctx.build_tx_create_auctions_manager() @@ -81,14 +89,26 @@ fn main() -> Result<(), Box> { .with_amount_offer_asset(10000) .send()?; - ctx.get_auction(( - "untrn", - ctx.get_tokenfactory_denom(ACC_0_ADDR, TEST_TOKEN_1_NAME), - ))?; - ctx.get_auction(( - "untrn", - ctx.get_tokenfactory_denom(ACC_0_ADDR, TEST_TOKEN_2_NAME), - ))?; + let _ = ctx + .get_auction() + .offer_asset("untrn") + .ask_asset( + &ctx.get_tokenfactory_denom() + .creator(ACC_0_ADDR) + .subdenom(TEST_TOKEN_1_NAME.to_owned()) + .get(), + ) + .get_cw(); + let _ = ctx + .get_auction() + .offer_asset("untrn") + .ask_asset( + &ctx.get_tokenfactory_denom() + .creator(ACC_0_ADDR) + .subdenom(TEST_TOKEN_2_NAME.to_owned()) + .get(), + ) + .get_cw(); ctx.build_tx_create_token_registry() .with_owner(ACC_0_ADDR) @@ -105,10 +125,16 @@ fn main() -> Result<(), Box> { .with_denom_b(bruhtoken) .send()?; - let pool = ctx.get_astroport_pool( - "untrn", - ctx.get_tokenfactory_denom(ACC_0_ADDR, TEST_TOKEN_2_NAME), - )?; + let pool = ctx + .get_astro_pool() + .denoms( + "untrn".to_owned(), + ctx.get_tokenfactory_denom() + .creator(ACC_0_ADDR) + .subdenom(TEST_TOKEN_2_NAME.to_owned()) + .get(), + ) + .get_cw(); assert!(pool .query_value(&serde_json::json!({ @@ -140,8 +166,9 @@ fn main() -> Result<(), Box> { .send()?; let factory_contract_code_id = ctx - .get_contract("astroport_whitelist") - .unwrap() + .get_contract() + .contract("astroport_whitelist") + .get_cw() .code_id .unwrap(); @@ -165,7 +192,7 @@ fn main() -> Result<(), Box> { .salt_hex_encoded(hex::encode("examplesalt").as_str()) .get(); - let mut cw = ctx.get_contract("astroport_whitelist").unwrap(); + let mut cw = ctx.get_contract().contract("astroport_whitelist").get_cw(); cw.contract_addr = Some(addr); cw.execute( diff --git a/examples/neutron_osmosis.rs b/examples/neutron_osmosis.rs index 3c633a6..2a27436 100644 --- a/examples/neutron_osmosis.rs +++ b/examples/neutron_osmosis.rs @@ -32,7 +32,11 @@ fn main() -> Result<(), Box> { .with_chain_name("neutron") .with_subdenom("bruhtoken") .send()?; - let bruhtoken = ctx.get_tokenfactory_denom(NEUTRON_ACC_0_ADDR, "bruhtoken"); + let bruhtoken = ctx + .get_tokenfactory_denom() + .creator(NEUTRON_ACC_0_ADDR) + .subdenom("bruhtoken".into()) + .get(); ctx.build_tx_mint_tokenfactory_token() .with_chain_name("neutron") .with_amount(10000000000000000000) @@ -53,8 +57,18 @@ fn main() -> Result<(), Box> { .with_amount(1000000) .send()?; - let ibc_bruhtoken = ctx.get_ibc_denom(&bruhtoken, "neutron", "osmosis"); - let ibc_neutron = ctx.get_ibc_denom("untrn", "neutron", "osmosis"); + let ibc_bruhtoken = ctx + .get_ibc_denom() + .base_denom(bruhtoken.clone()) + .src("neutron") + .dest("osmosis") + .get(); + let ibc_neutron = ctx + .get_ibc_denom() + .base_denom("untrn".into()) + .src("neutron") + .dest("osmosis") + .get(); // Create an osmosis pool ctx.build_tx_create_osmo_pool() @@ -65,7 +79,10 @@ fn main() -> Result<(), Box> { .send()?; // Get its id - let pool_id = ctx.get_osmo_pool(&ibc_neutron, &ibc_bruhtoken)?; + let pool_id = ctx + .get_osmo_pool() + .denoms(ibc_neutron.clone(), ibc_bruhtoken.clone()) + .get_u64(); // Fund the pool ctx.build_tx_fund_osmo_pool() diff --git a/examples/osmosis.rs b/examples/osmosis.rs index edfe8cd..765fb23 100644 --- a/examples/osmosis.rs +++ b/examples/osmosis.rs @@ -1,7 +1,18 @@ -use localic_utils::{ConfigChainBuilder, TestContextBuilder, OSMOSIS_CHAIN_NAME}; +use astroport::pair_concentrated::ConcentratedPoolParams; +use cosmwasm_std::Decimal; +use localic_utils::{ + types::{ + contract::MinAmount, + osmosis::{CosmWasmPoolType, PoolInitParams, PoolType}, + }, + ConfigChainBuilder, TestContextBuilder, DEFAULT_KEY, OSMOSIS_CHAIN_NAME, +}; use std::error::Error; const ACC_0_ADDR: &str = "osmo1hj5fveer5cjtn4wd6wstzugjfdxzl0xpwhpz63"; +const LOCAL_CODE_ID_CACHE_PATH: &str = "code_id_cache_osmo.json"; + +const TEST_TOKEN_1_NAME: &str = "bruhtoken1000"; /// Demonstrates using localic-utils for neutron. fn main() -> Result<(), Box> { @@ -16,29 +27,71 @@ fn main() -> Result<(), Box> { .with_chain(ConfigChainBuilder::default_osmosis().build()?) .build()?; + // Upload astroport contracts + ctx.build_tx_upload_contracts().send_with_local_cache( + "contracts_osmosis", + OSMOSIS_CHAIN_NAME, + LOCAL_CODE_ID_CACHE_PATH, + )?; + + // Whitelist the PCL contract + ctx.build_tx_whitelist_cosmwasm_pool() + .with_contract_path("contracts_osmosis/astroport_pcl_osmo.wasm") + .send()?; + // Create some tokens on osmosis ctx.build_tx_create_tokenfactory_token() - .with_chain_name(OSMOSIS_CHAIN_NAME) - .with_subdenom("bruhtoken") + .with_chain(OSMOSIS_CHAIN_NAME) + .with_subdenom(TEST_TOKEN_1_NAME) .send()?; - let bruhtoken = ctx.get_tokenfactory_denom(ACC_0_ADDR, "bruhtoken"); + let bruhtoken = ctx + .get_tokenfactory_denom() + .creator(ACC_0_ADDR) + .subdenom(TEST_TOKEN_1_NAME.into()) + .get(); ctx.build_tx_mint_tokenfactory_token() - .with_chain_name(OSMOSIS_CHAIN_NAME) + .with_chain(OSMOSIS_CHAIN_NAME) .with_amount(10000000000000000000) .with_denom(&bruhtoken) .with_recipient_addr(ACC_0_ADDR) .send()?; // Create an osmosis pool + ctx.build_tx_create_token_registry() + .with_owner(ACC_0_ADDR) + .with_chain(OSMOSIS_CHAIN_NAME) + .send()?; + ctx.build_tx_create_factory() + .with_owner(ACC_0_ADDR) + .with_chain(OSMOSIS_CHAIN_NAME) + .send()?; + ctx.build_tx_create_osmo_pool() + .with_pool_type(PoolType::CosmWasm(CosmWasmPoolType::Pcl)) .with_weight("uosmo", 1) .with_weight(&bruhtoken, 1) .with_initial_deposit("uosmo", 1) .with_initial_deposit(&bruhtoken, 1) + .with_pool_init_params(PoolInitParams::Pcl(ConcentratedPoolParams { + amp: Decimal::one(), + gamma: Decimal::one(), + mid_fee: Decimal::one(), + out_fee: Decimal::one(), + fee_gamma: Decimal::one(), + repeg_profit_threshold: Decimal::one(), + min_price_scale_delta: Decimal::one(), + price_scale: Decimal::one(), + ma_half_time: 0, + track_asset_balances: None, + fee_share: None, + })) .send()?; // Get its id - let pool_id = ctx.get_osmo_pool("uosmo", &bruhtoken)?; + let pool_id = ctx + .get_osmo_pool() + .denoms("uosmo".into(), bruhtoken.clone()) + .get_u64(); // Fund the pool ctx.build_tx_fund_osmo_pool() @@ -48,5 +101,153 @@ fn main() -> Result<(), Box> { .with_share_amount_out(1000000000000) .send()?; + // Deploy some astroport and valence contracts to osmosis + // Deploy valence auctions + ctx.build_tx_create_auctions_manager() + .with_min_auction_amount(&[( + &String::from("uosmo"), + MinAmount { + send: "0".into(), + start_auction: "0".into(), + }, + )]) + .with_server_addr(ACC_0_ADDR) + .with_chain(OSMOSIS_CHAIN_NAME) + .send()?; + ctx.build_tx_create_price_oracle() + .with_chain(OSMOSIS_CHAIN_NAME) + .send()?; + ctx.build_tx_manual_oracle_price_update() + .with_offer_asset("uosmo") + .with_ask_asset(bruhtoken.as_str()) + .with_price(Decimal::percent(10)) + .with_chain(OSMOSIS_CHAIN_NAME) + .send()?; + ctx.build_tx_update_auction_oracle() + .with_chain(OSMOSIS_CHAIN_NAME) + .send()?; + + ctx.build_tx_create_auction() + .with_offer_asset("uosmo") + .with_ask_asset(bruhtoken.as_str()) + .with_amount_offer_asset(10000) + .with_chain(OSMOSIS_CHAIN_NAME) + .send()?; + ctx.build_tx_create_auction() + .with_offer_asset("uosmo") + .with_ask_asset(bruhtoken.as_str()) + .with_amount_offer_asset(10000) + .with_chain(OSMOSIS_CHAIN_NAME) + .send()?; + + let _ = ctx + .get_auction() + .src(OSMOSIS_CHAIN_NAME) + .offer_asset("uosmo") + .ask_asset( + &ctx.get_tokenfactory_denom() + .creator(ACC_0_ADDR) + .subdenom(TEST_TOKEN_1_NAME.to_owned()) + .get(), + ) + .get_cw(); + let _ = ctx + .get_auction() + .src(OSMOSIS_CHAIN_NAME) + .offer_asset("uosmo") + .ask_asset( + &ctx.get_tokenfactory_denom() + .creator(ACC_0_ADDR) + .subdenom(TEST_TOKEN_1_NAME.to_owned()) + .get(), + ) + .get_cw(); + + ctx.build_tx_create_pool() + .with_denom_a("uosmo") + .with_denom_b(bruhtoken.clone()) + .with_chain(OSMOSIS_CHAIN_NAME) + .send()?; + + let pool = ctx + .get_astro_pool() + .denoms( + "uosmo".to_owned(), + ctx.get_tokenfactory_denom() + .creator(ACC_0_ADDR) + .subdenom(TEST_TOKEN_1_NAME.to_owned()) + .get(), + ) + .get_cw(); + + assert!(pool + .query_value(&serde_json::json!({ + "pair": {} + })) + .get("data") + .and_then(|data| data.get("asset_infos")) + .is_some()); + + ctx.build_tx_fund_auction() + .with_offer_asset("uosmo") + .with_ask_asset(bruhtoken.as_str()) + .with_amount_offer_asset(10000) + .with_chain(OSMOSIS_CHAIN_NAME) + .send()?; + + ctx.build_tx_start_auction() + .with_offer_asset("uosmo") + .with_ask_asset(bruhtoken.as_str()) + .with_end_block_delta(1000000) + .with_chain(OSMOSIS_CHAIN_NAME) + .send()?; + + ctx.build_tx_fund_pool() + .with_denom_a("uosmo") + .with_denom_b(bruhtoken) + .with_amount_denom_a(10000) + .with_amount_denom_b(10000) + .with_slippage_tolerance(Decimal::percent(50)) + .with_liq_token_receiver(ACC_0_ADDR) + .with_chain(OSMOSIS_CHAIN_NAME) + .send()?; + + let factory_contract_code_id = ctx + .get_contract() + .contract("astroport_whitelist") + .get_cw() + .code_id + .unwrap(); + + // Instantiate a contract with a predictable address + ctx.build_tx_instantiate2() + .with_code_id(factory_contract_code_id) + .with_msg(serde_json::json!({ + "admins": [], + "mutable": false, + })) + .with_salt_hex_encoded(hex::encode("examplesalt").as_str()) + .with_label("test_contract") + .with_flags("--gas 10000000") + .send() + .unwrap(); + + let addr = ctx + .get_built_contract_address() + .contract("cw1_whitelist") + .creator(ACC_0_ADDR) + .salt_hex_encoded(hex::encode("examplesalt").as_str()) + .get(); + + let mut cw = ctx.get_contract().contract("cw1_whitelist").get_cw(); + cw.contract_addr = Some(addr); + + cw.execute( + DEFAULT_KEY, + &serde_json::json!({ "execute": { "msgs": [] } }).to_string(), + "", + ) + .unwrap(); + Ok(()) } diff --git a/flake.nix b/flake.nix index 185a1a4..68be087 100644 --- a/flake.nix +++ b/flake.nix @@ -1,5 +1,6 @@ { - description = "A flake providing a reproducible environment for localic-utils"; + description = + "A flake providing a reproducible environment for localic-utils"; inputs = { nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; @@ -13,14 +14,46 @@ outputs = { self, nixpkgs, flake-utils, rust-overlay }: flake-utils.lib.eachDefaultSystem (system: let - pkgs = import nixpkgs { inherit system; overlays = [ rust-overlay.overlays.default ]; }; - in { - devShells.default = pkgs.mkShell { - nativeBuildInputs = with pkgs.buildPackages; [ - rust-bin.stable.latest.default + pkgs = import nixpkgs { + inherit system; + overlays = [ rust-overlay.overlays.default ]; + }; + in rec { + packages.cosmwasm-check = pkgs.rustPlatform.buildRustPackage rec { + pname = "cosmwasm-checck"; + version = "v2.1.3"; + + src = pkgs.fetchFromGitHub { + owner = "CosmWasm"; + repo = "cosmwasm"; + rev = version; + hash = "sha256-WXhz47cNeSRlUGfiXZkGOvu6WjK26MPJB716DiFqYPY="; + }; + + cargoHash = "sha256-2zTShh1tN6jC/PoVitcYwxAIKwel6uwJKRudC7LoBYQ="; + + meta = { + description = + "Utility for validating properties of cosmwasm artifacts"; + homepage = "https://github.com/CosmWasm/cosmwasm"; + }; + + checkFlags = [ + "--skip=results::events::tests::attribute_new_reserved_key_panicks" + "--skip=results::events::tests::attribute_new_reserved_key_panicks2" ]; + }; - buildInputs = with pkgs; [ openssl libiconv pkg-config ]; + devShells.default = pkgs.mkShell { + nativeBuildInputs = with pkgs.buildPackages; + [ rust-bin.stable.latest.default ]; + + buildInputs = with pkgs; [ + openssl + libiconv + pkg-config + packages.cosmwasm-check + ]; }; }); } diff --git a/src/lib.rs b/src/lib.rs index da734d5..1f36071 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,6 +35,8 @@ pub const OSMOSIS_CHAIN_PREFIX: &str = "osmo"; pub const OSMOSIS_CHAIN_ADMIN_ADDR: &str = "osmo1hj5fveer5cjtn4wd6wstzugjfdxzl0xpwhpz63"; pub const OSMOSIS_CHAIN_NAME: &str = "osmosis"; pub const OSMOSIS_POOLFILE_PATH: &str = "/tmp/pool_file.json"; +pub const OSMOSIS_WHITELIST_PROP_PATH: &str = "/tmp/prop.json"; +pub const OSMOSIS_PCL_POOL_TYPE_NAME: &str = "concentrated"; /// Stride chain info pub const STRIDE_CHAIN_ID: &str = "localstride-1"; @@ -48,10 +50,13 @@ pub const AUCTION_CONTRACT_NAME: &str = "auction"; pub const AUCTIONS_MANAGER_CONTRACT_NAME: &str = "auctions_manager"; pub const TOKEN_REGISTRY_NAME: &str = "astroport_native_coin_registry"; pub const FACTORY_NAME: &str = "astroport_factory"; +pub const FACTORY_ON_OSMOSIS_NAME: &str = "astroport_factory_osmosis"; +pub const PAIR_PCL_ON_OSMOSIS_NAME: &str = "astroport_pcl_osmo"; pub const PAIR_NAME: &str = "astroport_pair"; pub const STABLE_PAIR_NAME: &str = "astroport_pair_stable"; pub const TOKEN_NAME: &str = "cw20_base"; pub const WHITELIST_NAME: &str = "astroport_whitelist"; +pub const CW1_WHITELIST_NAME: &str = "cw1_whitelist"; pub const PRICE_ORACLE_NAME: &str = "price_oracle"; /// Local ic info @@ -60,6 +65,7 @@ pub const LOCAL_IC_API_URL: &str = "http://localhost:42069/"; /// Builder defautls pub const ADMIN_KEY: &str = "admin"; pub const DEFAULT_KEY: &str = "acc0"; +pub const VALIDATOR_KEY: &str = "validator"; pub const DEFAULT_TRANSFER_PORT: &str = "transfer"; pub const DEFAULT_AUCTION_LABEL: &str = "auction"; diff --git a/src/types/mod.rs b/src/types/mod.rs index 8ef094a..762819d 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -5,3 +5,5 @@ pub mod config; pub mod contract; pub mod ibc; + +pub mod osmosis; diff --git a/src/types/osmosis.rs b/src/types/osmosis.rs new file mode 100644 index 0000000..6b2ea52 --- /dev/null +++ b/src/types/osmosis.rs @@ -0,0 +1,20 @@ +use astroport::pair_concentrated::ConcentratedPoolParams; + +/// All pool types supported by local-ic utils and Osmosis +#[derive(Clone, Copy)] +pub enum PoolType { + Xyk, + CosmWasm(CosmWasmPoolType), +} + +/// Init parameters for different osmosis pool types +#[derive(Clone)] +pub enum PoolInitParams { + Pcl(ConcentratedPoolParams), +} + +/// All cosmwasm pool types supported by local-ic utils +#[derive(Clone, Copy)] +pub enum CosmWasmPoolType { + Pcl, +} diff --git a/src/utils/fs.rs b/src/utils/fs.rs index 364a5b0..647abbf 100644 --- a/src/utils/fs.rs +++ b/src/utils/fs.rs @@ -14,6 +14,7 @@ use std::{ /// A tx uploading contract artifacts. pub struct UploadContractsTxBuilder<'a> { key: Option<&'a str>, + chain: &'a str, test_ctx: &'a mut TestContext, } @@ -24,11 +25,18 @@ impl<'a> UploadContractsTxBuilder<'a> { self } + pub fn with_chain(&mut self, chain: &'a str) -> &mut Self { + self.chain = chain; + + self + } + /// Sends the transaction. pub fn send(&mut self) -> Result<(), Error> { self.test_ctx.tx_upload_contracts( self.key .ok_or(Error::MissingBuilderParam(String::from("key")))?, + self.chain, ) } @@ -53,11 +61,12 @@ impl TestContext { pub fn build_tx_upload_contracts(&mut self) -> UploadContractsTxBuilder { UploadContractsTxBuilder { key: Some(DEFAULT_KEY), + chain: NEUTRON_CHAIN_NAME, test_ctx: self, } } - fn tx_upload_contracts(&mut self, key: &str) -> Result<(), Error> { + fn tx_upload_contracts(&mut self, key: &str, chain: &str) -> Result<(), Error> { fs::read_dir(&self.artifacts_dir)? .filter_map(|dir_ent| dir_ent.ok()) .filter(|dir_ent| { @@ -67,9 +76,9 @@ impl TestContext { .map(fs::canonicalize) .try_for_each(|maybe_abs_path| { let path = maybe_abs_path?; - let neutron_local_chain = self.get_mut_chain(NEUTRON_CHAIN_NAME); + let chain = self.get_mut_chain(chain); - let mut cw = CosmWasm::new(&neutron_local_chain.rb); + let mut cw = CosmWasm::new(&chain.rb); let code_id = cw.store(key, &path)?; @@ -77,9 +86,7 @@ impl TestContext { .file_stem() .and_then(|stem| stem.to_str()) .ok_or(Error::Misc(String::from("failed to format file path")))?; - neutron_local_chain - .contract_codes - .insert(id.to_string(), code_id); + chain.contract_codes.insert(id.to_string(), code_id); Ok(()) }) diff --git a/src/utils/queries.rs b/src/utils/queries.rs index 0490849..740d6af 100644 --- a/src/utils/queries.rs +++ b/src/utils/queries.rs @@ -1,6 +1,6 @@ use crate::{ types::ibc::{get_prefixed_denom, parse_denom_trace}, - TRANSFER_PORT, + AUCTIONS_MANAGER_CONTRACT_NAME, FACTORY_ON_OSMOSIS_NAME, TRANSFER_PORT, }; use super::{ @@ -9,182 +9,451 @@ use super::{ PAIR_NAME, PRICE_ORACLE_NAME, STABLE_PAIR_NAME, TX_HASH_QUERY_PAUSE_SEC, TX_HASH_QUERY_RETRIES, }, - test_context::TestContext, + test_context::{LocalChain, TestContext}, }; -use localic_std::modules::cosmwasm::CosmWasm; +use localic_std::{modules::cosmwasm::CosmWasm, transactions::ChainRequestBuilder}; use serde_json::Value; use std::{path::PathBuf, thread, time::Duration}; -impl TestContext { - /// Gets the event log of a transaction as a JSON object, - /// or returns an error if it does not exist. - pub fn guard_tx_errors(&self, chain_name: &str, hash: &str) -> Result<(), Error> { - if !self.unwrap_logs { - return Ok(()); +pub enum QueryType { + TransferChannel, + Connection, + CCVChannel, + IBCDenom, + AdminAddr, + NativeDenom, + ChainPrefix, + RequestBuilder, + BuiltContractAddress, + CodeInfo, + Contract, + AuctionsManager, + PriceOracle, + Auction, + TokenfactoryDenom, + Factory, + AstroPool, + OsmoPool, +} + +pub struct TestContextQuery<'a> { + context: &'a TestContext, + query_type: QueryType, + src_chain: Option, + dest_chain: Option, + contract_name: Option, + + // denoms in a pool + offer_asset: Option, + ask_asset: Option, + denoms: Option<(String, String)>, + + base_denom: Option, + subdenom: Option, + + // build-contract-address query args + creator_address: Option, + salt_hex_encoded: Option, +} + +impl<'a> TestContextQuery<'a> { + pub fn new(context: &'a TestContext, query_type: QueryType) -> Self { + Self { + context, + query_type, + src_chain: Some(NEUTRON_CHAIN_NAME.to_owned()), + dest_chain: None, + contract_name: None, + offer_asset: None, + ask_asset: None, + base_denom: None, + subdenom: None, + denoms: None, + creator_address: None, + salt_hex_encoded: None, } + } - let chain = self.get_chain(chain_name); - let mut logs = None; + pub fn src(mut self, src_chain: &str) -> Self { + self.src_chain = Some(src_chain.to_string()); + self + } - for _ in 0..TX_HASH_QUERY_RETRIES { - thread::sleep(Duration::from_secs(TX_HASH_QUERY_PAUSE_SEC)); + pub fn dest(mut self, dest_chain: &str) -> Self { + self.dest_chain = Some(dest_chain.to_string()); + self + } - let mut tx_res = chain.rb.query_tx_hash(hash); + pub fn contract(mut self, contract_name: &str) -> Self { + self.contract_name = Some(contract_name.to_string()); + self + } - if tx_res.get("raw_log").is_none() { - continue; - } + pub fn offer_asset(mut self, offer_asset: &str) -> Self { + self.offer_asset = Some(offer_asset.to_owned()); + self + } - logs = Some(tx_res["raw_log"].take()); + pub fn ask_asset(mut self, ask_asset: &str) -> Self { + self.ask_asset = Some(ask_asset.to_owned()); + self + } - break; - } + pub fn denoms(mut self, denom_a: String, denom_b: String) -> Self { + self.denoms = Some((denom_a, denom_b)); + self + } - let raw_log = logs.as_ref().and_then(|raw_log| raw_log.as_str()).unwrap(); + pub fn base_denom(mut self, base_denom: String) -> Self { + self.base_denom = Some(base_denom); + self + } - if raw_log.is_empty() { - return Ok(()); + pub fn subdenom(mut self, subdenom: String) -> Self { + self.subdenom = Some(subdenom); + self + } + + pub fn creator(mut self, creator_addr: &str) -> Self { + self.creator_address = Some(creator_addr.to_owned()); + self + } + + pub fn salt_hex_encoded(mut self, salt_hex_encoded: &str) -> Self { + self.salt_hex_encoded = Some(salt_hex_encoded.to_owned()); + self + } + + pub fn get(self) -> String { + // None cases explicitly enumerated here to ensure compilation-time + // checking of query inclusion in some get_x method + match self.query_type { + QueryType::TransferChannel => self.get_transfer_channel().map(ToOwned::to_owned), + QueryType::Connection => self.get_connection_id().map(ToOwned::to_owned), + QueryType::CCVChannel => self.get_ccv_channel().map(ToOwned::to_owned), + QueryType::IBCDenom => self.get_ibc_denom(), + QueryType::AdminAddr => self.get_admin_addr().map(ToOwned::to_owned), + QueryType::NativeDenom => self.get_native_denom().map(ToOwned::to_owned), + QueryType::ChainPrefix => self.get_chain_prefix().map(ToOwned::to_owned), + QueryType::BuiltContractAddress => self.get_built_contract_address(), + QueryType::TokenfactoryDenom => self.get_tokenfactory_denom(), + QueryType::CodeInfo + | QueryType::OsmoPool + | QueryType::AuctionsManager + | QueryType::AstroPool + | QueryType::Auction + | QueryType::Contract + | QueryType::Factory + | QueryType::RequestBuilder + | QueryType::PriceOracle => None, } + .unwrap() + } - let logs = serde_json::from_str::(raw_log).map_err(|_| Error::TxFailed { - hash: hash.to_owned(), - error: raw_log.to_owned(), - })?; + pub fn get_cw(self) -> CosmWasm<'a> { + match self.query_type { + QueryType::Contract => self.get_contract(), + QueryType::Factory => match self.src_chain.as_deref().unwrap() { + OSMOSIS_CHAIN_NAME => self + .contract(FACTORY_ON_OSMOSIS_NAME) + .get_deployed_contract(), + _ => self.contract(FACTORY_NAME).get_deployed_contract(), + }, + QueryType::PriceOracle => self.contract(PRICE_ORACLE_NAME).get_deployed_contract(), + QueryType::AuctionsManager => self + .contract(AUCTIONS_MANAGER_CONTRACT_NAME) + .get_deployed_contract(), + QueryType::Auction => self.get_auction(), + QueryType::AstroPool => self.get_astro_pool(), + QueryType::TransferChannel + | QueryType::Connection + | QueryType::CCVChannel + | QueryType::IBCDenom + | QueryType::AdminAddr + | QueryType::NativeDenom + | QueryType::ChainPrefix + | QueryType::BuiltContractAddress + | QueryType::TokenfactoryDenom + | QueryType::OsmoPool + | QueryType::RequestBuilder + | QueryType::CodeInfo => None, + } + .unwrap() + } - if let Some(err) = logs.as_str() { - return Err(Error::TxFailed { - hash: hash.to_owned(), - error: err.to_owned(), - }); + pub fn get_value(self) -> Value { + match self.query_type { + QueryType::CodeInfo => self.get_code_info(), + QueryType::TransferChannel + | QueryType::Connection + | QueryType::CCVChannel + | QueryType::IBCDenom + | QueryType::AdminAddr + | QueryType::NativeDenom + | QueryType::ChainPrefix + | QueryType::BuiltContractAddress + | QueryType::TokenfactoryDenom + | QueryType::OsmoPool + | QueryType::AuctionsManager + | QueryType::AstroPool + | QueryType::Auction + | QueryType::Contract + | QueryType::Factory + | QueryType::RequestBuilder + | QueryType::PriceOracle => None, } + .unwrap() + } - Ok(()) + pub fn get_u64(self) -> u64 { + match self.query_type { + QueryType::OsmoPool => self.get_osmo_pool(), + QueryType::TransferChannel + | QueryType::Connection + | QueryType::CCVChannel + | QueryType::IBCDenom + | QueryType::AdminAddr + | QueryType::NativeDenom + | QueryType::ChainPrefix + | QueryType::BuiltContractAddress + | QueryType::TokenfactoryDenom + | QueryType::AuctionsManager + | QueryType::AstroPool + | QueryType::Auction + | QueryType::Contract + | QueryType::Factory + | QueryType::RequestBuilder + | QueryType::PriceOracle + | QueryType::CodeInfo => None, + } + .unwrap() } - /// Get a new CosmWasm instance for a contract identified by a name. - pub fn get_contract(&self, name: &str) -> Result { - let chain = self.get_chain(NEUTRON_CHAIN_NAME); + pub fn get_all(self) -> Vec { + match self.query_type { + QueryType::TransferChannel => self.get_all_transfer_channels(), + QueryType::Connection => self.get_all_connections(), + _ => vec![], + } + .into_iter() + .map(ToOwned::to_owned) + .collect::>() + } - let code_id = chain - .contract_codes - .get(name) - .ok_or(Error::Misc(format!("contract '{name}' is missing")))?; + pub fn get_request_builder(mut self, chain: &str) -> &'a ChainRequestBuilder { + self.src_chain = Some(chain.to_string()); + let rb = match self.query_type { + QueryType::RequestBuilder => self.get_rb(), + _ => None, + }; + rb.unwrap() + } - let artifacts_path = &self.artifacts_dir; + fn get_transfer_channel(&self) -> Option<&str> { + self.context + .transfer_channel_ids + .get(&(self.src_chain.clone()?, self.dest_chain.clone()?)) + .map(|s| s.as_str()) + } - Ok(CosmWasm::new_from_existing( - &chain.rb, - Some(PathBuf::from(format!("{artifacts_path}/{name}.wasm"))), - Some(*code_id), - None, - )) + fn get_all_transfer_channels(&self) -> Vec<&str> { + self.src_chain + .as_ref() + .map(|src| { + self.context + .transfer_channel_ids + .iter() + .filter(|((s, _), _)| s == src) + .map(|(_, v)| v.as_str()) + .collect::>() + }) + .unwrap_or_default() } - /// Get a new CosmWasm instance for the existing deployed auctions manager. - pub fn get_auctions_manager(&self) -> Result { - let neutron = self.get_chain(NEUTRON_CHAIN_NAME); + fn get_connection_id(&self) -> Option<&str> { + self.context + .connection_ids + .get(&(self.src_chain.clone()?, self.dest_chain.clone()?)) + .map(|s| s.as_str()) + } - let contract_info = self - .auctions_manager + fn get_all_connections(&self) -> Vec<&str> { + self.src_chain .as_ref() - .ok_or(Error::MissingContextVariable(String::from( - "auctions_manager", - )))?; - - Ok(CosmWasm::new_from_existing( - &neutron.rb, - Some(contract_info.artifact_path.clone()), - Some(contract_info.code_id), - Some(contract_info.address.clone()), - )) + .map(|src| { + self.context + .connection_ids + .iter() + .filter(|((s, _), _)| s == src) + .map(|(_, s)| s.as_str()) + .collect::>() + }) + .unwrap_or_default() } - /// Get a new CosmWasm instance for the existing deployed auctions manager. - pub fn get_price_oracle(&self) -> Result { - let neutron = self.get_chain(NEUTRON_CHAIN_NAME); + fn get_ccv_channel(&self) -> Option<&str> { + self.context + .ccv_channel_ids + .get(&(self.src_chain.clone()?, self.dest_chain.clone()?)) + .map(|s| s.as_str()) + } - let mut contract = self.get_contract(PRICE_ORACLE_NAME)?; - let contract_addr = - neutron - .contract_addrs - .get(PRICE_ORACLE_NAME) - .ok_or(Error::MissingContextVariable(String::from( - "contract_addrs::price_oracle", - )))?; - contract.contract_addr = Some(contract_addr.clone()); + fn get_ibc_denom(&self) -> Option { + let dest_chain = self.dest_chain.as_deref()?; + let src_chain = self.src_chain.as_deref()?; - Ok(contract) + let channel_id = self + .context + .get_transfer_channels() + .src(dest_chain) + .dest(src_chain) + .get(); + + let prefixed_denom = get_prefixed_denom( + TRANSFER_PORT.to_string(), + channel_id.to_string(), + self.base_denom.clone()?, + ); + + let src_denom_trace = parse_denom_trace(prefixed_denom); + let ibc_denom = src_denom_trace.ibc_denom(); + + Some(ibc_denom) } - /// Gets a CosmWasm instance for an auction with a given pair. - pub fn get_auction, TDenomB: AsRef>( - &self, - denoms: (TDenomA, TDenomB), - ) -> Result { - let mut auction_contract = self.get_contract(AUCTION_CONTRACT_NAME)?; + fn get_admin_addr(&self) -> Option<&str> { + let src = self.src_chain.as_deref()?; - // The auctions manager for this deployment - let contract_a = self.get_auctions_manager()?; + Some(self.context.chains.get(src)?.admin_addr.as_ref()) + } - // Get the address of the auction specified - let resp = contract_a.query_value(&serde_json::json!({ - "get_pair_addr": { - "pair": (denoms.0.as_ref(), denoms.1.as_ref()) - } - })); + fn get_native_denom(&self) -> Option<&str> { + let src = self.src_chain.as_deref()?; - auction_contract.contract_addr = Some( - resp.get("data") - .and_then(|json| json.as_str()) - .ok_or(Error::Misc(format!("tx failed with resp: {:?}", resp)))? - .to_owned(), - ); + Some(self.context.chains.get(src)?.native_denom.as_ref()) + } + + fn get_chain_prefix(&self) -> Option<&str> { + let src = self.src_chain.as_deref()?; - Ok(auction_contract) + Some(self.context.chains.get(src)?.chain_prefix.as_ref()) } - pub fn get_tokenfactory_denom(&self, creator_addr: &str, subdenom: &str) -> String { - format!("factory/{creator_addr}/{subdenom}") + fn get_code_info(&self) -> Option { + let contract = self + .context + .get_contract() + .src(self.src_chain.as_deref()?) + .contract(self.contract_name.as_ref()?) + .get_cw(); + let code_id = contract.code_id?; + let chain = self.context.chains.get(self.src_chain.as_deref()?)?; + + // This will produce a { ... text: "{ 'data_hash': xyz }" }. Get the code info enclosed + let resp = chain.rb.query(&format!("q wasm code-info {code_id}"), true); + + let str_info_object = resp["text"].as_str()?; + serde_json::from_str(str_info_object).ok() } - /// Gets the deployed atroport factory for Neutron. - pub fn get_astroport_factory(&self) -> Result { - let neutron = self.get_chain(NEUTRON_CHAIN_NAME); + fn get_tokenfactory_denom(&self) -> Option { + let creator_addr = self.creator_address.as_deref()?; + let subdenom = self.subdenom.as_deref()?; + + Some(format!("factory/{creator_addr}/{subdenom}")) + } + + fn get_built_contract_address(&self) -> Option { + let code_info = self.get_code_info()?; + let code_id_hash = code_info["data_hash"].as_str()?; + + let creator_address = self.creator_address.as_ref()?; + let salt = self.salt_hex_encoded.as_deref()?; + + let chain = self.context.chains.get(self.src_chain.as_deref()?)?; - let code_id = - neutron - .contract_codes - .get(FACTORY_NAME) - .ok_or(Error::MissingContextVariable(format!( - "contract_codes::{FACTORY_NAME}", - )))?; - let contract_addr = - neutron - .contract_addrs - .get(FACTORY_NAME) - .ok_or(Error::MissingContextVariable(format!( - "contract_addrs::{FACTORY_NAME}", - )))?; + let mut resp = chain.rb.bin( + &format!("q wasm build-address {code_id_hash} {creator_address} {salt}"), + true, + ); - let artifacts_path = self.artifacts_dir.as_str(); + // text field contains built address + match resp["text"].take() { + Value::String(s) => Some(s.replace("\n", "")), + _ => None, + } + } - Ok(CosmWasm::new_from_existing( - &neutron.rb, - Some(PathBuf::from(format!( - "{artifacts_path}/{FACTORY_NAME}.wasm" - ))), + fn get_contract(&self) -> Option> { + let chain: &LocalChain = self.context.get_chain(self.src_chain.as_deref()?); + let name = self.contract_name.as_deref()?; + + let code_id = chain.contract_codes.get(name)?; + let artifacts_path = &self.context.artifacts_dir; + + Some(CosmWasm::new_from_existing( + &chain.rb, + Some(PathBuf::from(format!("{artifacts_path}/{name}.wasm"))), Some(*code_id), - Some(contract_addr.clone()), + None, )) } - /// Gets a previously deployed astroport pair. - pub fn get_astroport_pool( - &self, - denom_a: impl AsRef, - denom_b: impl AsRef, - ) -> Result { - let factory = self.get_astroport_factory()?; + fn get_deployed_contract(&self) -> Option> { + let chain = self.context.get_chain(self.src_chain.as_deref()?); + let name = self.contract_name.as_deref()?; + + let code_id = chain.contract_codes.get(name)?; + let contract_addr = chain.contract_addrs.get(name)?.clone(); + let artifacts_path = &self.context.artifacts_dir; + + Some(CosmWasm::new_from_existing( + &chain.rb, + Some(PathBuf::from(format!("{artifacts_path}/{name}.wasm"))), + Some(*code_id), + Some(contract_addr), + )) + } + + /// Multiple auctions might exist, so contract_addrs singleton info + /// cannot be used + fn get_auction(&self) -> Option> { + let mut auction_contract = self + .context + .get_contract() + .src(self.src_chain.as_deref()?) + .contract(AUCTION_CONTRACT_NAME) + .get_cw(); + + let denoms = (self.offer_asset.as_deref()?, self.ask_asset.as_deref()?); + + // The auctions manager for this deployment + let contract_a = self + .context + .get_auctions_manager() + .src(self.src_chain.as_deref()?) + .get_cw(); + + // Get the address of the auction specified + let resp = contract_a.query_value(&serde_json::json!({ + "get_pair_addr": { + "pair": (denoms.0, denoms.1) + } + })); + + auction_contract.contract_addr = + Some(resp.get("data").and_then(|json| json.as_str())?.to_owned()); + + Some(auction_contract) + } + + fn get_astro_pool(&self) -> Option> { + let (denom_a, denom_b) = self.denoms.as_ref()?; + let factory = self + .context + .get_factory() + .src(self.src_chain.as_deref()?) + .get_cw(); let pair_info = factory.query_value(&serde_json::json!( { @@ -192,12 +461,12 @@ impl TestContext { "asset_infos": [ { "native_token": { - "denom": denom_a.as_ref(), + "denom": denom_a, } }, { "native_token": { - "denom": denom_b.as_ref(), + "denom": denom_b, } } ] @@ -215,45 +484,51 @@ impl TestContext { .and_then(|data| data.get("pair_type")) .unwrap(); - let neutron = self.get_chain(NEUTRON_CHAIN_NAME); + let chain = self.context.get_chain(self.src_chain.as_deref()?); if kind.get("xyk").is_some() { - let contract = self.get_contract(PAIR_NAME)?; - - return Ok(CosmWasm::new_from_existing( - &neutron.rb, + let contract = self + .context + .get_contract() + .contract(PAIR_NAME) + .src(self.src_chain.as_deref()?) + .get_cw(); + + return Some(CosmWasm::new_from_existing( + &chain.rb, contract.file_path, contract.code_id, Some(addr.to_owned()), )); } - let contract = self.get_contract(STABLE_PAIR_NAME)?; + let contract = self + .context + .get_contract() + .contract(STABLE_PAIR_NAME) + .src(self.src_chain.as_deref()?) + .get_cw(); - Ok(CosmWasm::new_from_existing( - &neutron.rb, + Some(CosmWasm::new_from_existing( + &chain.rb, contract.file_path, contract.code_id, Some(addr.to_owned()), )) } - /// Gets the id of the pool with the specifieed denoms. - pub fn get_osmo_pool( - &self, - denom_a: impl AsRef, - denom_b: impl AsRef, - ) -> Result { - let osmosis = self.get_chain(OSMOSIS_CHAIN_NAME); - let denom_a_str = denom_a.as_ref(); + fn get_osmo_pool(&self) -> Option { + // Do not use get_chain here, since we only want to support osmosis pools on osmosis + let (denom_a, denom_b) = self.denoms.as_ref()?; + let osmosis = self.context.get_chain(OSMOSIS_CHAIN_NAME); let res = osmosis.rb.query( - &format!("q poolmanager list-pools-by-denom {denom_a_str} --output=json"), + &format!("q poolmanager list-pools-by-denom {denom_a} --output=json"), true, ); let res_text = res.get("text").and_then(|v| v.as_str()).unwrap(); - let res_value: Value = serde_json::from_str(res_text)?; + let res_value: Value = serde_json::from_str(res_text).ok()?; let pools_value = res_value.get("pools").unwrap(); let pool = pools_value @@ -276,42 +551,142 @@ impl TestContext { .and_then(|id_str| id_str.as_str()) .unwrap(); - Ok(pool.parse().unwrap()) + Some(pool.parse().unwrap()) } - /// Gets the IBC denom for a base denom given a src and dest chain. - pub fn get_ibc_denom(&mut self, base_denom: &str, src_chain: &str, dest_chain: &str) -> String { - if let Some(denom) = self - .ibc_denoms - .get(&(base_denom.to_string(), dest_chain.to_string())) - { - return denom.clone(); + fn get_rb(&self) -> Option<&'a ChainRequestBuilder> { + if let Some(ref src) = self.src_chain { + self.context.chains.get(src).map(|chain| &chain.rb) + } else { + None } + } +} - let channel_id = self - .get_transfer_channels() - .src(dest_chain) - .dest(src_chain) - .get(); +impl TestContext { + pub fn get_transfer_channels(&self) -> TestContextQuery { + TestContextQuery::new(self, QueryType::TransferChannel) + } - let prefixed_denom = get_prefixed_denom( - TRANSFER_PORT.to_string(), - channel_id.to_string(), - base_denom.to_string(), - ); + pub fn get_connections(&self) -> TestContextQuery { + TestContextQuery::new(self, QueryType::Connection) + } - let src_denom_trace = parse_denom_trace(prefixed_denom); - let ibc_denom = src_denom_trace.ibc_denom(); + pub fn get_ccv_channels(&self) -> TestContextQuery { + TestContextQuery::new(self, QueryType::CCVChannel) + } - self.ibc_denoms.insert( - (base_denom.to_string(), dest_chain.to_string()), - ibc_denom.clone(), - ); - self.ibc_denoms.insert( - (ibc_denom.clone(), src_chain.to_string()), - base_denom.to_string(), - ); + pub fn get_ibc_denom(&self) -> TestContextQuery { + TestContextQuery::new(self, QueryType::IBCDenom) + } + + pub fn get_admin_addr(&self) -> TestContextQuery { + TestContextQuery::new(self, QueryType::AdminAddr) + } + + pub fn get_native_denom(&self) -> TestContextQuery { + TestContextQuery::new(self, QueryType::NativeDenom) + } + + pub fn get_chain_prefix(&self) -> TestContextQuery { + TestContextQuery::new(self, QueryType::ChainPrefix) + } + + pub fn get_request_builder(&self) -> TestContextQuery { + TestContextQuery::new(self, QueryType::RequestBuilder) + } + + pub fn get_code_info(&self) -> TestContextQuery { + TestContextQuery::new(self, QueryType::CodeInfo) + } + + pub fn get_built_contract_address(&self) -> TestContextQuery { + TestContextQuery::new(self, QueryType::BuiltContractAddress) + } - ibc_denom + pub fn get_contract(&self) -> TestContextQuery { + TestContextQuery::new(self, QueryType::Contract) + } + + pub fn get_tokenfactory_denom(&self) -> TestContextQuery { + TestContextQuery::new(self, QueryType::TokenfactoryDenom) + } + + pub fn get_price_oracle(&self) -> TestContextQuery { + TestContextQuery::new(self, QueryType::PriceOracle).contract(PRICE_ORACLE_NAME) + } + + pub fn get_auction(&self) -> TestContextQuery { + TestContextQuery::new(self, QueryType::Auction).contract(AUCTION_CONTRACT_NAME) + } + + pub fn get_factory(&self) -> TestContextQuery { + TestContextQuery::new(self, QueryType::Factory).contract(FACTORY_NAME) + } + + pub fn get_astro_pool(&self) -> TestContextQuery { + TestContextQuery::new(self, QueryType::AstroPool) + } + + pub fn get_osmo_pool(&self) -> TestContextQuery { + TestContextQuery::new(self, QueryType::OsmoPool) + } + + pub fn get_auctions_manager(&self) -> TestContextQuery { + TestContextQuery::new(self, QueryType::AuctionsManager) + .contract(AUCTIONS_MANAGER_CONTRACT_NAME) + } + + pub fn get_chain(&self, chain_name: &str) -> &LocalChain { + self.chains.get(chain_name).unwrap() + } + + pub fn get_mut_chain(&mut self, chain_name: &str) -> &mut LocalChain { + self.chains.get_mut(chain_name).unwrap() + } + + /// Gets the event log of a transaction as a JSON object, + /// or returns an error if it does not exist. + pub fn guard_tx_errors(&self, chain_name: &str, hash: &str) -> Result<(), Error> { + if !self.unwrap_logs { + return Ok(()); + } + + let chain = self.get_chain(chain_name); + let mut logs = None; + + for _ in 0..TX_HASH_QUERY_RETRIES { + thread::sleep(Duration::from_secs(TX_HASH_QUERY_PAUSE_SEC)); + + let mut tx_res = chain.rb.query_tx_hash(hash); + + if tx_res.get("raw_log").is_none() { + continue; + } + + logs = Some(tx_res["raw_log"].take()); + + break; + } + + let raw_log = logs.as_ref().and_then(|raw_log| raw_log.as_str()).unwrap(); + + if raw_log.is_empty() { + return Ok(()); + } + + let logs = serde_json::from_str::(raw_log).map_err(|_| Error::TxFailed { + hash: hash.to_owned(), + error: raw_log.to_owned(), + })?; + + if let Some(err) = logs.as_str() { + return Err(Error::TxFailed { + hash: hash.to_owned(), + error: err.to_owned(), + }); + } + + Ok(()) } } diff --git a/src/utils/setup/astroport.rs b/src/utils/setup/astroport.rs index deac752..7d1d3a1 100644 --- a/src/utils/setup/astroport.rs +++ b/src/utils/setup/astroport.rs @@ -1,8 +1,9 @@ use super::super::{ super::{ - error::Error, types::contract::DeployedContractInfo, DEFAULT_KEY, FACTORY_NAME, - NEUTRON_CHAIN_ADMIN_ADDR, NEUTRON_CHAIN_NAME, PAIR_NAME, STABLE_PAIR_NAME, TOKEN_NAME, - TOKEN_REGISTRY_NAME, WHITELIST_NAME, + error::Error, CW1_WHITELIST_NAME, DEFAULT_KEY, FACTORY_NAME, FACTORY_ON_OSMOSIS_NAME, + NEUTRON_CHAIN_ADMIN_ADDR, NEUTRON_CHAIN_NAME, OSMOSIS_CHAIN_NAME, + OSMOSIS_PCL_POOL_TYPE_NAME, PAIR_NAME, PAIR_PCL_ON_OSMOSIS_NAME, STABLE_PAIR_NAME, + TOKEN_NAME, TOKEN_REGISTRY_NAME, WHITELIST_NAME, }, test_context::TestContext, }; @@ -17,6 +18,7 @@ use cosmwasm_std::Decimal; pub struct CreateTokenRegistryTxBuilder<'a> { key: Option<&'a str>, owner: Option, + chain: &'a str, test_ctx: &'a mut TestContext, } @@ -27,6 +29,12 @@ impl<'a> CreateTokenRegistryTxBuilder<'a> { self } + pub fn with_chain(&mut self, chain: &'a str) -> &mut Self { + self.chain = chain; + + self + } + pub fn with_owner(&mut self, owner: impl Into) -> &mut Self { self.owner = Some(owner.into()); @@ -38,6 +46,7 @@ impl<'a> CreateTokenRegistryTxBuilder<'a> { self.test_ctx.tx_create_token_registry( self.key .ok_or(Error::MissingBuilderParam(String::from("key")))?, + self.chain, self.owner .clone() .ok_or(Error::MissingBuilderParam(String::from("owner")))?, @@ -48,6 +57,7 @@ impl<'a> CreateTokenRegistryTxBuilder<'a> { /// A tx creating a token registry. pub struct CreatePoolTxBuilder<'a> { key: &'a str, + chain: &'a str, pair_type: PairType, denom_a: Option, denom_b: Option, @@ -61,6 +71,12 @@ impl<'a> CreatePoolTxBuilder<'a> { self } + pub fn with_chain(&mut self, chain: &'a str) -> &mut Self { + self.chain = chain; + + self + } + pub fn with_pairtype(&mut self, pairtype: PairType) -> &mut Self { self.pair_type = pairtype; @@ -83,6 +99,7 @@ impl<'a> CreatePoolTxBuilder<'a> { pub fn send(&mut self) -> Result<(), Error> { self.test_ctx.tx_create_pool( self.key, + self.chain, self.pair_type.clone(), self.denom_a .clone() @@ -98,6 +115,7 @@ impl<'a> CreatePoolTxBuilder<'a> { pub struct CreateFactoryTxBuilder<'a> { key: &'a str, owner: String, + chain: &'a str, test_ctx: &'a mut TestContext, } @@ -114,16 +132,23 @@ impl<'a> CreateFactoryTxBuilder<'a> { self } + pub fn with_chain(&mut self, chain: &'a str) -> &mut Self { + self.chain = chain; + + self + } + /// Sends the transaction. pub fn send(&mut self) -> Result<(), Error> { self.test_ctx - .tx_create_factory(self.key, self.owner.clone()) + .tx_create_factory(self.key, self.chain, self.owner.clone()) } } /// A tx funding an astroport pool. pub struct FundPoolTxBuilder<'a> { key: &'a str, + chain: &'a str, denom_a: Option, denom_b: Option, amt_denom_a: Option, @@ -140,6 +165,12 @@ impl<'a> FundPoolTxBuilder<'a> { self } + pub fn with_chain(&mut self, chain: &'a str) -> &mut Self { + self.chain = chain; + + self + } + pub fn with_denom_a(&mut self, denom_a: impl Into) -> &mut Self { self.denom_a = Some(denom_a.into()); @@ -180,6 +211,7 @@ impl<'a> FundPoolTxBuilder<'a> { pub fn send(&mut self) -> Result<(), Error> { self.test_ctx.tx_fund_pool( self.key, + self.chain, self.denom_a .clone() .ok_or(Error::MissingBuilderParam(String::from("denom_a")))?, @@ -207,6 +239,7 @@ impl TestContext { CreateTokenRegistryTxBuilder { key: Some(DEFAULT_KEY), owner: Some(NEUTRON_CHAIN_ADMIN_ADDR.to_owned()), + chain: NEUTRON_CHAIN_NAME, test_ctx: self, } } @@ -215,14 +248,16 @@ impl TestContext { fn tx_create_token_registry( &mut self, key: &str, + chain_name: &str, owner_addr: impl Into, ) -> Result<(), Error> { - let mut contract_a = self.get_contract(TOKEN_REGISTRY_NAME)?; - let code_id = contract_a - .code_id - .ok_or(Error::MissingContextVariable(String::from( - "astroport_token_registry::code_id", - )))?; + let native_denom = self.get_native_denom().src(chain_name).get().clone(); + + let mut contract_a = self + .get_contract() + .src(chain_name) + .contract(TOKEN_REGISTRY_NAME) + .get_cw(); let contract = contract_a.instantiate( key, @@ -232,28 +267,16 @@ impl TestContext { .as_str(), TOKEN_REGISTRY_NAME, None, - "--gas 1000000", + &format!("--gas 1000000 --fees 42069420{native_denom}"), )?; let addr = contract.address; - let artifact_path = - contract_a - .file_path - .ok_or(Error::MissingContextVariable(String::from( - "astroport_token_registry::artifact_path", - )))?; - let neutron = self.get_mut_chain(NEUTRON_CHAIN_NAME); + let chain = self.get_mut_chain(chain_name); - neutron + chain .contract_addrs .insert(TOKEN_REGISTRY_NAME.to_owned(), addr.clone()); - self.astroport_token_registry = Some(DeployedContractInfo { - code_id, - address: addr, - artifact_path, - }); - Ok(()) } @@ -262,6 +285,7 @@ impl TestContext { CreateFactoryTxBuilder { key: DEFAULT_KEY, owner: NEUTRON_CHAIN_ADMIN_ADDR.to_owned(), + chain: NEUTRON_CHAIN_NAME, test_ctx: self, } } @@ -270,46 +294,60 @@ impl TestContext { fn tx_create_factory( &mut self, key: &str, + chain_name: &str, factory_owner: impl Into, ) -> Result<(), Error> { - let neutron = self.get_chain(NEUTRON_CHAIN_NAME); + if chain_name == OSMOSIS_CHAIN_NAME { + // Osmosis setup should be handled differently with astroport-on-osmosis contract:s + return self.tx_create_factory_osmo(key, factory_owner); + } + + // Assume neutron, or some neutron-capable chain, otherwise + // bubble-up missing capabilities error to the user + let chain = self.get_chain(chain_name); let pair_xyk_code_id = - neutron + chain .contract_codes .get(PAIR_NAME) .ok_or(Error::MissingContextVariable(String::from( "contract_codes::astroport_pair", )))?; let pair_stable_code_id = - neutron + chain .contract_codes .get(STABLE_PAIR_NAME) .ok_or(Error::MissingContextVariable(String::from( "contract_codes::astroport_pair_stable", )))?; let token_code_id = - neutron + chain .contract_codes .get(TOKEN_NAME) .ok_or(Error::MissingContextVariable(String::from( "contract_codes::cw20_base", )))?; let whitelist_code_id = - neutron + chain .contract_codes .get(WHITELIST_NAME) .ok_or(Error::MissingContextVariable(String::from( "contract_codes::astroport_whitelist", )))?; - let native_registry_addr = neutron.contract_addrs.get(TOKEN_REGISTRY_NAME).ok_or( - Error::MissingContextVariable(String::from( - "contract_ddrs::astroport_native_coin_registry", - )), - )?; + let native_registry_addr = + chain + .contract_addrs + .get(TOKEN_REGISTRY_NAME) + .ok_or(Error::MissingContextVariable(String::from( + "contract_ddrs::astroport_native_coin_registry", + )))?; - let mut contract_a = self.get_contract(FACTORY_NAME)?; + let mut contract_a = self + .get_contract() + .src(chain_name) + .contract(FACTORY_NAME) + .get_cw(); let contract = contract_a.instantiate( key, @@ -348,19 +386,106 @@ impl TestContext { "", )?; - let neutron = self.get_mut_chain(NEUTRON_CHAIN_NAME); + let chain = self.get_mut_chain(chain_name); - neutron + chain .contract_addrs .insert(FACTORY_NAME.to_owned(), contract.address); Ok(()) } + fn tx_create_factory_osmo( + &mut self, + key: &str, + factory_owner: impl Into, + ) -> Result<(), Error> { + let chain = self.get_chain(OSMOSIS_CHAIN_NAME); + + // Pcl contract code ID for a custom pool + let pair_pcl_code_id = self + .get_contract() + .contract(PAIR_PCL_ON_OSMOSIS_NAME) + .src(OSMOSIS_CHAIN_NAME) + .get_cw() + .code_id + .unwrap(); + + // Cw20 base code ID + let token_code_id = self + .get_contract() + .contract(TOKEN_NAME) + .src(OSMOSIS_CHAIN_NAME) + .get_cw() + .code_id + .unwrap(); + + // Don't use the astroport whitelist here, since it does not support osmosis + // use the cw plus whitelist + let whitelist_code_id = self + .get_contract() + .contract(CW1_WHITELIST_NAME) + .src(OSMOSIS_CHAIN_NAME) + .get_cw() + .code_id + .unwrap(); + + // Instantiate the osmosis factory + let mut contract_a = self + .get_contract() + .src(OSMOSIS_CHAIN_NAME) + .contract(FACTORY_ON_OSMOSIS_NAME) + .get_cw(); + + // Get the deployed native coin registry + let native_registry_addr = + chain + .contract_addrs + .get(TOKEN_REGISTRY_NAME) + .ok_or(Error::MissingContextVariable(String::from( + "contract_ddrs::astroport_native_coin_registry", + )))?; + + println!("B: {:?}", pair_pcl_code_id); + + // Enable PCL (custom) pools only + let contract = contract_a.instantiate( + key, + &serde_json::to_string(&serde_json::json!({ + "pair_configs": vec![PairConfig { + code_id: pair_pcl_code_id, + pair_type: PairType::Custom(String::from(OSMOSIS_PCL_POOL_TYPE_NAME)), + total_fee_bps: 100, + maker_fee_bps: 10, + is_disabled: false, + is_generator_disabled: false, + permissioned: false, + }], + "token_code_id": token_code_id, + "owner": factory_owner.into(), + "whitelist_code_id": whitelist_code_id, + "coin_registry_address": native_registry_addr.clone(), + })) + .unwrap(), + FACTORY_NAME, + None, + &format!("--gas 1000000 --fees 42069420uosmo"), + )?; + + let chain = self.get_mut_chain(OSMOSIS_CHAIN_NAME); + + chain + .contract_addrs + .insert(FACTORY_ON_OSMOSIS_NAME.to_owned(), contract.address); + + Ok(()) + } + /// Creates a pool with the specififed denoms. pub fn build_tx_create_pool(&mut self) -> CreatePoolTxBuilder { CreatePoolTxBuilder { key: DEFAULT_KEY, + chain: NEUTRON_CHAIN_NAME, pair_type: PairType::Xyk {}, denom_a: Default::default(), denom_b: Default::default(), @@ -372,12 +497,14 @@ impl TestContext { fn tx_create_pool( &self, key: &str, + chain: &str, pair_type: PairType, denom_a: impl Into, denom_b: impl Into, ) -> Result<(), Error> { // Factory contract instance - let contract_a = self.get_astroport_factory()?; + let contract_a = self.get_factory().src(chain).get_cw(); + let fee_denom = self.get_native_denom().src(chain).get(); // Create the pair let tx = contract_a.execute( @@ -395,7 +522,7 @@ impl TestContext { init_params: None, })? .as_str(), - "--gas 1000000", + &format!("--fees 42069420{fee_denom} --gas 1000000"), )?; // Get the address of the createed contract via logs @@ -403,7 +530,7 @@ impl TestContext { "transaction did not produce a tx hash", )))?; - self.guard_tx_errors(NEUTRON_CHAIN_NAME, tx_hash.as_str())?; + self.guard_tx_errors(chain, tx_hash.as_str())?; Ok(()) } @@ -412,6 +539,7 @@ impl TestContext { pub fn build_tx_fund_pool(&mut self) -> FundPoolTxBuilder { FundPoolTxBuilder { key: DEFAULT_KEY, + chain: NEUTRON_CHAIN_NAME, denom_a: Default::default(), denom_b: Default::default(), amt_denom_a: Default::default(), @@ -427,18 +555,22 @@ impl TestContext { fn tx_fund_pool( &mut self, key: &str, - denom_a: impl Into + AsRef, - denom_b: impl Into + AsRef, + chain: &str, + denom_a: String, + denom_b: String, amt_denom_a: u128, amt_denom_b: u128, slippage_tolerance: Decimal, liq_token_receiver: impl Into, ) -> Result<(), Error> { - // Get the instance from the address - let pool = self.get_astroport_pool(denom_a.as_ref(), denom_b.as_ref())?; + let fee_denom = self.get_native_denom().src(chain).get(); - let denom_a = denom_a.into(); - let denom_b = denom_b.into(); + // Get the instance from the address + let pool = self + .get_astro_pool() + .src(chain) + .denoms(denom_a.clone(), denom_b.clone()) + .get_cw(); // Provide liquidity let tx = pool @@ -465,12 +597,12 @@ impl TestContext { min_lp_to_receive: None, })? .as_str(), - &format!("--amount {amt_denom_a}{denom_a},{amt_denom_b}{denom_b} --gas 1000000"), + &format!("--amount {amt_denom_a}{denom_a},{amt_denom_b}{denom_b} --gas 1000000 --fees 42069420{fee_denom}"), )? .tx_hash .ok_or(Error::TxMissingLogs)?; - self.guard_tx_errors(NEUTRON_CHAIN_NAME, tx.as_str())?; + self.guard_tx_errors(chain, tx.as_str())?; Ok(()) } diff --git a/src/utils/setup/osmosis.rs b/src/utils/setup/osmosis.rs index 9871297..14462c1 100644 --- a/src/utils/setup/osmosis.rs +++ b/src/utils/setup/osmosis.rs @@ -1,12 +1,68 @@ use super::super::{ - super::{error::Error, DEFAULT_KEY, OSMOSIS_CHAIN_NAME, OSMOSIS_POOLFILE_PATH}, + super::{ + error::Error, + types::osmosis::{CosmWasmPoolType, PoolInitParams, PoolType}, + DEFAULT_KEY, OSMOSIS_CHAIN_ADMIN_ADDR, OSMOSIS_CHAIN_NAME, OSMOSIS_POOLFILE_PATH, + OSMOSIS_WHITELIST_PROP_PATH, PAIR_PCL_ON_OSMOSIS_NAME, VALIDATOR_KEY, + }, test_context::TestContext, }; -use cosmwasm_std::Decimal; -use std::{fs::OpenOptions, io::Write, path::Path}; +use astroport::{ + asset::AssetInfo, + factory::{self, PairType}, + pair_concentrated::ConcentratedPoolParams, +}; +use base64::{prelude::BASE64_STANDARD, Engine}; +use cosmwasm_std::{Binary, Decimal}; +use serde_json::Value; +use std::{ + fs::{self, OpenOptions}, + io::Write, + path::Path, +}; + +pub struct WhitelistCosmWasmPoolTxBuilder<'a> { + key: &'a str, + from: &'a str, + contract_path: Option<&'a str>, + test_ctx: &'a mut TestContext, +} + +impl<'a> WhitelistCosmWasmPoolTxBuilder<'a> { + pub fn with_key(&mut self, key: &'a str) -> &mut Self { + self.key = key; + + self + } + + pub fn with_proposer(&mut self, proposer: &'a str) -> &mut Self { + self.from = proposer; + + self + } + + pub fn with_contract_path(&mut self, contract_path: &'a str) -> &mut Self { + self.contract_path = Some(contract_path); + + self + } + + /// Sends the transaction, returning the pool ID if it was created successfully. + pub fn send(&mut self) -> Result<(), Error> { + self.test_ctx.tx_whitelist_cosmwasm_pool( + self.key, + self.from, + self.contract_path + .expect("missing whitelisted pool contract path"), + ) + } +} pub struct CreateOsmoPoolTxBuilder<'a> { key: &'a str, + flags: Option<&'a str>, + pool_type: PoolType, + pool_init_params: Option, weights: Vec<(u64, &'a str)>, initial_deposit: Vec<(u64, &'a str)>, swap_fee: Decimal, @@ -22,6 +78,24 @@ impl<'a> CreateOsmoPoolTxBuilder<'a> { self } + pub fn with_flags(&mut self, flags: &'a str) -> &mut Self { + self.flags = Some(flags); + + self + } + + pub fn with_pool_type(&mut self, pool_type: PoolType) -> &mut Self { + self.pool_type = pool_type; + + self + } + + pub fn with_pool_init_params(&mut self, init_params: PoolInitParams) -> &mut Self { + self.pool_init_params = Some(init_params); + + self + } + pub fn with_weight(&mut self, denom: &'a str, weight: u64) -> &mut Self { self.weights.push((weight, denom)); @@ -56,6 +130,9 @@ impl<'a> CreateOsmoPoolTxBuilder<'a> { pub fn send(&mut self) -> Result<(), Error> { self.test_ctx.tx_create_osmo_pool( self.key, + self.flags, + self.pool_type, + self.pool_init_params.clone(), self.weights.iter().cloned(), self.initial_deposit.iter().cloned(), self.swap_fee, @@ -112,9 +189,21 @@ impl<'a> FundOsmoPoolTxBuilder<'a> { } impl TestContext { + pub fn build_tx_whitelist_cosmwasm_pool(&mut self) -> WhitelistCosmWasmPoolTxBuilder { + WhitelistCosmWasmPoolTxBuilder { + key: VALIDATOR_KEY, + from: OSMOSIS_CHAIN_ADMIN_ADDR, + contract_path: Default::default(), + test_ctx: self, + } + } + pub fn build_tx_create_osmo_pool(&mut self) -> CreateOsmoPoolTxBuilder { CreateOsmoPoolTxBuilder { key: DEFAULT_KEY, + flags: Default::default(), + pool_type: PoolType::Xyk, + pool_init_params: Default::default(), weights: Default::default(), initial_deposit: Default::default(), swap_fee: Decimal::percent(0), @@ -126,6 +215,35 @@ impl TestContext { /// Creates an osmosis pool with the given denoms. fn tx_create_osmo_pool<'a>( + &mut self, + key: &str, + flags: Option<&str>, + pool_type: PoolType, + pool_params: Option, + weights: impl Iterator, + initial_deposit: impl Iterator, + swap_fee: Decimal, + exit_fee: Decimal, + future_governor: &'a str, + ) -> Result<(), Error> { + match pool_type { + PoolType::Xyk => self.tx_create_osmo_pool_xyk( + key, + weights, + initial_deposit, + swap_fee, + exit_fee, + future_governor, + ), + PoolType::CosmWasm(CosmWasmPoolType::Pcl) => match pool_params.unwrap() { + PoolInitParams::Pcl(params) => { + self.tx_create_osmo_pool_pcl(key, flags, weights, params) + } + }, + } + } + + fn tx_create_osmo_pool_xyk<'a>( &mut self, key: &str, weights: impl Iterator, @@ -182,6 +300,62 @@ impl TestContext { Ok(()) } + fn tx_create_osmo_pool_pcl<'a>( + &mut self, + key: &str, + flags: Option<&str>, + weights: impl Iterator, + init_params: ConcentratedPoolParams, + ) -> Result<(), Error> { + // Creating a PCL pool takes a few steps: + // - Instantiating the contract for the PCL pool + // - Registering the pool in x/cosmwasmpool + // - Registering THAT pool in x/poolmanager + + // Start by creating the PCL contract instance + // Select only denoms for weights (weights are not supplied at instantiation in astroport vs osmosis) + // Support only native tokens, not cw 20's + let asset_infos = weights + .map(|(_, denom)| denom.to_owned()) + .map(|denom| AssetInfo::NativeToken { denom }) + .collect::>(); + + let factory = self.get_factory().src(OSMOSIS_CHAIN_NAME).get_cw(); + + println!( + "{:?} {:?}", + serde_json::to_string(&init_params).unwrap(), + serde_json::to_string(&factory::ExecuteMsg::CreatePair { + pair_type: PairType::Custom(String::from("concentrated")), + asset_infos: asset_infos.clone(), + init_params: Some(Binary(serde_json::to_vec(&init_params).unwrap())), + }) + ); + + let receipt = factory + .execute( + key, + &serde_json::to_string(&factory::ExecuteMsg::CreatePair { + pair_type: PairType::Custom(String::from("concentrated")), + asset_infos, + init_params: Some(Binary(serde_json::to_vec(&init_params).unwrap())), + }) + .unwrap(), + &format!( + "--fees 42069420uosmo{} --gas 42069420 --amount 1000000000uosmo", + flags.map(|flags| format!(" {flags}")).unwrap_or_default() + ), + ) + .unwrap(); + + self.guard_tx_errors( + OSMOSIS_CHAIN_NAME, + receipt.tx_hash.ok_or(Error::TxMissingLogs)?.as_str(), + )?; + + Ok(()) + } + pub fn build_tx_fund_osmo_pool(&mut self) -> FundOsmoPoolTxBuilder { FundOsmoPoolTxBuilder { key: DEFAULT_KEY, @@ -219,4 +393,162 @@ impl TestContext { Ok(()) } + + fn tx_whitelist_cosmwasm_pool( + &mut self, + key: &str, + from: &str, + contract_path: &str, + ) -> Result<(), Error> { + // Whitelist the pool cosmwasm contract by + // writing the prop json to the osmosis node + // and submitting a UploadCosmWasmPoolCodeAndWhiteListProposal prop + // and voting to pass the prop + let osmosis = self.get_chain(OSMOSIS_CHAIN_NAME); + + let authority_resp = osmosis + .rb + .q("q auth module-account gov --output=json", true); + let authority_info: Value = + serde_json::from_str(authority_resp["text"].as_str().unwrap()).unwrap(); + let authority = authority_info["account"]["base_account"]["address"] + .as_str() + .unwrap(); + + // Read the bytecode of the contract + let bytecode = fs::read(contract_path).unwrap(); + + let prop = serde_json::to_vec(&serde_json::json!( + { + "messages": [ + { + "@type": "/cosmos.gov.v1.MsgExecLegacyContent", + "content": { + "@type": "/osmosis.cosmwasmpool.v1beta1.UploadCosmWasmPoolCodeAndWhiteListProposal", + "title": "Whitelisting Contract", + "description": "", + "wasmByteCode": BASE64_STANDARD.encode(bytecode), + }, + "authority": authority, + } + ], + "initial_deposit": [ { "denom": "uosmo", "amount": "100000" }], + "title": "Whitelisting Contract", + "summary": "Whitelisting Contract", + "proposer": from, + } + )).unwrap(); + + // Upload the proposal as JSON file + fs::write(OSMOSIS_WHITELIST_PROP_PATH, prop.as_slice()).unwrap(); + + osmosis + .rb + .upload_file(Path::new(OSMOSIS_WHITELIST_PROP_PATH), true)? + .send()? + .text()?; + + let chain_id = &osmosis.rb.chain_id; + + let remote_proposal_path = format!("/var/cosmos-chain/{chain_id}/prop.json"); + + // Bond voting tokens + let validator_address_resp = osmosis.rb.bin( + &format!("keys show {key} --bech val --output=json --keyring-backend test --home /var/cosmos-chain/{chain_id}/"), + true, + ); + let validator_address_obj: Value = + serde_json::from_str(&validator_address_resp["text"].as_str().unwrap()).unwrap(); + let validator_address = validator_address_obj["address"].as_str().unwrap(); + + let receipt = osmosis + .rb + .tx( + &format!("tx staking delegate {validator_address} 10000000000uosmo --from {key} --fees 42069420uosmo"), + true, + ) + .unwrap(); + + self.guard_tx_errors( + OSMOSIS_CHAIN_NAME, + receipt + .get("txhash") + .and_then(|receipt| receipt.as_str()) + .ok_or(Error::TxMissingLogs)?, + )?; + + // Submit the proposal + let _ = osmosis.rb.tx( + &format!( + "tx gov submit-proposal {remote_proposal_path} --from {key} --fees 42069420uosmo --gas 771023100" + ), + true, + )?; + + // Find the proposal's ID + let props = osmosis + .rb + .q(&format!("q gov proposals --output=json"), true); + + let proposals_object: Value = + serde_json::from_str(&props["text"].as_str().unwrap()).unwrap(); + + let mut proposal_ids = proposals_object["proposals"] + .as_array() + .unwrap() + .into_iter() + .map(|prop| prop.as_object().unwrap()["id"].as_str().unwrap()) + .map(|prop_id_str| prop_id_str.parse::().unwrap()) + .collect::>(); + proposal_ids.sort(); + + // Deposit to activate the proposal + let _ = osmosis.rb.tx( + &format!( + "tx gov deposit {} 10000000uosmo --from {key} --fees 42069420uosmo --gas 179841000", + proposal_ids[proposal_ids.len() - 1] + ), + true, + )?; + + // Vote on the proposal + let receipt = osmosis.rb.tx( + &format!( + "tx gov vote {} yes --from {key} --fees 42069420uosmo", + proposal_ids[proposal_ids.len() - 1] + ), + true, + )?; + + self.guard_tx_errors( + OSMOSIS_CHAIN_NAME, + receipt + .get("txhash") + .and_then(|receipt| receipt.as_str()) + .ok_or(Error::TxMissingLogs)?, + )?; + + // Get the whitelisted code + let codes_resp: Value = serde_json::from_str( + osmosis.rb.q("wasm list-code --output=json", true)["text"] + .as_str() + .unwrap(), + ) + .unwrap(); + let codes = codes_resp["code_infos"] + .as_array() + .unwrap() + .into_iter() + .map(|code_id_object| code_id_object["code_id"].as_str().unwrap()) + .collect::>(); + let code = codes[codes.len() - 1].parse::().unwrap(); + + let osmosis = self.get_mut_chain(OSMOSIS_CHAIN_NAME); + + osmosis + .contract_codes + .insert(PAIR_PCL_ON_OSMOSIS_NAME.to_owned(), code); + + Ok(()) + } } diff --git a/src/utils/setup/stride.rs b/src/utils/setup/stride.rs index b9f2d45..c107a00 100644 --- a/src/utils/setup/stride.rs +++ b/src/utils/setup/stride.rs @@ -58,7 +58,12 @@ impl TestContext { pub fn set_up_stride_host_zone(&mut self, dest_chain: &str) { let native_denom = self.get_native_denom().src(dest_chain).get().clone(); - let host_denom_on_stride = self.get_ibc_denom(&native_denom, dest_chain, STRIDE_CHAIN_NAME); + let host_denom_on_stride = self + .get_ibc_denom() + .base_denom(native_denom) + .dest(dest_chain) + .src(STRIDE_CHAIN_NAME) + .get(); let stride = self.get_chain(STRIDE_CHAIN_NAME); let stride_rb = &stride.rb; diff --git a/src/utils/setup/tokens.rs b/src/utils/setup/tokens.rs index 2375696..f381eaa 100644 --- a/src/utils/setup/tokens.rs +++ b/src/utils/setup/tokens.rs @@ -18,7 +18,7 @@ impl<'a> CreateTokenFactoryTokenTxBuilder<'a> { self } - pub fn with_chain_name(&mut self, chain_name: impl Into) -> &mut Self { + pub fn with_chain(&mut self, chain_name: impl Into) -> &mut Self { self.chain_name = Some(chain_name.into()); self @@ -61,7 +61,7 @@ impl<'a> MintTokenFactoryTokenTxBuilder<'a> { self } - pub fn with_chain_name(&mut self, chain_name: impl Into) -> &mut Self { + pub fn with_chain(&mut self, chain_name: impl Into) -> &mut Self { self.chain_name = Some(chain_name.into()); self diff --git a/src/utils/setup/valence.rs b/src/utils/setup/valence.rs index 2ab1915..e544183 100644 --- a/src/utils/setup/valence.rs +++ b/src/utils/setup/valence.rs @@ -1,12 +1,9 @@ use super::super::{ super::{ error::Error, - types::contract::{ - AuctionStrategy, ChainHaltConfig, DeployedContractInfo, MinAmount, - PriceFreshnessStrategy, - }, + types::contract::{AuctionStrategy, ChainHaltConfig, MinAmount, PriceFreshnessStrategy}, AUCTIONS_MANAGER_CONTRACT_NAME, AUCTION_CONTRACT_NAME, DEFAULT_AUCTION_LABEL, DEFAULT_KEY, - NEUTRON_CHAIN_ADMIN_ADDR, NEUTRON_CHAIN_NAME, PRICE_ORACLE_NAME, + NEUTRON_CHAIN_ADMIN_ADDR, NEUTRON_CHAIN_NAME, OSMOSIS_CHAIN_NAME, PRICE_ORACLE_NAME, }, test_context::TestContext, }; @@ -17,6 +14,7 @@ use serde_json::Value; /// A tx creating an auctions manager. pub struct CreateAuctionsManagerTxBuilder<'a> { key: &'a str, + chain: &'a str, min_auction_amount: &'a [(&'a str, MinAmount)], server_addr: &'a str, test_ctx: &'a mut TestContext, @@ -28,6 +26,13 @@ impl<'a> CreateAuctionsManagerTxBuilder<'a> { self } + + pub fn with_chain(&mut self, chain: &'a str) -> &mut Self { + self.chain = chain; + + self + } + pub fn with_min_auction_amount( &mut self, min_auction_amount: &'a [(&'a str, MinAmount)], @@ -47,6 +52,7 @@ impl<'a> CreateAuctionsManagerTxBuilder<'a> { pub fn send(&mut self) -> Result<(), Error> { self.test_ctx.tx_create_auctions_manager( self.key, + self.chain, self.min_auction_amount, self.server_addr, ) @@ -55,6 +61,7 @@ impl<'a> CreateAuctionsManagerTxBuilder<'a> { pub struct CreateAuctionTxBuilder<'a> { key: &'a str, + chain: &'a str, offer_asset: Option<&'a str>, ask_asset: Option<&'a str>, auction_strategy: AuctionStrategy, @@ -72,6 +79,12 @@ impl<'a> CreateAuctionTxBuilder<'a> { self } + pub fn with_chain(&mut self, chain: &'a str) -> &mut Self { + self.chain = chain; + + self + } + pub fn with_offer_asset(&mut self, asset: &'a str) -> &mut Self { self.offer_asset = Some(asset); @@ -121,6 +134,7 @@ impl<'a> CreateAuctionTxBuilder<'a> { pub fn send(&mut self) -> Result<(), Error> { self.test_ctx.tx_create_auction( self.key, + self.chain, ( self.offer_asset .ok_or(Error::MissingBuilderParam(String::from("pair")))?, @@ -141,6 +155,7 @@ impl<'a> CreateAuctionTxBuilder<'a> { pub struct FundAuctionTxBuilder<'a> { key: &'a str, + chain: &'a str, offer_asset: Option<&'a str>, ask_asset: Option<&'a str>, amt_offer_asset: Option, @@ -154,6 +169,12 @@ impl<'a> FundAuctionTxBuilder<'a> { self } + pub fn with_chain(&mut self, chain: &'a str) -> &mut Self { + self.chain = chain; + + self + } + pub fn with_offer_asset(&mut self, asset: &'a str) -> &mut Self { self.offer_asset = Some(asset); @@ -176,6 +197,7 @@ impl<'a> FundAuctionTxBuilder<'a> { pub fn send(&mut self) -> Result<(), Error> { self.test_ctx.tx_fund_auction( self.key, + self.chain, ( self.offer_asset .ok_or(Error::MissingBuilderParam(String::from("pair")))?, @@ -192,6 +214,7 @@ impl<'a> FundAuctionTxBuilder<'a> { pub struct StartAuctionTxBuilder<'a> { key: &'a str, + chain: &'a str, offer_asset: Option<&'a str>, ask_asset: Option<&'a str>, end_block_delta: Option, @@ -205,6 +228,12 @@ impl<'a> StartAuctionTxBuilder<'a> { self } + pub fn with_chain(&mut self, chain: &'a str) -> &mut Self { + self.chain = chain; + + self + } + pub fn with_offer_asset(&mut self, asset: &'a str) -> &mut Self { self.offer_asset = Some(asset); @@ -227,6 +256,7 @@ impl<'a> StartAuctionTxBuilder<'a> { pub fn send(&mut self) -> Result<(), Error> { self.test_ctx.tx_start_auction( self.key, + self.chain, self.end_block_delta .ok_or(Error::MissingBuilderParam(String::from("end_block_delta")))?, ( @@ -241,6 +271,7 @@ impl<'a> StartAuctionTxBuilder<'a> { pub struct MigrateAuctionTxBuilder<'a> { key: &'a str, + chain: &'a str, offer_asset: Option<&'a str>, ask_asset: Option<&'a str>, test_ctx: &'a mut TestContext, @@ -253,6 +284,12 @@ impl<'a> MigrateAuctionTxBuilder<'a> { self } + pub fn with_chain(&mut self, chain: &'a str) -> &mut Self { + self.chain = chain; + + self + } + pub fn with_offer_asset(&mut self, asset: &'a str) -> &mut Self { self.offer_asset = Some(asset); @@ -269,6 +306,7 @@ impl<'a> MigrateAuctionTxBuilder<'a> { pub fn send(&mut self) -> Result<(), Error> { self.test_ctx.tx_migrate_auction( self.key, + self.chain, ( self.offer_asset .ok_or(Error::MissingBuilderParam(String::from("pair")))?, @@ -281,6 +319,7 @@ impl<'a> MigrateAuctionTxBuilder<'a> { pub struct CreatePriceOracleTxBuilder<'a> { key: &'a str, + chain: &'a str, seconds_allow_manual_change: u64, seconds_auction_prices_fresh: u64, test_ctx: &'a mut TestContext, @@ -293,6 +332,12 @@ impl<'a> CreatePriceOracleTxBuilder<'a> { self } + pub fn with_chain(&mut self, chain: &'a str) -> &mut Self { + self.chain = chain; + + self + } + pub fn with_seconds_allow_manual_change(&mut self, sec: u64) -> &mut Self { self.seconds_allow_manual_change = sec; @@ -309,6 +354,7 @@ impl<'a> CreatePriceOracleTxBuilder<'a> { pub fn send(&mut self) -> Result<(), Error> { self.test_ctx.tx_create_price_oracle( self.key, + self.chain, self.seconds_allow_manual_change, self.seconds_auction_prices_fresh, ) @@ -317,6 +363,7 @@ impl<'a> CreatePriceOracleTxBuilder<'a> { pub struct UpdateAuctionOracleTxBuilder<'a> { key: &'a str, + chain: &'a str, test_ctx: &'a mut TestContext, } @@ -327,14 +374,21 @@ impl<'a> UpdateAuctionOracleTxBuilder<'a> { self } + pub fn with_chain(&mut self, chain: &'a str) -> &mut Self { + self.chain = chain; + + self + } + /// Sends the transaction. pub fn send(&mut self) -> Result<(), Error> { - self.test_ctx.tx_update_auction_oracle(self.key) + self.test_ctx.tx_update_auction_oracle(self.key, self.chain) } } pub struct ManualOraclePriceUpdateTxBuilder<'a> { key: &'a str, + chain: &'a str, offer_asset: Option<&'a str>, ask_asset: Option<&'a str>, price: Option, @@ -348,6 +402,12 @@ impl<'a> ManualOraclePriceUpdateTxBuilder<'a> { self } + pub fn with_chain(&mut self, chain: &'a str) -> &mut Self { + self.chain = chain; + + self + } + pub fn with_offer_asset(&mut self, asset: &'a str) -> &mut Self { self.offer_asset = Some(asset); @@ -370,6 +430,7 @@ impl<'a> ManualOraclePriceUpdateTxBuilder<'a> { pub fn send(&mut self) -> Result<(), Error> { self.test_ctx.tx_manual_oracle_price_update( self.key, + self.chain, self.offer_asset .ok_or(Error::MissingBuilderParam(String::from("offer_asset")))?, self.ask_asset @@ -384,6 +445,7 @@ impl TestContext { pub fn build_tx_create_auctions_manager(&mut self) -> CreateAuctionsManagerTxBuilder { CreateAuctionsManagerTxBuilder { key: DEFAULT_KEY, + chain: NEUTRON_CHAIN_NAME, min_auction_amount: &[], server_addr: NEUTRON_CHAIN_ADMIN_ADDR, test_ctx: self, @@ -395,19 +457,23 @@ impl TestContext { fn tx_create_auctions_manager<'a>( &mut self, sender_key: &str, + chain: &str, min_auction_amount: impl AsRef<[(&'a str, MinAmount)]>, server_addr: impl AsRef, ) -> Result<(), Error> { - let mut contract_a: CosmWasm = self.get_contract(AUCTIONS_MANAGER_CONTRACT_NAME)?; - let neutron = self.get_chain(NEUTRON_CHAIN_NAME); - - let auction_code_id = - neutron - .contract_codes - .get(AUCTION_CONTRACT_NAME) - .ok_or(Error::Misc(format!( - "contract '{AUCTION_CONTRACT_NAME}' is missing" - )))?; + let mut contract_a: CosmWasm = self + .get_contract() + .contract(AUCTIONS_MANAGER_CONTRACT_NAME) + .src(chain) + .get_cw(); + let local_chain = self.get_chain(chain); + + let auction_code_id = local_chain + .contract_codes + .get(AUCTION_CONTRACT_NAME) + .ok_or(Error::Misc(format!( + "contract '{AUCTION_CONTRACT_NAME}' is missing" + )))?; let contract = contract_a.instantiate( sender_key, @@ -420,20 +486,14 @@ impl TestContext { .as_str(), AUCTIONS_MANAGER_CONTRACT_NAME, None, - "", + if chain == OSMOSIS_CHAIN_NAME { + "--fees 42069420uosmo" + } else { + "" + }, )?; - self.auctions_manager = Some(DeployedContractInfo { - code_id: contract_a.code_id.ok_or(Error::Misc(format!( - "contract '{AUCTIONS_MANAGER_CONTRACT_NAME}' has no code ID" - )))?, - address: contract.address.clone(), - artifact_path: contract_a.file_path.ok_or(Error::Misc(format!( - "contract '{AUCTIONS_MANAGER_CONTRACT_NAME}' has no file path" - )))?, - }); - - let chain = self.get_mut_chain(NEUTRON_CHAIN_NAME); + let chain = self.get_mut_chain(chain); chain .contract_addrs @@ -445,6 +505,7 @@ impl TestContext { pub fn build_tx_create_price_oracle(&mut self) -> CreatePriceOracleTxBuilder { CreatePriceOracleTxBuilder { key: DEFAULT_KEY, + chain: NEUTRON_CHAIN_NAME, seconds_allow_manual_change: 0, seconds_auction_prices_fresh: 100000000000, test_ctx: self, @@ -456,10 +517,11 @@ impl TestContext { fn tx_create_price_oracle( &mut self, sender_key: &str, + chain: &str, seconds_allow_manual_change: u64, seconds_auction_prices_fresh: u64, ) -> Result<(), Error> { - let auctions_manager: CosmWasm = self.get_auctions_manager()?; + let auctions_manager: CosmWasm = self.get_auctions_manager().src(chain).get_cw(); let auctions_manager_addr = auctions_manager .contract_addr @@ -467,7 +529,11 @@ impl TestContext { "contract_addresses::auctions_manager", )))?; - let mut contract_a = self.get_contract(PRICE_ORACLE_NAME)?; + let mut contract_a = self + .get_contract() + .contract(PRICE_ORACLE_NAME) + .src(chain) + .get_cw(); let contract = contract_a.instantiate( sender_key, serde_json::json!({ @@ -479,10 +545,14 @@ impl TestContext { .as_str(), PRICE_ORACLE_NAME, None, - "", + if chain == OSMOSIS_CHAIN_NAME { + "--fees 42069420uosmo" + } else { + "" + }, )?; - let chain = self.get_mut_chain(NEUTRON_CHAIN_NAME); + let chain = self.get_mut_chain(chain); chain .contract_addrs @@ -495,6 +565,7 @@ impl TestContext { pub fn build_tx_create_auction(&mut self) -> CreateAuctionTxBuilder { CreateAuctionTxBuilder { key: DEFAULT_KEY, + chain: NEUTRON_CHAIN_NAME, offer_asset: Default::default(), ask_asset: Default::default(), auction_strategy: AuctionStrategy { @@ -520,6 +591,7 @@ impl TestContext { fn tx_create_auction, TDenomB: AsRef>( &mut self, sender_key: &str, + chain: &str, pair: (TDenomA, TDenomB), auction_strategy: AuctionStrategy, chain_halt_config: ChainHaltConfig, @@ -528,8 +600,9 @@ impl TestContext { amount_denom_a: u128, ) -> Result<(), Error> { // The auctions manager for this deployment - let contract_a = self.get_auctions_manager()?; + let contract_a = self.get_auctions_manager().src(chain).get_cw(); let denom_a = pair.0.as_ref(); + let fee_denom = self.get_native_denom().src(chain).get(); let receipt = contract_a.execute( sender_key, @@ -548,7 +621,8 @@ impl TestContext { }}) .to_string() .as_str(), - format!("--amount {amount_denom_a}{denom_a} --gas 2000000").as_str(), + format!("--amount {amount_denom_a}{denom_a} --fees 42069420{fee_denom} --gas 1000000") + .as_str(), )?; log::debug!( @@ -558,10 +632,7 @@ impl TestContext { receipt ); - self.guard_tx_errors( - NEUTRON_CHAIN_NAME, - receipt.tx_hash.ok_or(Error::TxMissingLogs)?.as_str(), - )?; + self.guard_tx_errors(chain, receipt.tx_hash.ok_or(Error::TxMissingLogs)?.as_str())?; Ok(()) } @@ -570,6 +641,7 @@ impl TestContext { pub fn build_tx_migrate_auction(&mut self) -> MigrateAuctionTxBuilder { MigrateAuctionTxBuilder { key: DEFAULT_KEY, + chain: NEUTRON_CHAIN_NAME, offer_asset: Default::default(), ask_asset: Default::default(), test_ctx: self, @@ -580,13 +652,21 @@ impl TestContext { fn tx_migrate_auction, TDenomB: AsRef>( &mut self, sender_key: &str, + chain: &str, pair: (TDenomA, TDenomB), ) -> Result<(), Error> { // The auctions manager for this deployment - let contract_a = self.get_auctions_manager()?; - let code_id = self.get_contract(AUCTION_CONTRACT_NAME)?.code_id.ok_or( - Error::MissingContextVariable(String::from("code_ids::auction")), - )?; + let contract_a = self.get_auctions_manager().src(chain).get_cw(); + let code_id = self + .get_contract() + .contract(AUCTION_CONTRACT_NAME) + .src(chain) + .get_cw() + .code_id + .ok_or(Error::MissingContextVariable(String::from( + "code_ids::auction", + )))?; + let fee_denom = self.get_native_denom().src(chain).get(); let receipt = contract_a.execute( sender_key, @@ -603,7 +683,7 @@ impl TestContext { }}) .to_string() .as_str(), - "--gas 2000000", + &format!("--fees 42069420{fee_denom}"), )?; log::debug!( @@ -613,10 +693,7 @@ impl TestContext { receipt ); - self.guard_tx_errors( - NEUTRON_CHAIN_NAME, - receipt.tx_hash.ok_or(Error::TxMissingLogs)?.as_str(), - )?; + self.guard_tx_errors(chain, receipt.tx_hash.ok_or(Error::TxMissingLogs)?.as_str())?; Ok(()) } @@ -625,21 +702,19 @@ impl TestContext { pub fn build_tx_update_auction_oracle(&mut self) -> UpdateAuctionOracleTxBuilder { UpdateAuctionOracleTxBuilder { key: DEFAULT_KEY, + chain: NEUTRON_CHAIN_NAME, test_ctx: self, } } - fn tx_update_auction_oracle(&mut self, sender_key: &str) -> Result<(), Error> { + fn tx_update_auction_oracle(&mut self, sender_key: &str, chain: &str) -> Result<(), Error> { // The auctions manager for this deployment - let contract_a = self.get_auctions_manager()?; - let neutron = self.get_chain(NEUTRON_CHAIN_NAME); - let oracle = - neutron - .contract_addrs - .get(PRICE_ORACLE_NAME) - .ok_or(Error::MissingContextVariable(String::from( - "contract_addrs::price_oracle", - )))?; + let contract_a = self.get_auctions_manager().src(chain).get_cw(); + let local_chain = self.get_chain(chain); + let oracle = local_chain.contract_addrs.get(PRICE_ORACLE_NAME).ok_or( + Error::MissingContextVariable(String::from("contract_addrs::price_oracle")), + )?; + let fee_denom = self.get_native_denom().src(chain).get(); let receipt = contract_a.execute( sender_key, @@ -652,13 +727,10 @@ impl TestContext { }}) .to_string() .as_str(), - "--gas 2000000", + &format!("--fees 42069420{fee_denom}"), )?; - self.guard_tx_errors( - NEUTRON_CHAIN_NAME, - receipt.tx_hash.ok_or(Error::TxMissingLogs)?.as_str(), - )?; + self.guard_tx_errors(chain, receipt.tx_hash.ok_or(Error::TxMissingLogs)?.as_str())?; Ok(()) } @@ -667,6 +739,7 @@ impl TestContext { pub fn build_tx_manual_oracle_price_update(&mut self) -> ManualOraclePriceUpdateTxBuilder { ManualOraclePriceUpdateTxBuilder { key: DEFAULT_KEY, + chain: NEUTRON_CHAIN_NAME, offer_asset: Default::default(), ask_asset: Default::default(), price: Default::default(), @@ -677,12 +750,14 @@ impl TestContext { fn tx_manual_oracle_price_update( &mut self, sender_key: &str, + chain: &str, offer_asset: &str, ask_asset: &str, price: Decimal, ) -> Result<(), Error> { // The auctions manager for this deployment - let oracle = self.get_price_oracle()?; + let oracle = self.get_price_oracle().src(chain).get_cw(); + let fee_denom = self.get_native_denom().src(chain).get(); let receipt = oracle.execute( sender_key, @@ -695,13 +770,10 @@ impl TestContext { }) .to_string() .as_str(), - "--gas 2000000", + &format!("--fees 42069420{fee_denom}"), )?; - self.guard_tx_errors( - NEUTRON_CHAIN_NAME, - receipt.tx_hash.ok_or(Error::TxMissingLogs)?.as_str(), - )?; + self.guard_tx_errors(chain, receipt.tx_hash.ok_or(Error::TxMissingLogs)?.as_str())?; Ok(()) } @@ -710,6 +782,7 @@ impl TestContext { pub fn build_tx_fund_auction(&mut self) -> FundAuctionTxBuilder { FundAuctionTxBuilder { key: DEFAULT_KEY, + chain: NEUTRON_CHAIN_NAME, offer_asset: Default::default(), ask_asset: Default::default(), amt_offer_asset: Default::default(), @@ -721,10 +794,12 @@ impl TestContext { fn tx_fund_auction, TDenomB: AsRef>( &mut self, sender_key: &str, + chain: &str, pair: (TDenomA, TDenomB), amt_offer_asset: u128, ) -> Result<(), Error> { - let manager = self.get_auctions_manager()?; + let manager = self.get_auctions_manager().src(chain).get_cw(); + let fee_denom = self.get_native_denom().src(chain).get(); let denom_a = pair.0.as_ref(); @@ -737,13 +812,11 @@ impl TestContext { }) .to_string() .as_str(), - format!("--amount {amt_offer_asset}{denom_a} --gas 1000000").as_str(), + format!("--amount {amt_offer_asset}{denom_a} --gas 1000000 --fees 42069420{fee_denom}") + .as_str(), )?; - self.guard_tx_errors( - NEUTRON_CHAIN_NAME, - receipt.tx_hash.ok_or(Error::TxMissingLogs)?.as_str(), - )?; + self.guard_tx_errors(chain, receipt.tx_hash.ok_or(Error::TxMissingLogs)?.as_str())?; Ok(()) } @@ -752,6 +825,7 @@ impl TestContext { pub fn build_tx_start_auction(&mut self) -> StartAuctionTxBuilder { StartAuctionTxBuilder { key: DEFAULT_KEY, + chain: NEUTRON_CHAIN_NAME, offer_asset: Default::default(), ask_asset: Default::default(), end_block_delta: Default::default(), @@ -763,13 +837,15 @@ impl TestContext { fn tx_start_auction, TDenomB: AsRef>( &mut self, sender_key: &str, + chain: &str, end_blocks: u128, pair: (TDenomA, TDenomB), ) -> Result<(), Error> { - let manager = self.get_auctions_manager()?; - let neutron = self.get_chain(NEUTRON_CHAIN_NAME); + let manager = self.get_auctions_manager().src(chain).get_cw(); + let fee_denom = self.get_native_denom().src(chain).get(); + let local_chain = self.get_chain(chain); - let start_block_resp = neutron + let start_block_resp = local_chain .rb .bin("q block --node=%RPC% --chain-id=%CHAIN_ID%", true); let maybe_start_block_data: Value = start_block_resp @@ -802,13 +878,10 @@ impl TestContext { }) .to_string() .as_str(), - "--gas 1000000", + &format!("--fees 42069420{fee_denom}"), )?; - self.guard_tx_errors( - NEUTRON_CHAIN_NAME, - receipt.tx_hash.ok_or(Error::TxMissingLogs)?.as_str(), - )?; + self.guard_tx_errors(chain, receipt.tx_hash.ok_or(Error::TxMissingLogs)?.as_str())?; Ok(()) } diff --git a/src/utils/test_context.rs b/src/utils/test_context.rs index 2cf7884..52cbbc1 100644 --- a/src/utils/test_context.rs +++ b/src/utils/test_context.rs @@ -2,10 +2,9 @@ use super::super::{ error::Error, types::{ config::{ConfigChain, Logs}, - contract::DeployedContractInfo, ibc::Channel as QueryChannel, }, - ICTEST_HOME_VAR, LOCAL_IC_API_URL, NEUTRON_CHAIN_NAME, TRANSFER_PORT, + ICTEST_HOME_VAR, LOCAL_IC_API_URL, TRANSFER_PORT, }; use localic_std::{ @@ -14,7 +13,6 @@ use localic_std::{ relayer::{Channel, Relayer}, transactions::ChainRequestBuilder, }; -use serde_json::Value; use std::{collections::HashMap, env, fs::OpenOptions, path::PathBuf}; /// A configurable builder that can be used to create a TestContext. @@ -353,9 +351,6 @@ impl TestContextBuilder { artifacts_dir: artifacts_dir .clone() .ok_or(Error::MissingBuilderParam(String::from("artifacts_dir")))?, - auctions_manager: None, - astroport_token_registry: None, - astroport_factory: None, unwrap_logs: *unwrap_raw_logs, log_file, }) @@ -376,13 +371,6 @@ pub struct TestContext { /// The path to .wasm contract artifacts pub artifacts_dir: String, - /// Valence deployment info - pub auctions_manager: Option, - - /// Astroport deployment info - pub astroport_token_registry: Option, - pub astroport_factory: Option, - /// Whether or not logs should be expected and guarded for each tx pub unwrap_logs: bool, @@ -449,276 +437,6 @@ impl LocalChain { } } -impl TestContext { - pub fn get_transfer_channels(&self) -> TestContextQuery { - TestContextQuery::new(self, QueryType::TransferChannel) - } - - pub fn get_connections(&self) -> TestContextQuery { - TestContextQuery::new(self, QueryType::Connection) - } - - pub fn get_ccv_channels(&self) -> TestContextQuery { - TestContextQuery::new(self, QueryType::CCVChannel) - } - - pub fn get_ibc_denoms(&self) -> TestContextQuery { - TestContextQuery::new(self, QueryType::IBCDenom) - } - - pub fn get_admin_addr(&self) -> TestContextQuery { - TestContextQuery::new(self, QueryType::AdminAddr) - } - - pub fn get_native_denom(&self) -> TestContextQuery { - TestContextQuery::new(self, QueryType::NativeDenom) - } - - pub fn get_chain_prefix(&self) -> TestContextQuery { - TestContextQuery::new(self, QueryType::ChainPrefix) - } - - pub fn get_request_builder(&self) -> TestContextQuery { - TestContextQuery::new(self, QueryType::RequestBuilder) - } - - pub fn get_built_contract_address(&self) -> TestContextQuery { - TestContextQuery::new(self, QueryType::BuiltContractAddress) - } - - pub fn get_chain(&self, chain_name: &str) -> &LocalChain { - self.chains.get(chain_name).unwrap() - } - - pub fn get_mut_chain(&mut self, chain_name: &str) -> &mut LocalChain { - self.chains.get_mut(chain_name).unwrap() - } -} - -pub enum QueryType { - TransferChannel, - Connection, - CCVChannel, - IBCDenom, - AdminAddr, - NativeDenom, - ChainPrefix, - RequestBuilder, - BuiltContractAddress, - CodeInfo, -} - -pub struct TestContextQuery<'a> { - context: &'a TestContext, - query_type: QueryType, - src_chain: Option, - dest_chain: Option, - contract_name: Option, - - // build-contract-address query args - creator_address: Option, - salt_hex_encoded: Option, -} - -impl<'a> TestContextQuery<'a> { - pub fn new(context: &'a TestContext, query_type: QueryType) -> Self { - Self { - context, - query_type, - src_chain: None, - dest_chain: None, - contract_name: None, - creator_address: None, - salt_hex_encoded: None, - } - } - - pub fn src(mut self, src_chain: &str) -> Self { - self.src_chain = Some(src_chain.to_string()); - self - } - - pub fn dest(mut self, dest_chain: &str) -> Self { - self.dest_chain = Some(dest_chain.to_string()); - self - } - - pub fn contract(mut self, contract_name: &str) -> Self { - self.contract_name = Some(contract_name.to_string()); - self - } - - pub fn creator(mut self, creator_addr: &str) -> Self { - self.creator_address = Some(creator_addr.to_owned()); - self - } - - pub fn salt_hex_encoded(mut self, salt_hex_encoded: &str) -> Self { - self.salt_hex_encoded = Some(salt_hex_encoded.to_owned()); - self - } - - pub fn get(self) -> String { - match self.query_type { - QueryType::TransferChannel => self.get_transfer_channel().map(ToOwned::to_owned), - QueryType::Connection => self.get_connection_id().map(ToOwned::to_owned), - QueryType::CCVChannel => self.get_ccv_channel().map(ToOwned::to_owned), - QueryType::IBCDenom => self.get_ibc_denom().map(ToOwned::to_owned), - QueryType::AdminAddr => self.get_admin_addr().map(ToOwned::to_owned), - QueryType::NativeDenom => self.get_native_denom().map(ToOwned::to_owned), - QueryType::ChainPrefix => self.get_chain_prefix().map(ToOwned::to_owned), - QueryType::BuiltContractAddress => self.get_built_contract_address(), - QueryType::CodeInfo => self.get_code_info().map(|s| s.to_string()), - _ => None, - } - .unwrap() - } - - pub fn get_all(self) -> Vec { - match self.query_type { - QueryType::TransferChannel => self.get_all_transfer_channels(), - QueryType::Connection => self.get_all_connections(), - _ => vec![], - } - .into_iter() - .map(ToOwned::to_owned) - .collect::>() - } - - pub fn get_request_builder(mut self, chain: &str) -> &'a ChainRequestBuilder { - self.src_chain = Some(chain.to_string()); - let rb = match self.query_type { - QueryType::RequestBuilder => self.get_rb(), - _ => None, - }; - rb.unwrap() - } - - fn get_transfer_channel(&self) -> Option<&str> { - self.context - .transfer_channel_ids - .get(&(self.src_chain.clone()?, self.dest_chain.clone()?)) - .map(|s| s.as_str()) - } - - fn get_all_transfer_channels(&self) -> Vec<&str> { - self.src_chain - .as_ref() - .map(|src| { - self.context - .transfer_channel_ids - .iter() - .filter(|((s, _), _)| s == src) - .map(|(_, v)| v.as_str()) - .collect::>() - }) - .unwrap_or_default() - } - - fn get_connection_id(&self) -> Option<&str> { - self.context - .connection_ids - .get(&(self.src_chain.clone()?, self.dest_chain.clone()?)) - .map(|s| s.as_str()) - } - - fn get_all_connections(&self) -> Vec<&str> { - self.src_chain - .as_ref() - .map(|src| { - self.context - .connection_ids - .iter() - .filter(|((s, _), _)| s == src) - .map(|(_, s)| s.as_str()) - .collect::>() - }) - .unwrap_or_default() - } - - fn get_ccv_channel(&self) -> Option<&str> { - self.context - .ccv_channel_ids - .get(&(self.src_chain.clone()?, self.dest_chain.clone()?)) - .map(|s| s.as_str()) - } - - fn get_ibc_denom(&self) -> Option<&str> { - self.context - .ibc_denoms - .get(&(self.src_chain.clone()?, self.dest_chain.clone()?)) - .map(|s| s.as_str()) - } - - fn get_admin_addr(&self) -> Option<&str> { - let src = self.src_chain.as_deref()?; - - Some(self.context.chains.get(src)?.admin_addr.as_ref()) - } - - fn get_native_denom(&self) -> Option<&str> { - let src = self.src_chain.as_deref()?; - - Some(self.context.chains.get(src)?.native_denom.as_ref()) - } - - fn get_chain_prefix(&self) -> Option<&str> { - let src = self.src_chain.as_deref()?; - - Some(self.context.chains.get(src)?.chain_prefix.as_ref()) - } - - fn get_code_info(&self) -> Option { - let contract = self - .context - .get_contract(self.contract_name.as_ref()?) - .ok()?; - let code_id = contract.code_id?; - let chain = self - .context - .chains - .get(self.src_chain.as_deref().unwrap_or(NEUTRON_CHAIN_NAME))?; - - // This will produce a { ... text: "{ 'data_hash': xyz }" }. Get the code info enclosed - let resp = chain.rb.query(&format!("q wasm code-info {code_id}"), true); - - let str_info_object = resp["text"].as_str()?; - serde_json::from_str(str_info_object).ok() - } - - fn get_built_contract_address(&self) -> Option { - let code_info = self.get_code_info()?; - let code_id_hash = code_info["data_hash"].as_str()?; - - let creator_address = self.creator_address.as_ref()?; - let salt = self.salt_hex_encoded.as_deref()?; - - let chain = self - .context - .chains - .get(self.src_chain.as_deref().unwrap_or(NEUTRON_CHAIN_NAME))?; - - let mut resp = chain.rb.bin( - &format!("q wasm build-address {code_id_hash} {creator_address} {salt}"), - true, - ); - - // text field contains built address - match resp["text"].take() { - Value::String(s) => Some(s.replace("\n", "")), - _ => None, - } - } - - fn get_rb(&self) -> Option<&'a ChainRequestBuilder> { - if let Some(ref src) = self.src_chain { - self.context.chains.get(src).map(|chain| &chain.rb) - } else { - None - } - } -} - pub fn find_pairwise_transfer_channel_ids( rb: &ChainRequestBuilder, src_chain_id: &str,