diff --git a/.github/workflows/cache.yml b/.github/workflows/cache.yml deleted file mode 100644 index 80def8fb..00000000 --- a/.github/workflows/cache.yml +++ /dev/null @@ -1,43 +0,0 @@ ---- -# yamllint disable rule:line-length -name: Clear cache - -on: - schedule: - - cron: '30 1 * * 1-5' - workflow_dispatch: - -jobs: - job1: - name: Clear Docker - if: always() - runs-on: ${{ matrix.runner_label }} - strategy: - matrix: - runner_label: ${{ fromJSON('["lionco-runner-1", "lionco-runner-2", "lionco-runner-3", "lionco-runner-4", "lionco-runner-5", "lionco-runner-6", "lionco-runner-7", "lionco-runner-8", "lionco-runner-9", "lionco-runner-10"]') }} - steps: - - name: Stop old containers - run: docker ps -q | grep -q . && docker stop $(docker ps -q) -t0 || echo "No containers to stop" - - name: Remove old containers - run: docker ps -a -q | grep -q . && docker rm $(docker ps -a -q) || echo "No containers to remove" - - name: Remove all volumes - run: docker volume ls -q | grep -q . && docker volume rm $(docker volume ls -q) || echo "No volumes to remove" - - name: Delete old images - run: docker system prune --volumes --all --force - job2: - name: Clear workspaces - if: always() - needs: job1 - runs-on: ${{ matrix.runner_label }} - strategy: - matrix: - runner_label: ${{ fromJSON('["lionco-runner-1", "lionco-runner-2", "lionco-runner-3", "lionco-runner-4", "lionco-runner-5", "lionco-runner-6", "lionco-runner-7", "lionco-runner-8", "lionco-runner-9", "lionco-runner-10"]') }} - steps: - - name: Clean Workspace - if: always() - uses: AutoModality/action-clean@v1.1.0 - - uses: xembly/workflow-manager@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - run: clean - verbose: true diff --git a/.github/workflows/cleanup.yml b/.github/workflows/cleanup.yml index fa2ba7cb..11ca9fda 100644 --- a/.github/workflows/cleanup.yml +++ b/.github/workflows/cleanup.yml @@ -42,7 +42,7 @@ on: jobs: del_runs: - runs-on: self-hosted + runs-on: ubicloud-standard-2 steps: - name: Delete workflow runs uses: Mattraks/delete-workflow-runs@v2 diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 6d9db1f1..c2a53d6e 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -13,7 +13,7 @@ jobs: # This job only runs for pull request comments name: PR comment if: ${{ github.event.issue.pull_request }} - runs-on: ubuntu-latest + runs-on: ubicloud-standard-2 steps: - name: Send Notification uses: appleboy/telegram-action@master @@ -26,7 +26,7 @@ jobs: pull_requests_and_review: name: Pull request action or review if: ${{ !github.event.issue.pull_request }} - runs-on: ubuntu-latest + runs-on: ubicloud-standard-2 steps: - name: Send Notification uses: appleboy/telegram-action@master diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 40229036..c4ce3e0c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -6,7 +6,7 @@ name: tests jobs: clippy: name: Actions - clippy - runs-on: self-hosted + runs-on: ubicloud-standard-4 steps: - uses: actions/checkout@v4 with: @@ -23,7 +23,7 @@ jobs: - run: cargo clippy --lib --target wasm32-unknown-unknown -- -D warnings coverage: name: Actions - unit tests coverage - runs-on: self-hosted + runs-on: ubicloud-standard-4 steps: - uses: actions/checkout@v4 with: @@ -59,7 +59,7 @@ jobs: coverage-summary-title: Code Coverage Summary rustfmt: name: Actions - rustfmt - runs-on: self-hosted + runs-on: ubicloud-standard-4 steps: - uses: actions/checkout@v4 with: @@ -73,7 +73,7 @@ jobs: - run: cargo fmt -- --check unit-test: name: Actions - unit test - runs-on: self-hosted + runs-on: ubicloud-standard-4 steps: - uses: actions/checkout@v4 with: @@ -89,7 +89,7 @@ jobs: RUST_BACKTRACE: 1 lint-test: name: Actions - integration tests lint - runs-on: self-hosted + runs-on: ubicloud-standard-4 steps: - name: Setup node uses: actions/setup-node@v4 @@ -104,7 +104,7 @@ jobs: run: cd integration_tests && yarn --ignore-engines && yarn lint images-prepare: name: Actions - images prepare - runs-on: self-hosted + runs-on: ubicloud-standard-4 steps: - name: Upgrade docker compose to use v2 run: sudo curl -L @@ -136,7 +136,7 @@ jobs: yarn build-images artifacts-prepare: name: Actions - artifacts prepare - runs-on: self-hosted + runs-on: ubicloud-standard-4 steps: - uses: actions/checkout@v4 with: @@ -157,7 +157,7 @@ jobs: needs: - images-prepare - artifacts-prepare - runs-on: self-hosted + runs-on: ubicloud-standard-4 steps: - name: Upgrade docker compose to use v2 run: sudo curl -L @@ -202,7 +202,7 @@ jobs: needs: - images-prepare - artifacts-prepare - runs-on: self-hosted + runs-on: ubicloud-standard-4 steps: - name: Upgrade docker compose to use v2 run: sudo curl -L @@ -247,7 +247,7 @@ jobs: needs: - images-prepare - artifacts-prepare - runs-on: self-hosted + runs-on: ubicloud-standard-4 steps: - name: Upgrade docker compose to use v2 run: sudo curl -L @@ -292,7 +292,7 @@ jobs: needs: - images-prepare - artifacts-prepare - runs-on: self-hosted + runs-on: ubicloud-standard-4 steps: - name: Upgrade docker compose to use v2 run: sudo curl -L @@ -337,7 +337,7 @@ jobs: needs: - images-prepare - artifacts-prepare - runs-on: self-hosted + runs-on: ubicloud-standard-4 steps: - name: Upgrade docker compose to use v2 run: sudo curl -L @@ -382,7 +382,7 @@ jobs: needs: - images-prepare - artifacts-prepare - runs-on: self-hosted + runs-on: ubicloud-standard-4 steps: - name: Upgrade docker compose to use v2 run: sudo curl -L @@ -427,7 +427,7 @@ jobs: needs: - images-prepare - artifacts-prepare - runs-on: self-hosted + runs-on: ubicloud-standard-4 steps: - name: Upgrade docker compose to use v2 run: sudo curl -L @@ -472,7 +472,7 @@ jobs: needs: - images-prepare - artifacts-prepare - runs-on: self-hosted + runs-on: ubicloud-standard-4 steps: - name: Upgrade docker compose to use v2 run: sudo curl -L @@ -517,7 +517,7 @@ jobs: needs: - images-prepare - artifacts-prepare - runs-on: self-hosted + runs-on: ubicloud-standard-4 steps: - name: Upgrade docker compose to use v2 run: sudo curl -L @@ -562,7 +562,7 @@ jobs: needs: - images-prepare - artifacts-prepare - runs-on: self-hosted + runs-on: ubicloud-standard-4 steps: - name: Upgrade docker compose to use v2 run: sudo curl -L @@ -607,7 +607,7 @@ jobs: needs: - images-prepare - artifacts-prepare - runs-on: self-hosted + runs-on: ubicloud-standard-4 steps: - name: Upgrade docker compose to use v2 run: sudo curl -L @@ -652,7 +652,7 @@ jobs: needs: - images-prepare - artifacts-prepare - runs-on: self-hosted + runs-on: ubicloud-standard-4 steps: - name: Upgrade docker compose to use v2 run: sudo curl -L diff --git a/Cargo.lock b/Cargo.lock index 5f728366..16620070 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -730,6 +730,26 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "drop-nft-querier" +version = "1.0.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-ownable", + "cw-storage-plus 1.2.0", + "cw2 1.1.2", + "cw721 0.18.0", + "cw721-base 0.18.0", + "drop-helpers", + "drop-macros", + "drop-puppeteer-base", + "drop-staking-base", + "neutron-sdk", + "semver", + "thiserror", +] + [[package]] name = "drop-price-provider" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index 6c577520..ff3ad6f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ members = [ "contracts/price-provider", "contracts/validators-stats", "contracts/validators-set", + "contracts/nft-querier", "packages/base", "packages/helpers", "packages/macros", diff --git a/contracts/factory/src/bin/drop-factory-schema.rs b/contracts/factory/src/bin/drop-factory-schema.rs index 5e026829..ed4e59a0 100644 --- a/contracts/factory/src/bin/drop-factory-schema.rs +++ b/contracts/factory/src/bin/drop-factory-schema.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::write_api; -use drop_factory::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; +use drop_staking_base::msg::factory::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; fn main() { write_api! { diff --git a/contracts/factory/src/contract.rs b/contracts/factory/src/contract.rs index 3ef82a36..440f9f0e 100644 --- a/contracts/factory/src/contract.rs +++ b/contracts/factory/src/contract.rs @@ -1,11 +1,3 @@ -use crate::{ - error::ContractResult, - msg::{ - ExecuteMsg, InstantiateMsg, MigrateMsg, ProxyMsg, QueryMsg, UpdateConfigMsg, - ValidatorSetMsg, - }, - state::{State, STATE}, -}; use cosmwasm_std::{ attr, instantiate2_address, to_json_binary, Attribute, Binary, CodeInfoResponse, CosmosMsg, Deps, DepsMut, Env, HexBinary, MessageInfo, Response, StdResult, Uint128, WasmMsg, @@ -13,9 +5,14 @@ use cosmwasm_std::{ use drop_helpers::answer::response; use drop_staking_base::state::splitter::Config as SplitterConfig; use drop_staking_base::{ + error::factory::ContractResult, msg::{ core::{InstantiateMsg as CoreInstantiateMsg, QueryMsg as CoreQueryMsg}, distribution::InstantiateMsg as DistributionInstantiateMsg, + factory::{ + ExecuteMsg, InstantiateMsg, MigrateMsg, ProxyMsg, QueryMsg, UpdateConfigMsg, + ValidatorSetMsg, + }, pump::InstantiateMsg as RewardsPumpInstantiateMsg, puppeteer::InstantiateMsg as PuppeteerInstantiateMsg, rewards_manager::{ @@ -32,7 +29,10 @@ use drop_staking_base::{ }, withdrawal_voucher::InstantiateMsg as WithdrawalVoucherInstantiateMsg, }, - state::pump::PumpTimeout, + state::{ + factory::{State, STATE}, + pump::PumpTimeout, + }, }; use neutron_sdk::{ bindings::{msg::NeutronMsg, query::NeutronQuery}, @@ -411,7 +411,7 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult) -> StdResult { let state = STATE.load(deps.storage)?; - to_json_binary(&crate::state::PauseInfoResponse { + to_json_binary(&drop_staking_base::state::factory::PauseInfoResponse { core: deps .querier .query_wasm_smart(state.core_contract, &CoreQueryMsg::PauseInfo {})?, @@ -569,14 +569,14 @@ fn execute_proxy_msg( } }, ProxyMsg::Core(msg) => match msg { - crate::msg::CoreMsg::Pause {} => { + drop_staking_base::msg::factory::CoreMsg::Pause {} => { messages.push(get_proxied_message( state.core_contract, drop_staking_base::msg::core::ExecuteMsg::Pause {}, vec![], )?); } - crate::msg::CoreMsg::Unpause {} => { + drop_staking_base::msg::factory::CoreMsg::Unpause {} => { messages.push(get_proxied_message( state.core_contract, drop_staking_base::msg::core::ExecuteMsg::Unpause {}, @@ -627,7 +627,7 @@ pub fn migrate( } fn get_splitter_receivers( - fee_params: Option, + fee_params: Option, staker_address: String, ) -> ContractResult> { match fee_params { diff --git a/contracts/factory/src/lib.rs b/contracts/factory/src/lib.rs index a5abdbb0..2943dbb5 100644 --- a/contracts/factory/src/lib.rs +++ b/contracts/factory/src/lib.rs @@ -1,4 +1 @@ pub mod contract; -pub mod error; -pub mod msg; -pub mod state; diff --git a/contracts/nft-querier/.cargo/config b/contracts/nft-querier/.cargo/config new file mode 100644 index 00000000..88ed1522 --- /dev/null +++ b/contracts/nft-querier/.cargo/config @@ -0,0 +1,2 @@ +[alias] +schema = "run --bin drop-nft-querier-schema" diff --git a/contracts/nft-querier/Cargo.toml b/contracts/nft-querier/Cargo.toml new file mode 100644 index 00000000..835f087e --- /dev/null +++ b/contracts/nft-querier/Cargo.toml @@ -0,0 +1,39 @@ +[package] +authors = ["Vladislav Vasilev "] +description = "Contract to query unbonding NFT status" +edition = "2021" +name = "drop-nft-querier" +version = "1.0.0" + +exclude = [ + # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. + "contract.wasm", + "hash.txt", +] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[dependencies] +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cw2 = { workspace = true } +thiserror = { workspace = true } +drop-staking-base = { workspace = true } +drop-puppeteer-base = { workspace = true } +drop-helpers = { workspace = true } +drop-macros = { workspace = true } +neutron-sdk = { workspace = true } +cw-ownable = { workspace = true } +semver = { workspace = true } +cw721 = { workspace = true } +cw721-base = { workspace = true } diff --git a/contracts/nft-querier/README.md b/contracts/nft-querier/README.md new file mode 100644 index 00000000..60b72a5b --- /dev/null +++ b/contracts/nft-querier/README.md @@ -0,0 +1 @@ +# DROP NFT Querier diff --git a/contracts/nft-querier/src/bin/drop-nft-querier-schema.rs b/contracts/nft-querier/src/bin/drop-nft-querier-schema.rs new file mode 100644 index 00000000..8404eb93 --- /dev/null +++ b/contracts/nft-querier/src/bin/drop-nft-querier-schema.rs @@ -0,0 +1,11 @@ +use cosmwasm_schema::write_api; +use drop_nft_querier::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + query: QueryMsg, + execute: ExecuteMsg, + migrate: MigrateMsg + } +} diff --git a/contracts/nft-querier/src/contract.rs b/contracts/nft-querier/src/contract.rs new file mode 100644 index 00000000..65ee446f --- /dev/null +++ b/contracts/nft-querier/src/contract.rs @@ -0,0 +1,117 @@ +use crate::{ + error::ContractResult, + msg::{ExecuteMsg, InstantiateMsg, NftState, QueryMsg}, + state::{Config, CONFIG}, +}; +use cosmwasm_std::{attr, to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response}; +use cw721::AllNftInfoResponse; +use drop_helpers::answer::response; +use drop_staking_base::{ + msg::{ + core::QueryMsg as CoreQueryMsg, factory::QueryMsg as FactoryQueryMsg, + withdrawal_voucher::QueryMsg as WithdrawalVoucherQueryMsg, + }, + state::factory::State as FactoryState, +}; +use neutron_sdk::bindings::{msg::NeutronMsg, query::NeutronQuery}; + +const CONTRACT_NAME: &str = concat!("crates.io:drop-staking__", env!("CARGO_PKG_NAME")); +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> ContractResult> { + cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + cw_ownable::initialize_owner(deps.storage, deps.api, Some(info.sender.as_str()))?; + CONFIG.save( + deps.storage, + &Config { + factory_contract: msg.factory_contract, + }, + )?; + Ok(response( + "instantiate", + CONTRACT_NAME, + vec![attr("owner", info.sender)], + )) +} + +#[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> ContractResult { + match msg { + QueryMsg::Config {} => to_json_binary(&CONFIG.load(deps.storage)?).map_err(From::from), + QueryMsg::NftState { nft_id } => query_nft_state(deps, nft_id), + QueryMsg::Ownership {} => { + to_json_binary(&cw_ownable::get_ownership(deps.storage)?).map_err(From::from) + } + } +} + +fn query_nft_state(deps: Deps, nft_id: String) -> ContractResult { + let factory_state: FactoryState = deps.querier.query_wasm_smart( + CONFIG.load(deps.storage)?.factory_contract, + &FactoryQueryMsg::State {}, + )?; + let nft_details: AllNftInfoResponse = + deps.querier + .query_wasm_smart( + factory_state.withdrawal_voucher_contract, + &WithdrawalVoucherQueryMsg::AllNftInfo { + token_id: nft_id, + include_expired: None, + }, + ) + .map_err(|_| crate::error::ContractError::UnknownNftId {})?; + + let batch_id = nft_details.info.extension.unwrap().batch_id; // We always have an extension + let unbond_batch: drop_staking_base::state::core::UnbondBatch = deps.querier.query_wasm_smart( + factory_state.core_contract, + &CoreQueryMsg::UnbondBatch { + batch_id: cosmwasm_std::Uint128::from(batch_id.parse::().unwrap()), // We always add this field by ourselves when creating NFT so we can certain that .unwrap() won't fail + }, + )?; + let nft_status = match unbond_batch.status { + drop_staking_base::state::core::UnbondBatchStatus::Withdrawn => NftState::Ready, + _ => NftState::Unready, + }; + to_json_binary(&nft_status).map_err(From::from) +} + +#[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> ContractResult> { + match msg { + ExecuteMsg::UpdateConfig { new_config } => execute_update_config(deps, info, new_config), + ExecuteMsg::UpdateOwnership(action) => { + cw_ownable::update_ownership(deps.into_empty(), &env.block, &info.sender, action)?; + Ok(response::<(&str, &str), _>( + "execute-update-ownership", + CONTRACT_NAME, + [], + )) + } + } +} + +fn execute_update_config( + deps: DepsMut, + info: MessageInfo, + msg: Config, +) -> ContractResult> { + cw_ownable::assert_owner(deps.storage, &info.sender)?; + let mut attrs = vec![attr("action", "update-config")]; + let mut config = CONFIG.load(deps.storage)?; + + config.factory_contract = msg.factory_contract.clone(); + attrs.push(attr("factory_contract", msg.factory_contract)); + + ContractResult::Ok(response("execute-update-config", CONTRACT_NAME, attrs)) +} diff --git a/contracts/nft-querier/src/error.rs b/contracts/nft-querier/src/error.rs new file mode 100644 index 00000000..e8c58a65 --- /dev/null +++ b/contracts/nft-querier/src/error.rs @@ -0,0 +1,16 @@ +use cosmwasm_std::StdError; +use cw_ownable::OwnershipError; + +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + #[error("{0}")] + OwnershipError(#[from] OwnershipError), + #[error("Unknown NFT ID given")] + UnknownNftId {}, +} + +pub type ContractResult = Result; diff --git a/contracts/nft-querier/src/lib.rs b/contracts/nft-querier/src/lib.rs new file mode 100644 index 00000000..cc53f548 --- /dev/null +++ b/contracts/nft-querier/src/lib.rs @@ -0,0 +1,7 @@ +pub mod contract; +pub mod error; +pub mod msg; +pub mod state; + +#[cfg(test)] +pub mod tests; diff --git a/contracts/nft-querier/src/msg.rs b/contracts/nft-querier/src/msg.rs new file mode 100644 index 00000000..177dad58 --- /dev/null +++ b/contracts/nft-querier/src/msg.rs @@ -0,0 +1,34 @@ +use crate::state::Config; +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::Addr; +use cw_ownable::{cw_ownable_execute, cw_ownable_query}; + +#[cw_serde] +pub struct InstantiateMsg { + pub factory_contract: Addr, +} + +#[cw_serde] +pub struct MigrateMsg {} + +#[cw_ownable_execute] +#[cw_serde] +pub enum ExecuteMsg { + UpdateConfig { new_config: Config }, +} + +#[cw_serde] +pub enum NftState { + Ready, + Unready, +} + +#[cw_ownable_query] +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(crate::state::Config)] + Config {}, + #[returns(NftState)] + NftState { nft_id: String }, +} diff --git a/contracts/nft-querier/src/state.rs b/contracts/nft-querier/src/state.rs new file mode 100644 index 00000000..d2644d96 --- /dev/null +++ b/contracts/nft-querier/src/state.rs @@ -0,0 +1,10 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Addr; +use cw_storage_plus::Item; + +#[cw_serde] +pub struct Config { + pub factory_contract: Addr, +} + +pub const CONFIG: Item = Item::new("config"); diff --git a/contracts/nft-querier/src/tests.rs b/contracts/nft-querier/src/tests.rs new file mode 100644 index 00000000..e0c9345a --- /dev/null +++ b/contracts/nft-querier/src/tests.rs @@ -0,0 +1,418 @@ +use crate::{ + error::ContractError, + msg::{ExecuteMsg, InstantiateMsg, NftState, QueryMsg}, + state::{Config, CONFIG}, +}; +use cosmwasm_std::{ + attr, from_json, + testing::{mock_env, mock_info}, + to_json_binary, Api, Empty, Event, Response, Uint128, +}; +use cw721::{NftInfoResponse, OwnerOfResponse}; +use drop_helpers::testing::mock_dependencies; +use drop_staking_base::msg::withdrawal_voucher::Extension; + +pub type Cw721VoucherContract<'a> = cw721_base::Cw721Contract<'a, Extension, Empty, Empty, Empty>; + +#[test] +fn test_execute_instantiate() { + let mut deps = mock_dependencies(&[]); + let factory_addr = deps.api.addr_validate("factory_contract").unwrap(); + let res = crate::contract::instantiate( + deps.as_mut().into_empty(), + mock_env(), + mock_info("owner", &[]), + InstantiateMsg { + factory_contract: factory_addr, + }, + ) + .unwrap(); + + assert_eq!( + res, + Response::new().add_event( + Event::new("crates.io:drop-staking__drop-nft-querier-instantiate") + .add_attribute("owner", "owner") + ) + ); + + let query_res: cw_ownable::Ownership = from_json( + crate::contract::query(deps.as_ref(), mock_env(), QueryMsg::Ownership {}).unwrap(), + ) + .unwrap(); + assert_eq!(query_res.owner.unwrap(), "owner"); +} + +#[test] +fn test_execute_update_config_unauthorized() { + let mut deps = mock_dependencies(&[]); + let new_factory_contract = deps.api.addr_validate("new_factory_contract").unwrap(); + let deps_mut = deps.as_mut(); + cw_ownable::initialize_owner(deps_mut.storage, deps_mut.api, Some("owner")).unwrap(); + let err = crate::contract::execute( + deps.as_mut().into_empty(), + mock_env(), + mock_info("somebody", &[]), + ExecuteMsg::UpdateConfig { + new_config: Config { + factory_contract: new_factory_contract, + }, + }, + ) + .unwrap_err(); + + assert_eq!( + err, + ContractError::OwnershipError(cw_ownable::OwnershipError::NotOwner) + ) +} + +#[test] +fn test_execute_update_config() { + let mut deps = mock_dependencies(&[]); + let old_factory_contract = deps.api.addr_validate("old_factory_contract").unwrap(); + let new_factory_contract = deps.api.addr_validate("new_factory_contract").unwrap(); + let deps_mut = deps.as_mut(); + cw_ownable::initialize_owner(deps_mut.storage, deps_mut.api, Some("owner")).unwrap(); + + CONFIG + .save( + deps_mut.storage, + &Config { + factory_contract: old_factory_contract, + }, + ) + .unwrap(); + + let res = crate::contract::execute( + deps.as_mut().into_empty(), + mock_env(), + mock_info("owner", &[]), + ExecuteMsg::UpdateConfig { + new_config: Config { + factory_contract: new_factory_contract, + }, + }, + ) + .unwrap(); + + assert_eq!( + res, + Response::new().add_event( + Event::new("crates.io:drop-staking__drop-nft-querier-execute-update-config") + .add_attributes(vec![ + attr("action", "update-config"), + attr("factory_contract", "new_factory_contract") + ]) + ) + ); +} + +#[test] +fn test_query_config() { + let mut deps = mock_dependencies(&[]); + let factory_contract = deps.api.addr_validate("factory_contract").unwrap(); + CONFIG + .save( + deps.as_mut().storage, + &Config { + factory_contract: factory_contract.clone(), + }, + ) + .unwrap(); + + let res: Config = + from_json(crate::contract::query(deps.as_ref(), mock_env(), QueryMsg::Config {}).unwrap()) + .unwrap(); + assert_eq!(res, Config { factory_contract }); +} + +#[test] +fn test_query_nft_state_ready() { + let mut deps = mock_dependencies(&[]); + let factory_contract = deps.api.addr_validate("factory_contract").unwrap(); + CONFIG + .save(deps.as_mut().storage, &Config { factory_contract }) + .unwrap(); + deps.querier + .add_wasm_query_response("factory_contract", |_| { + to_json_binary(&drop_staking_base::state::factory::State { + token_contract: "token_contract".to_string(), + core_contract: "core_contract".to_string(), + puppeteer_contract: "puppeteer_contract".to_string(), + staker_contract: "staker_contract".to_string(), + withdrawal_voucher_contract: "withdrawal_voucher_contract".to_string(), + withdrawal_manager_contract: "withdrawal_manager_contract".to_string(), + strategy_contract: "strategy_contract".to_string(), + validators_set_contract: "validators_set_contract".to_string(), + distribution_contract: "distribution_contract".to_string(), + rewards_manager_contract: "rewards_manager_contract".to_string(), + rewards_pump_contract: "rewards_pump_contract".to_string(), + splitter_contract: "splitter_contract".to_string(), + }) + .unwrap() + }); + deps.querier + .add_wasm_query_response("withdrawal_voucher_contract", |_| { + to_json_binary(&cw721::AllNftInfoResponse { + access: OwnerOfResponse { + owner: "owner".to_string(), + approvals: vec![], + }, + info: NftInfoResponse { + token_uri: None, + extension: drop_staking_base::state::withdrawal_voucher::Metadata { + name: "name".to_string(), + description: None, + attributes: None, + batch_id: "0".to_string(), + amount: Uint128::zero(), + }, + }, + }) + .unwrap() + }); + deps.querier.add_wasm_query_response("core_contract", |_| { + to_json_binary(&drop_staking_base::state::core::UnbondBatch { + total_dasset_amount_to_withdraw: Uint128::zero(), + expected_native_asset_amount: Uint128::zero(), + expected_release_time: 0u64, + total_unbond_items: 0u64, + status: drop_staking_base::state::core::UnbondBatchStatus::Withdrawn, + slashing_effect: None, + unbonded_amount: None, + withdrawn_amount: None, + status_timestamps: drop_staking_base::state::core::UnbondBatchStatusTimestamps { + new: 0u64, + unbond_requested: None, + unbond_failed: None, + unbonding: None, + withdrawing: None, + withdrawn: None, + withdrawing_emergency: None, + withdrawn_emergency: None, + }, + }) + .unwrap() + }); + + let res: NftState = from_json( + crate::contract::query( + deps.as_ref(), + mock_env(), + QueryMsg::NftState { + nft_id: "nft".to_string(), + }, + ) + .unwrap(), + ) + .unwrap(); + + assert_eq!(res, NftState::Ready); +} + +#[test] +fn test_query_nft_state_unready() { + let mut deps = mock_dependencies(&[]); + let factory_contract = deps.api.addr_validate("factory_contract").unwrap(); + + CONFIG + .save(deps.as_mut().storage, &Config { factory_contract }) + .unwrap(); + deps.querier + .add_wasm_query_response("factory_contract", |_| { + to_json_binary(&drop_staking_base::state::factory::State { + token_contract: "token_contract".to_string(), + core_contract: "core_contract".to_string(), + puppeteer_contract: "puppeteer_contract".to_string(), + staker_contract: "staker_contract".to_string(), + withdrawal_voucher_contract: "withdrawal_voucher_contract".to_string(), + withdrawal_manager_contract: "withdrawal_manager_contract".to_string(), + strategy_contract: "strategy_contract".to_string(), + validators_set_contract: "validators_set_contract".to_string(), + distribution_contract: "distribution_contract".to_string(), + rewards_manager_contract: "rewards_manager_contract".to_string(), + rewards_pump_contract: "rewards_pump_contract".to_string(), + splitter_contract: "splitter_contract".to_string(), + }) + .unwrap() + }); + deps.querier + .add_wasm_query_response("withdrawal_voucher_contract", |_| { + to_json_binary(&cw721::AllNftInfoResponse { + access: OwnerOfResponse { + owner: "owner".to_string(), + approvals: vec![], + }, + info: NftInfoResponse { + token_uri: None, + extension: drop_staking_base::state::withdrawal_voucher::Metadata { + name: "name".to_string(), + description: None, + attributes: None, + batch_id: "0".to_string(), + amount: Uint128::zero(), + }, + }, + }) + .unwrap() + }); + deps.querier.add_wasm_query_response("core_contract", |_| { + to_json_binary(&drop_staking_base::state::core::UnbondBatch { + total_dasset_amount_to_withdraw: Uint128::zero(), + expected_native_asset_amount: Uint128::zero(), + expected_release_time: 0u64, + total_unbond_items: 0u64, + status: drop_staking_base::state::core::UnbondBatchStatus::New, + slashing_effect: None, + unbonded_amount: None, + withdrawn_amount: None, + status_timestamps: drop_staking_base::state::core::UnbondBatchStatusTimestamps { + new: 0u64, + unbond_requested: None, + unbond_failed: None, + unbonding: None, + withdrawing: None, + withdrawn: None, + withdrawing_emergency: None, + withdrawn_emergency: None, + }, + }) + .unwrap() + }); + + let res: NftState = from_json( + crate::contract::query( + deps.as_ref(), + mock_env(), + QueryMsg::NftState { + nft_id: "nft".to_string(), + }, + ) + .unwrap(), + ) + .unwrap(); + + assert_eq!(res, NftState::Unready); +} + +#[test] +fn test_query_nft_state_unknown_nft_id() { + let mut deps = mock_dependencies(&[]); + let factory_contract = deps.api.addr_validate("factory_contract").unwrap(); + CONFIG + .save(deps.as_mut().storage, &Config { factory_contract }) + .unwrap(); + deps.querier + .add_wasm_query_response("factory_contract", |_| { + to_json_binary(&drop_staking_base::state::factory::State { + token_contract: "token_contract".to_string(), + core_contract: "core_contract".to_string(), + puppeteer_contract: "puppeteer_contract".to_string(), + staker_contract: "staker_contract".to_string(), + withdrawal_voucher_contract: "withdrawal_voucher_contract".to_string(), + withdrawal_manager_contract: "withdrawal_manager_contract".to_string(), + strategy_contract: "strategy_contract".to_string(), + validators_set_contract: "validators_set_contract".to_string(), + distribution_contract: "distribution_contract".to_string(), + rewards_manager_contract: "rewards_manager_contract".to_string(), + rewards_pump_contract: "rewards_pump_contract".to_string(), + splitter_contract: "splitter_contract".to_string(), + }) + .unwrap() + }); + deps.querier + .add_wasm_query_response("withdrawal_voucher_contract", move |_| { + to_json_binary(&cosmwasm_std::Binary::from( + Cw721VoucherContract::default() + .query( + mock_dependencies(&[]).as_ref().into_empty(), + mock_env(), + cw721_base::QueryMsg::AllNftInfo { + token_id: "wrong_token_id".to_string(), + include_expired: None, + }, + ) + .unwrap_err() + .to_string() + .as_bytes(), + )) + .unwrap() + }); + + let res = crate::contract::query( + deps.as_ref(), + mock_env(), + QueryMsg::NftState { + nft_id: "nft".to_string(), + }, + ) + .unwrap_err(); + + assert_eq!(res, crate::error::ContractError::UnknownNftId {}) +} + +#[test] +fn test_query_ownership() { + let mut deps = mock_dependencies(&[]); + let deps_mut = deps.as_mut(); + cw_ownable::initialize_owner(deps_mut.storage, deps_mut.api, Some("owner")).unwrap(); + let query_res: cw_ownable::Ownership = from_json( + crate::contract::query( + deps.as_ref(), + mock_env(), + crate::msg::QueryMsg::Ownership {}, + ) + .unwrap(), + ) + .unwrap(); + assert_eq!( + query_res, + cw_ownable::Ownership { + owner: Some(cosmwasm_std::Addr::unchecked("owner".to_string())), + pending_expiry: None, + pending_owner: None + } + ); +} + +#[test] +fn test_transfer_ownership() { + let mut deps = mock_dependencies(&[]); + let deps_mut = deps.as_mut(); + cw_ownable::initialize_owner(deps_mut.storage, deps_mut.api, Some("owner")).unwrap(); + crate::contract::execute( + deps.as_mut().into_empty(), + mock_env(), + mock_info("owner", &[]), + ExecuteMsg::UpdateOwnership(cw_ownable::Action::TransferOwnership { + new_owner: "new_owner".to_string(), + expiry: Some(cw_ownable::Expiration::Never {}), + }), + ) + .unwrap(); + crate::contract::execute( + deps.as_mut().into_empty(), + mock_env(), + mock_info("new_owner", &[]), + ExecuteMsg::UpdateOwnership(cw_ownable::Action::AcceptOwnership {}), + ) + .unwrap(); + let query_res: cw_ownable::Ownership = from_json( + crate::contract::query( + deps.as_ref(), + mock_env(), + crate::msg::QueryMsg::Ownership {}, + ) + .unwrap(), + ) + .unwrap(); + assert_eq!( + query_res, + cw_ownable::Ownership { + owner: Some(cosmwasm_std::Addr::unchecked("new_owner".to_string())), + pending_expiry: None, + pending_owner: None + } + ); +} diff --git a/integration_tests/package.json b/integration_tests/package.json index d53dfe9c..02ae90a0 100644 --- a/integration_tests/package.json +++ b/integration_tests/package.json @@ -5,6 +5,7 @@ "license": "MIT", "scripts": { "test": "vitest --run", + "test:nft-querier": "vitest --run nft-querier.test.ts --bail 1", "test:poc-provider-proposals": "vitest --run poc-provider-proposals.test.ts --bail 1", "test:poc-proposal-votes": "vitest --run poc-proposal-votes.test.ts --bail 1", "test:core": "vitest --run core.test.ts --bail 1", diff --git a/integration_tests/src/testcases/nft-querier.test.ts b/integration_tests/src/testcases/nft-querier.test.ts new file mode 100644 index 00000000..beb850f2 --- /dev/null +++ b/integration_tests/src/testcases/nft-querier.test.ts @@ -0,0 +1,1318 @@ +import { describe, expect, it, beforeAll, afterAll } from 'vitest'; +import { + DropAutoWithdrawer, + DropCore, + DropFactory, + DropPump, + DropPuppeteer, + DropStrategy, + DropStaker, + DropWithdrawalManager, + DropWithdrawalVoucher, + DropSplitter, + DropToken, + DropNftQuerier, +} from 'drop-ts-client'; +import { + QueryClient, + StakingExtension, + BankExtension, + setupStakingExtension, + setupBankExtension, + SigningStargateClient, +} from '@cosmjs/stargate'; +import { MsgTransfer } from 'cosmjs-types/ibc/applications/transfer/v1/tx'; +import { join } from 'path'; +import { Tendermint34Client } from '@cosmjs/tendermint-rpc'; +import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'; +import { Client as NeutronClient } from '@neutron-org/client-ts'; +import { AccountData, DirectSecp256k1HdWallet } from '@cosmjs/proto-signing'; +import { GasPrice } from '@cosmjs/stargate'; +import { setupPark } from '../testSuite'; +import fs from 'fs'; +import Cosmopark from '@neutron-org/cosmopark'; +import { waitFor } from '../helpers/waitFor'; +import { ResponseHookMsg } from 'drop-ts-client/lib/contractLib/dropCore'; +import { stringToPath } from '@cosmjs/crypto'; +import { sleep } from '../helpers/sleep'; +import { waitForPuppeteerICQ } from '../helpers/waitForPuppeteerICQ'; +import { instrumentCoreClass } from '../helpers/knot'; +import { checkExchangeRate } from '../helpers/exchangeRate'; + +const DropNftQuerierClass = DropNftQuerier.Client; +const DropTokenClass = DropToken.Client; +const DropFactoryClass = DropFactory.Client; +const DropCoreClass = DropCore.Client; +const DropPumpClass = DropPump.Client; +const DropPuppeteerClass = DropPuppeteer.Client; +const DropStrategyClass = DropStrategy.Client; +const DropStakerClass = DropStaker.Client; +const DropWithdrawalVoucherClass = DropWithdrawalVoucher.Client; +const DropWithdrawalManagerClass = DropWithdrawalManager.Client; +const DropAutoWithdrawerClass = DropAutoWithdrawer.Client; +const DropRewardsPumpClass = DropPump.Client; +const DropSplitterClass = DropSplitter.Client; +const UNBONDING_TIME = 360; + +describe('NFT Querier', () => { + const context: { + park?: Cosmopark; + contractAddress?: string; + wallet?: DirectSecp256k1HdWallet; + gaiaWallet?: DirectSecp256k1HdWallet; + gaiaWallet2?: DirectSecp256k1HdWallet; + nftQuerierContractClient?: InstanceType; + factoryContractClient?: InstanceType; + coreContractClient?: InstanceType; + strategyContractClient?: InstanceType; + stakerContractClient?: InstanceType; + pumpContractClient?: InstanceType; + splitterContractClient?: InstanceType; + rewardsPumpContractClient?: InstanceType; + puppeteerContractClient?: InstanceType; + tokenContractClient?: InstanceType; + withdrawalVoucherContractClient?: InstanceType< + typeof DropWithdrawalVoucherClass + >; + withdrawalManagerContractClient?: InstanceType< + typeof DropWithdrawalManagerClass + >; + autoWithdrawerContractClient?: InstanceType; + account?: AccountData; + icaAddress?: string; + rewardsPumpIcaAddress?: string; + stakerIcaAddress?: string; + client?: SigningCosmWasmClient; + gaiaClient?: SigningStargateClient; + gaiaUserAddress?: string; + gaiaUserAddress2?: string; + gaiaQueryClient?: QueryClient & StakingExtension & BankExtension; + neutronRPCEndpoint?: string; + neutronClient?: InstanceType; + neutronUserAddress?: string; + neutronSecondUserAddress?: string; + validatorAddress?: string; + secondValidatorAddress?: string; + tokenizedDenomOnNeutron?: string; + codeIds: { + core?: number; + token?: number; + withdrawalVoucher?: number; + withdrawalManager?: number; + strategy?: number; + puppeteer?: number; + staker?: number; + validatorsSet?: number; + distribution?: number; + rewardsManager?: number; + splitter?: number; + pump?: number; + nftQuerier?: number; + }; + exchangeRate?: number; + neutronIBCDenom?: string; + ldDenom?: string; + } = { codeIds: {} }; + + beforeAll(async (t) => { + context.park = await setupPark( + t, + ['neutron', 'gaia'], + { + gaia: { + genesis_opts: { + 'app_state.staking.params.unbonding_time': `${UNBONDING_TIME}s`, + }, + }, + }, + { + neutron: true, + hermes: { + config: { + 'chains.1.trusting_period': '2m0s', + }, + }, + }, + ); + context.wallet = await DirectSecp256k1HdWallet.fromMnemonic( + context.park.config.wallets.demowallet1.mnemonic, + { + prefix: 'neutron', + }, + ); + context.gaiaWallet = await DirectSecp256k1HdWallet.fromMnemonic( + context.park.config.wallets.demowallet1.mnemonic, + { + prefix: 'cosmos', + }, + ); + context.gaiaWallet2 = await DirectSecp256k1HdWallet.fromMnemonic( + context.park.config.wallets.demo1.mnemonic, + { + prefix: 'cosmos', + }, + ); + context.account = (await context.wallet.getAccounts())[0]; + context.neutronClient = new NeutronClient({ + apiURL: `http://127.0.0.1:${context.park.ports.neutron.rest}`, + rpcURL: `127.0.0.1:${context.park.ports.neutron.rpc}`, + prefix: 'neutron', + }); + context.neutronRPCEndpoint = `http://127.0.0.1:${context.park.ports.neutron.rpc}`; + context.client = await SigningCosmWasmClient.connectWithSigner( + context.neutronRPCEndpoint, + context.wallet, + { + gasPrice: GasPrice.fromString('0.025untrn'), + }, + ); + context.gaiaClient = await SigningStargateClient.connectWithSigner( + `http://127.0.0.1:${context.park.ports.gaia.rpc}`, + context.gaiaWallet, + { + gasPrice: GasPrice.fromString('0.025stake'), + }, + ); + const tmClient = await Tendermint34Client.connect( + `http://127.0.0.1:${context.park.ports.gaia.rpc}`, + ); + context.gaiaQueryClient = QueryClient.withExtensions( + tmClient, + setupStakingExtension, + setupBankExtension, + ); + const secondWallet = await DirectSecp256k1HdWallet.fromMnemonic( + context.park.config.wallets.demo2.mnemonic, + { + prefix: 'neutron', + }, + ); + context.neutronSecondUserAddress = ( + await secondWallet.getAccounts() + )[0].address; + }); + + afterAll(async () => { + await context.park.stop(); + }); + + it('transfer tokens to neutron', async () => { + context.gaiaUserAddress = ( + await context.gaiaWallet.getAccounts() + )[0].address; + context.gaiaUserAddress2 = ( + await context.gaiaWallet2.getAccounts() + )[0].address; + context.neutronUserAddress = ( + await context.wallet.getAccounts() + )[0].address; + { + const wallet = await DirectSecp256k1HdWallet.fromMnemonic( + context.park.config.master_mnemonic, + { + prefix: 'cosmosvaloper', + hdPaths: [stringToPath("m/44'/118'/1'/0/0") as any], + }, + ); + context.validatorAddress = (await wallet.getAccounts())[0].address; + } + { + const wallet = await DirectSecp256k1HdWallet.fromMnemonic( + context.park.config.master_mnemonic, + { + prefix: 'cosmosvaloper', + hdPaths: [stringToPath("m/44'/118'/2'/0/0") as any], + }, + ); + context.secondValidatorAddress = (await wallet.getAccounts())[0].address; + } + + const { gaiaClient, gaiaUserAddress, neutronUserAddress, neutronClient } = + context; + const res = await gaiaClient.signAndBroadcast( + gaiaUserAddress, + [ + { + typeUrl: '/ibc.applications.transfer.v1.MsgTransfer', + value: MsgTransfer.fromPartial({ + sender: gaiaUserAddress, + sourceChannel: 'channel-0', + sourcePort: 'transfer', + receiver: neutronUserAddress, + token: { denom: 'stake', amount: '2000000' }, + timeoutTimestamp: BigInt((Date.now() + 10 * 60 * 1000) * 1e6), + timeoutHeight: { + revisionHeight: BigInt(0), + revisionNumber: BigInt(0), + }, + }), + }, + ], + 1.5, + ); + expect(res.transactionHash).toHaveLength(64); + await waitFor(async () => { + const balances = + await neutronClient.CosmosBankV1Beta1.query.queryAllBalances( + neutronUserAddress, + ); + context.neutronIBCDenom = balances.data.balances.find((b) => + b.denom.startsWith('ibc/'), + )?.denom; + return balances.data.balances.length > 1; + }, 60_000); + expect(context.neutronIBCDenom).toBeTruthy(); + }); + + it('instantiate', async () => { + const { client, account } = context; + context.codeIds = {}; + { + const res = await client.upload( + account.address, + fs.readFileSync(join(__dirname, '../../../artifacts/drop_core.wasm')), + 1.5, + ); + expect(res.codeId).toBeGreaterThan(0); + context.codeIds.core = res.codeId; + } + { + const res = await client.upload( + account.address, + fs.readFileSync(join(__dirname, '../../../artifacts/drop_token.wasm')), + 1.5, + ); + expect(res.codeId).toBeGreaterThan(0); + context.codeIds.token = res.codeId; + } + { + const res = await client.upload( + account.address, + fs.readFileSync( + join(__dirname, '../../../artifacts/drop_withdrawal_voucher.wasm'), + ), + 1.5, + ); + expect(res.codeId).toBeGreaterThan(0); + context.codeIds.withdrawalVoucher = res.codeId; + } + { + const res = await client.upload( + account.address, + fs.readFileSync( + join(__dirname, '../../../artifacts/drop_withdrawal_manager.wasm'), + ), + 1.5, + ); + expect(res.codeId).toBeGreaterThan(0); + context.codeIds.withdrawalManager = res.codeId; + } + { + const res = await client.upload( + account.address, + fs.readFileSync( + join(__dirname, '../../../artifacts/drop_strategy.wasm'), + ), + 1.5, + ); + expect(res.codeId).toBeGreaterThan(0); + context.codeIds.strategy = res.codeId; + } + { + const res = await client.upload( + account.address, + fs.readFileSync( + join(__dirname, '../../../artifacts/drop_distribution.wasm'), + ), + 1.5, + ); + expect(res.codeId).toBeGreaterThan(0); + context.codeIds.distribution = res.codeId; + } + { + const res = await client.upload( + account.address, + fs.readFileSync( + join(__dirname, '../../../artifacts/drop_validators_set.wasm'), + ), + 1.5, + ); + expect(res.codeId).toBeGreaterThan(0); + context.codeIds.validatorsSet = res.codeId; + } + { + const res = await client.upload( + account.address, + fs.readFileSync( + join(__dirname, '../../../artifacts/drop_puppeteer.wasm'), + ), + 1.5, + ); + expect(res.codeId).toBeGreaterThan(0); + context.codeIds.puppeteer = res.codeId; + } + { + const res = await client.upload( + account.address, + fs.readFileSync( + join(__dirname, '../../../artifacts/drop_rewards_manager.wasm'), + ), + 1.5, + ); + expect(res.codeId).toBeGreaterThan(0); + context.codeIds.rewardsManager = res.codeId; + } + { + const res = await client.upload( + account.address, + fs.readFileSync(join(__dirname, '../../../artifacts/drop_staker.wasm')), + 1.5, + ); + expect(res.codeId).toBeGreaterThan(0); + context.codeIds.staker = res.codeId; + } + { + const res = await client.upload( + account.address, + fs.readFileSync( + join(__dirname, '../../../artifacts/drop_splitter.wasm'), + ), + 1.5, + ); + expect(res.codeId).toBeGreaterThan(0); + context.codeIds.splitter = res.codeId; + } + { + const res = await client.upload( + account.address, + fs.readFileSync(join(__dirname, '../../../artifacts/drop_pump.wasm')), + 1.5, + ); + expect(res.codeId).toBeGreaterThan(0); + context.codeIds.pump = res.codeId; + } + { + const res = await client.upload( + account.address, + fs.readFileSync( + join(__dirname, '../../../artifacts/drop_nft_querier.wasm'), + ), + 1.5, + ); + expect(res.codeId).toBeGreaterThan(0); + context.codeIds.nftQuerier = res.codeId; + } + const res = await client.upload( + account.address, + fs.readFileSync(join(__dirname, '../../../artifacts/drop_factory.wasm')), + 1.5, + ); + expect(res.codeId).toBeGreaterThan(0); + let instantiateRes = await DropFactory.Client.instantiate( + client, + account.address, + res.codeId, + { + sdk_version: process.env.SDK_VERSION || '0.46.0', + code_ids: { + core_code_id: context.codeIds.core, + token_code_id: context.codeIds.token, + withdrawal_voucher_code_id: context.codeIds.withdrawalVoucher, + withdrawal_manager_code_id: context.codeIds.withdrawalManager, + strategy_code_id: context.codeIds.strategy, + distribution_code_id: context.codeIds.distribution, + validators_set_code_id: context.codeIds.validatorsSet, + puppeteer_code_id: context.codeIds.puppeteer, + rewards_manager_code_id: context.codeIds.rewardsManager, + staker_code_id: context.codeIds.staker, + splitter_code_id: context.codeIds.splitter, + rewards_pump_code_id: context.codeIds.pump, + }, + remote_opts: { + connection_id: 'connection-0', + transfer_channel_id: 'channel-0', + reverse_transfer_channel_id: 'channel-0', + port_id: 'transfer', + denom: 'stake', + update_period: 2, + timeout: { + local: 60, + remote: 60, + }, + }, + salt: 'salt', + subdenom: 'drop', + token_metadata: { + description: 'Drop token', + display: 'drop', + exponent: 6, + name: 'Drop liquid staking token', + symbol: 'DROP', + uri: null, + uri_hash: null, + }, + local_denom: 'untrn', + base_denom: context.neutronIBCDenom, + core_params: { + idle_min_interval: 40, + unbond_batch_switch_time: 60, + unbonding_safe_period: 10, + unbonding_period: UNBONDING_TIME, + lsm_redeem_threshold: 10, + lsm_redeem_max_interval: 60_000, + lsm_min_bond_amount: '1', + min_stake_amount: '2', + icq_update_delay: 5, + }, + staker_params: { + min_stake_amount: '10000', + min_ibc_transfer: '1', + }, + }, + 'drop-staking-factory', + 'auto', + [], + ); + expect(instantiateRes.contractAddress).toHaveLength(66); + context.contractAddress = instantiateRes.contractAddress; + context.factoryContractClient = new DropFactory.Client( + client, + context.contractAddress, + ); + + instantiateRes = await DropNftQuerier.Client.instantiate( + client, + account.address, + context.codeIds.nftQuerier, + { + factory_contract: context.contractAddress, + }, + 'drop-nft-querier', + 'auto', + [], + ); + expect(instantiateRes.contractAddress).toHaveLength(66); + context.nftQuerierContractClient = new DropNftQuerier.Client( + client, + instantiateRes.contractAddress, + ); + }); + + it('query factory state', async () => { + const { factoryContractClient: contractClient, neutronClient } = context; + const res = await contractClient.queryState(); + expect(res).toBeTruthy(); + const tokenContractInfo = + await neutronClient.CosmwasmWasmV1.query.queryContractInfo( + res.token_contract, + ); + expect(tokenContractInfo.data.contract_info.label).toBe( + 'drop-staking-token', + ); + const coreContractInfo = + await neutronClient.CosmwasmWasmV1.query.queryContractInfo( + res.core_contract, + ); + expect(coreContractInfo.data.contract_info.label).toBe('drop-staking-core'); + const withdrawalVoucherContractInfo = + await neutronClient.CosmwasmWasmV1.query.queryContractInfo( + res.withdrawal_voucher_contract, + ); + expect(withdrawalVoucherContractInfo.data.contract_info.label).toBe( + 'drop-staking-withdrawal-voucher', + ); + const withdrawalManagerContractInfo = + await neutronClient.CosmwasmWasmV1.query.queryContractInfo( + res.withdrawal_manager_contract, + ); + expect(withdrawalManagerContractInfo.data.contract_info.label).toBe( + 'drop-staking-withdrawal-manager', + ); + const puppeteerContractInfo = + await neutronClient.CosmwasmWasmV1.query.queryContractInfo( + res.puppeteer_contract, + ); + expect(puppeteerContractInfo.data.contract_info.label).toBe( + 'drop-staking-puppeteer', + ); + context.coreContractClient = instrumentCoreClass( + new DropCore.Client(context.client, res.core_contract), + ); + context.withdrawalVoucherContractClient = new DropWithdrawalVoucher.Client( + context.client, + res.withdrawal_voucher_contract, + ); + context.withdrawalManagerContractClient = new DropWithdrawalManager.Client( + context.client, + res.withdrawal_manager_contract, + ); + context.strategyContractClient = new DropStrategy.Client( + context.client, + res.strategy_contract, + ); + context.puppeteerContractClient = new DropPuppeteer.Client( + context.client, + res.puppeteer_contract, + ); + context.stakerContractClient = new DropStaker.Client( + context.client, + res.staker_contract, + ); + context.splitterContractClient = new DropSplitter.Client( + context.client, + res.splitter_contract, + ); + context.rewardsPumpContractClient = new DropPump.Client( + context.client, + res.rewards_pump_contract, + ); + context.tokenContractClient = new DropToken.Client( + context.client, + res.token_contract, + ); + context.ldDenom = `factory/${res.token_contract}/drop`; + }); + + it('setup ICA for rewards pump', async () => { + const { rewardsPumpContractClient, neutronUserAddress } = context; + const res = await rewardsPumpContractClient.registerICA( + neutronUserAddress, + 1.5, + undefined, + [{ amount: '1000000', denom: 'untrn' }], + ); + expect(res.transactionHash).toHaveLength(64); + let ica = ''; + await waitFor(async () => { + const res = await rewardsPumpContractClient.queryIca(); + switch (res) { + case 'none': + case 'in_progress': + case 'timeout': + return false; + default: + ica = res.registered.ica_address; + return true; + } + }, 100_000); + expect(ica).toHaveLength(65); + expect(ica.startsWith('cosmos')).toBeTruthy(); + context.rewardsPumpIcaAddress = ica; + }); + + it('register staker ICA', async () => { + const { stakerContractClient, neutronUserAddress } = context; + const res = await stakerContractClient.registerICA( + neutronUserAddress, + 1.5, + undefined, + [{ amount: '1000000', denom: 'untrn' }], + ); + expect(res.transactionHash).toHaveLength(64); + let ica = ''; + await waitFor(async () => { + const res = await stakerContractClient.queryIca(); + switch (res) { + case 'none': + case 'in_progress': + case 'timeout': + return false; + default: + ica = res.registered.ica_address; + return true; + } + }, 100_000); + expect(ica).toHaveLength(65); + expect(ica.startsWith('cosmos')).toBeTruthy(); + context.stakerIcaAddress = ica; + }); + + it('register puppeteer ICA', async () => { + const { puppeteerContractClient, neutronUserAddress } = context; + const res = await puppeteerContractClient.registerICA( + neutronUserAddress, + 1.5, + undefined, + [{ amount: '1000000', denom: 'untrn' }], + ); + expect(res.transactionHash).toHaveLength(64); + let ica = ''; + await waitFor(async () => { + const res = await puppeteerContractClient.queryIca(); + switch (res) { + case 'none': + case 'in_progress': + case 'timeout': + return false; + default: + ica = res.registered.ica_address; + return true; + } + }, 100_000); + expect(ica).toHaveLength(65); + expect(ica.startsWith('cosmos')).toBeTruthy(); + context.icaAddress = ica; + }); + + it('set puppeteer ICA to the staker', async () => { + const res = await context.factoryContractClient.adminExecute( + context.neutronUserAddress, + { + msgs: [ + { + wasm: { + execute: { + contract_addr: context.stakerContractClient.contractAddress, + msg: Buffer.from( + JSON.stringify({ + update_config: { + new_config: { + puppeteer_ica: context.icaAddress, + }, + }, + }), + ).toString('base64'), + funds: [], + }, + }, + }, + ], + }, + 1.5, + undefined, + [], + ); + expect(res.transactionHash).toHaveLength(64); + }); + it('grant staker to delegate funds from puppeteer ICA and set up rewards receiver', async () => { + const { neutronUserAddress } = context; + const res = await context.factoryContractClient.adminExecute( + neutronUserAddress, + { + msgs: [ + { + wasm: { + execute: { + contract_addr: context.puppeteerContractClient.contractAddress, + msg: Buffer.from( + JSON.stringify({ + setup_protocol: { + delegate_grantee: context.stakerIcaAddress, + rewards_withdraw_address: context.rewardsPumpIcaAddress, + }, + }), + ).toString('base64'), + funds: [ + { + amount: '20000', + denom: 'untrn', + }, + ], + }, + }, + }, + ], + }, + 1.5, + undefined, + [ + { + amount: '20000', + denom: 'untrn', + }, + ], + ); + expect(res.transactionHash).toHaveLength(64); + const pupRes = await context.puppeteerContractClient.queryTxState(); + expect(pupRes.status).toBe('waiting_for_ack'); + }); + it('wait puppeteer response', async () => { + const { puppeteerContractClient } = context; + await waitFor(async () => { + const res = await puppeteerContractClient.queryTxState(); + return res.status === 'idle'; + }, 100_000); + }); + it('verify grant', async () => { + const res = await context.park.executeInNetwork( + 'gaia', + `${context.park.config.networks['gaia'].binary} query authz grants-by-grantee ${context.stakerIcaAddress} --output json`, + ); + const out = JSON.parse(res.out); + expect(out.grants).toHaveLength(1); + const grant = out.grants[0]; + expect(grant.granter).toEqual(context.icaAddress); + expect(grant.grantee).toEqual(context.stakerIcaAddress); + }); + + it('query exchange rate', async () => { + const { coreContractClient } = context; + context.exchangeRate = parseFloat( + await coreContractClient.queryExchangeRate(), + ); + expect(context.exchangeRate).toEqual(1); + await checkExchangeRate(context); + }); + + it('add validators into validators set', async () => { + const { + neutronUserAddress, + factoryContractClient, + validatorAddress, + secondValidatorAddress, + } = context; + const res = await factoryContractClient.proxy( + neutronUserAddress, + { + validator_set: { + update_validators: { + validators: [ + { + valoper_address: validatorAddress, + weight: 1, + }, + { + valoper_address: secondValidatorAddress, + weight: 1, + }, + ], + }, + }, + }, + 1.5, + undefined, + [ + { + amount: '1000000', + denom: 'untrn', + }, + ], + ); + expect(res.transactionHash).toHaveLength(64); + }); + + it('bond', async () => { + const { coreContractClient, neutronIBCDenom, neutronUserAddress } = context; + const { transactionHash } = await coreContractClient.bond( + neutronUserAddress, + {}, + 1.6, + undefined, + [ + { + amount: '10000', + denom: neutronIBCDenom, + }, + ], + ); + const { code } = await context.client.getTx(transactionHash); + expect(code).toBe(0); + await checkExchangeRate(context); + }); + + it('unbond', async () => { + const { coreContractClient, ldDenom, neutronUserAddress } = context; + const { transactionHash } = await coreContractClient.unbond( + neutronUserAddress, + 1.6, + undefined, + [ + { + amount: '100', + denom: ldDenom, + }, + ], + ); + const { code } = await context.client.getTx(transactionHash); + expect(code).toBe(0); + await checkExchangeRate(context); + }); + + it('check nft status', async () => { + const { tokens } = + await context.withdrawalVoucherContractClient.queryTokens({ + owner: context.neutronUserAddress, + }); + expect(tokens).toHaveLength(1); + const res = await context.nftQuerierContractClient.queryNftState({ + nft_id: tokens[0], + }); + expect(res).toBe('unready'); + }); + + it('try wrong nft', async () => { + await expect( + context.nftQuerierContractClient.queryNftState({ + nft_id: 'wrong_nft_id', + }), + ).rejects.toThrowError(/Unknown NFT ID given/); + }); + + describe('state machine', () => { + const ica: { balance?: number } = {}; + describe('prepare', () => { + it('get ICA balance', async () => { + const { gaiaClient } = context; + const res = await gaiaClient.getBalance(context.icaAddress, 'stake'); + ica.balance = parseInt(res.amount); + expect(ica.balance).toEqual(0); + }); + it('deploy pump', async () => { + const { client, account, neutronUserAddress } = context; + const resUpload = await client.upload( + account.address, + fs.readFileSync(join(__dirname, '../../../artifacts/drop_pump.wasm')), + 1.5, + ); + expect(resUpload.codeId).toBeGreaterThan(0); + const { codeId } = resUpload; + const res = await DropPump.Client.instantiate( + client, + neutronUserAddress, + codeId, + { + connection_id: 'connection-0', + local_denom: 'untrn', + timeout: { + local: 60, + remote: 60, + }, + dest_address: + context.withdrawalManagerContractClient.contractAddress, + dest_port: 'transfer', + dest_channel: 'channel-0', + refundee: neutronUserAddress, + owner: account.address, + }, + 'drop-staking-pump', + 1.5, + [], + ); + expect(res.contractAddress).toHaveLength(66); + context.pumpContractClient = new DropPump.Client( + client, + res.contractAddress, + ); + await context.pumpContractClient.registerICA( + neutronUserAddress, + 1.5, + undefined, + [ + { + amount: '1000000', + denom: 'untrn', + }, + ], + ); + let ica = ''; + await waitFor(async () => { + const res = await context.pumpContractClient.queryIca(); + switch (res) { + case 'none': + case 'in_progress': + case 'timeout': + return false; + default: + ica = res.registered.ica_address; + return true; + } + }, 50_000); + expect(ica).toHaveLength(65); + expect(ica.startsWith('cosmos')).toBeTruthy(); + const resFactory = await context.factoryContractClient.updateConfig( + neutronUserAddress, + { + core: { + pump_ica_address: ica, + }, + }, + ); + expect(resFactory.transactionHash).toHaveLength(64); + }); + it('get machine state', async () => { + const state = await context.coreContractClient.queryContractState(); + expect(state).toEqual('idle'); + }); + }); + describe('first cycle', () => { + it('staker ibc transfer', async () => { + const { neutronUserAddress } = context; + const res = await context.stakerContractClient.iBCTransfer( + neutronUserAddress, + 1.5, + undefined, + [{ amount: '20000', denom: 'untrn' }], + ); + expect(res.transactionHash).toHaveLength(64); + await waitFor(async () => { + const res = await context.stakerContractClient.queryTxState(); + return res.status === 'idle'; + }, 80_000); + const balances = await context.gaiaClient.getAllBalances( + context.stakerIcaAddress, + ); + expect(balances).toEqual([ + { + amount: '10000', + denom: context.park.config.networks.gaia.denom, + }, + ]); + }); + it('tick', async () => { + const { + neutronUserAddress, + gaiaClient, + coreContractClient, + puppeteerContractClient, + } = context; + + await waitForPuppeteerICQ( + gaiaClient, + coreContractClient, + puppeteerContractClient, + ); + + const res = await context.coreContractClient.tick( + neutronUserAddress, + 1.5, + undefined, + [ + { + amount: '1000000', + denom: 'untrn', + }, + ], + ); + expect(res.transactionHash).toHaveLength(64); + const state = await context.coreContractClient.queryContractState(); + expect(state).toEqual('staking_bond'); + await checkExchangeRate(context); + }); + it('wait for response from staker', async () => { + let response; + await waitFor(async () => { + try { + response = ( + await context.coreContractClient.queryLastStakerResponse() + ).response; + } catch (e) { + // + } + return !!response; + }, 100_000); + }); + it('get staker ICA zeroed balance', async () => { + const { gaiaClient } = context; + const res = await gaiaClient.getBalance( + context.stakerIcaAddress, + 'stake', + ); + const balance = parseInt(res.amount); + expect(0).toEqual(ica.balance); + ica.balance = balance; + }); + it('wait for balances to come', async () => { + let res; + const { remote_height: currentHeight } = + await context.puppeteerContractClient.queryExtension({ + msg: { + balances: {}, + }, + }); + await waitFor(async () => { + try { + res = await context.puppeteerContractClient.queryExtension({ + msg: { + balances: {}, + }, + }); + return res.remote_height !== currentHeight; + } catch (e) { + // + } + }, 100_000); + }); + it('second tick goes to unbonding', async () => { + const { + neutronUserAddress, + gaiaClient, + coreContractClient, + puppeteerContractClient, + } = context; + + await waitForPuppeteerICQ( + gaiaClient, + coreContractClient, + puppeteerContractClient, + ); + + const res = await context.coreContractClient.tick( + neutronUserAddress, + 1.5, + undefined, + [ + { + amount: '1000000', + denom: 'untrn', + }, + ], + ); + expect(res.transactionHash).toHaveLength(64); + const state = await context.coreContractClient.queryContractState(); + expect(state).toEqual('unbonding'); + await checkExchangeRate(context); + }); + it('wait for response from puppeteer', async () => { + let response: ResponseHookMsg; + await waitFor(async () => { + try { + response = ( + await context.coreContractClient.queryLastPuppeteerResponse() + ).response; + } catch (e) { + return false; + } + if (response === null) { + return false; + } + if ('error' in response) { + throw new Error(response.error.details); + } + return response && 'success' in response; + }, 100_000); + }); + it('next tick goes to idle', async () => { + const { + neutronUserAddress, + gaiaClient, + coreContractClient, + puppeteerContractClient, + } = context; + + await waitForPuppeteerICQ( + gaiaClient, + coreContractClient, + puppeteerContractClient, + ); + + const res = await context.coreContractClient.tick( + neutronUserAddress, + 1.5, + undefined, + [], + ); + expect(res.transactionHash).toHaveLength(64); + const state = await context.coreContractClient.queryContractState(); + expect(state).toEqual('idle'); + await checkExchangeRate(context); + }); + }); + describe('second cycle', () => { + let balance = 0; + it('get ICA balance', async () => { + const { gaiaClient } = context; + const res = await gaiaClient.getBalance(context.icaAddress, 'stake'); + balance = parseInt(res.amount); + }); + it('wait for 30 seconds', async () => { + await sleep(30_000); + }); + it('idle tick', async () => { + const { + neutronUserAddress, + gaiaClient, + coreContractClient, + puppeteerContractClient, + } = context; + + await waitForPuppeteerICQ( + gaiaClient, + coreContractClient, + puppeteerContractClient, + ); + + const res = await context.coreContractClient.tick( + neutronUserAddress, + 1.5, + undefined, + [], + ); + expect(res.transactionHash).toHaveLength(64); + const state = await context.coreContractClient.queryContractState(); + expect(state).toEqual('claiming'); + await checkExchangeRate(context); + }); + it('wait for response from puppeteer', async () => { + let response; + await waitFor(async () => { + try { + response = ( + await context.coreContractClient.queryLastPuppeteerResponse() + ).response; + } catch (e) { + // + } + return !!response; + }, 100_000); + }); + it('get ICA balance', async () => { + const { gaiaClient } = context; + const res = await gaiaClient.getBalance( + context.rewardsPumpIcaAddress, + 'stake', + ); + const newBalance = parseInt(res.amount); + expect(newBalance).toBeGreaterThanOrEqual(balance); + }); + it('wait for balance to update', async () => { + const { remote_height: currentHeight } = + (await context.puppeteerContractClient.queryExtension({ + msg: { + balances: {}, + }, + })) as any; + await waitFor(async () => { + const { remote_height: nowHeight } = + (await context.puppeteerContractClient.queryExtension({ + msg: { + balances: {}, + }, + })) as any; + return nowHeight !== currentHeight; + }, 30_000); + }); + it('next tick goes to idle', async () => { + const { + neutronUserAddress, + gaiaClient, + coreContractClient, + puppeteerContractClient, + } = context; + + await waitForPuppeteerICQ( + gaiaClient, + coreContractClient, + puppeteerContractClient, + ); + + const res = await context.coreContractClient.tick( + neutronUserAddress, + 1.5, + undefined, + [], + ); + expect(res.transactionHash).toHaveLength(64); + const state = await context.coreContractClient.queryContractState(); + expect(state).toEqual('idle'); + await checkExchangeRate(context); + }); + }); + describe('third cycle', () => { + it('update idle interval', async () => { + const { factoryContractClient, neutronUserAddress } = context; + const res = await factoryContractClient.updateConfig( + neutronUserAddress, + { + core: { + idle_min_interval: 10, + }, + }, + ); + expect(res.transactionHash).toHaveLength(64); + await sleep(10 * 1000); + }); + it(`wait until unbonding period is finished`, async () => { + const batchInfo = await context.coreContractClient.queryUnbondBatch({ + batch_id: '0', + }); + const currentTime = Math.floor(Date.now() / 1000); + if (batchInfo.expected_release_time > currentTime) { + const diffMs = + (batchInfo.expected_release_time - currentTime + 1) * 1000; + await sleep(diffMs); + } + }); + it('wait for ICA balance', async () => { + const { gaiaClient } = context; + await waitFor(async () => { + const res = await gaiaClient.getBalance(context.icaAddress, 'stake'); + return parseInt(res.amount) > 0; + }, 60_000); + }); + it('wait until fresh ICA balance is delivered', async () => { + const batchInfo = await context.coreContractClient.queryUnbondBatch({ + batch_id: '0', + }); + await waitFor(async () => { + const res = (await context.puppeteerContractClient.queryExtension({ + msg: { + balances: {}, + }, + })) as any; + const icaTs = Math.floor(res.timestamp / 1e9); + return icaTs > batchInfo.expected_release_time; + }, 500_000); + }); + it('tick', async () => { + const { coreContractClient, neutronUserAddress } = context; + await coreContractClient.tick(neutronUserAddress, 1.5, undefined, []); + const state = await context.coreContractClient.queryContractState(); + expect(state).toEqual('claiming'); + await checkExchangeRate(context); + }); + it('wait for the response from puppeteer', async () => { + let response: ResponseHookMsg; + await waitFor(async () => { + try { + response = ( + await context.coreContractClient.queryLastPuppeteerResponse() + ).response; + } catch (e) { + // + } + return !!response; + }, 200_000); + expect(response).toBeTruthy(); + expect(response).toHaveProperty('success'); + }); + it('wait for ICQ update', async () => { + await waitForPuppeteerICQ( + context.gaiaClient, + context.coreContractClient, + context.puppeteerContractClient, + ); + }); + it('tick', async () => { + const { + coreContractClient, + neutronUserAddress, + gaiaClient, + puppeteerContractClient, + } = context; + + await waitForPuppeteerICQ( + gaiaClient, + coreContractClient, + puppeteerContractClient, + ); + + await coreContractClient.tick(neutronUserAddress, 1.5, undefined, []); + const state = await context.coreContractClient.queryContractState(); + expect(state).toEqual('idle'); + await checkExchangeRate(context); + }); + }); + }); + + it('check nft status', async () => { + const { tokens } = + await context.withdrawalVoucherContractClient.queryTokens({ + owner: context.neutronUserAddress, + }); + expect(tokens).toHaveLength(1); + const res = await context.nftQuerierContractClient.queryNftState({ + nft_id: tokens[0], + }); + expect(res).toBe('ready'); + }); +}); diff --git a/contracts/factory/src/error.rs b/packages/base/src/error/factory.rs similarity index 100% rename from contracts/factory/src/error.rs rename to packages/base/src/error/factory.rs diff --git a/packages/base/src/error/mod.rs b/packages/base/src/error/mod.rs index 9c2c8033..61f3cf35 100644 --- a/packages/base/src/error/mod.rs +++ b/packages/base/src/error/mod.rs @@ -1,6 +1,7 @@ pub mod astroport_exchange_handler; pub mod core; pub mod distribution; +pub mod factory; pub mod price_provider; pub mod rewards_manager; pub mod splitter; diff --git a/contracts/factory/src/msg.rs b/packages/base/src/msg/factory.rs similarity index 82% rename from contracts/factory/src/msg.rs rename to packages/base/src/msg/factory.rs index 3f5b2e47..978c002f 100644 --- a/contracts/factory/src/msg.rs +++ b/packages/base/src/msg/factory.rs @@ -1,9 +1,11 @@ -use crate::state::{CodeIds, RemoteOpts}; +use crate::{ + msg::token::DenomMetadata, + state::factory::{CodeIds, RemoteOpts}, +}; use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{CosmosMsg, Decimal, Uint128}; use cw_ownable::cw_ownable_execute; use drop_macros::pausable; -use drop_staking_base::msg::token::DenomMetadata; use neutron_sdk::bindings::msg::NeutronMsg; #[cw_serde] @@ -49,8 +51,8 @@ pub struct StakerParams { #[cw_serde] pub enum UpdateConfigMsg { - Core(Box), - ValidatorsSet(drop_staking_base::state::validatorset::ConfigOptional), + Core(Box), + ValidatorsSet(crate::state::validatorset::ConfigOptional), } #[cw_serde] @@ -68,7 +70,7 @@ pub enum CoreMsg { #[cw_serde] pub enum ValidatorSetMsg { UpdateValidators { - validators: Vec, + validators: Vec, }, } @@ -86,8 +88,8 @@ pub struct MigrateMsg {} #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { - #[returns(crate::state::State)] + #[returns(crate::state::factory::State)] State {}, - #[returns(crate::state::PauseInfoResponse)] + #[returns(crate::state::factory::PauseInfoResponse)] PauseInfo {}, } diff --git a/packages/base/src/msg/mod.rs b/packages/base/src/msg/mod.rs index 00f5b8ea..2d316ac9 100644 --- a/packages/base/src/msg/mod.rs +++ b/packages/base/src/msg/mod.rs @@ -1,6 +1,7 @@ pub mod astroport_exchange_handler; pub mod core; pub mod distribution; +pub mod factory; pub mod hook_tester; pub mod price_provider; pub mod proposal_votes; diff --git a/contracts/factory/src/state.rs b/packages/base/src/state/factory.rs similarity index 100% rename from contracts/factory/src/state.rs rename to packages/base/src/state/factory.rs diff --git a/packages/base/src/state/mod.rs b/packages/base/src/state/mod.rs index d52aff68..0b77a8df 100644 --- a/packages/base/src/state/mod.rs +++ b/packages/base/src/state/mod.rs @@ -1,5 +1,6 @@ pub mod astroport_exchange_handler; pub mod core; +pub mod factory; pub mod hook_tester; pub mod price_provider; pub mod proposal_votes; diff --git a/ts-client/lib/contractLib/dropNftQuerier.d.ts b/ts-client/lib/contractLib/dropNftQuerier.d.ts new file mode 100644 index 00000000..43b56ade --- /dev/null +++ b/ts-client/lib/contractLib/dropNftQuerier.d.ts @@ -0,0 +1,99 @@ +import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult, InstantiateResult } from "@cosmjs/cosmwasm-stargate"; +import { StdFee } from "@cosmjs/amino"; +import { Coin } from "@cosmjs/amino"; +export type NftState = "ready" | "unready"; +/** + * Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future) + */ +export type Expiration = { + at_height: number; +} | { + at_time: Timestamp; +} | { + never: {}; +}; +/** + * A point in time in nanosecond precision. + * + * This type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z. + * + * ## Examples + * + * ``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202); + * + * let ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ``` + */ +export type Timestamp = Uint64; +/** + * A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq. + * + * # Examples + * + * Use `from` to create instances of this and `u64` to get the value out: + * + * ``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42); + * + * let b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ``` + */ +export type Uint64 = string; +/** + * Actions that can be taken to alter the contract's ownership + */ +export type UpdateOwnershipArgs = { + transfer_ownership: { + expiry?: Expiration | null; + new_owner: string; + }; +} | "accept_ownership" | "renounce_ownership"; +export interface DropNftQuerierSchema { + responses: Config | NftState | OwnershipForString; + query: NftStateArgs; + execute: UpdateConfigArgs | UpdateOwnershipArgs; + instantiate?: InstantiateMsg; + [k: string]: unknown; +} +export interface Config { + factory_contract: string; +} +/** + * The contract's ownership info + */ +export interface OwnershipForString { + /** + * The contract's current owner. `None` if the ownership has been renounced. + */ + owner?: string | null; + /** + * The deadline for the pending owner to accept the ownership. `None` if there isn't a pending ownership transfer, or if a transfer exists and it doesn't have a deadline. + */ + pending_expiry?: Expiration | null; + /** + * The account who has been proposed to take over the ownership. `None` if there isn't a pending ownership transfer. + */ + pending_owner?: string | null; +} +export interface NftStateArgs { + nft_id: string; +} +export interface UpdateConfigArgs { + new_config: Config1; +} +export interface Config1 { + factory_contract: string; +} +export interface InstantiateMsg { + factory_contract: string; +} +export declare class Client { + private readonly client; + contractAddress: string; + constructor(client: CosmWasmClient | SigningCosmWasmClient, contractAddress: string); + mustBeSigningClient(): Error; + static instantiate(client: SigningCosmWasmClient, sender: string, codeId: number, initMsg: InstantiateMsg, label: string, fees: StdFee | 'auto' | number, initCoins?: readonly Coin[]): Promise; + static instantiate2(client: SigningCosmWasmClient, sender: string, codeId: number, salt: number, initMsg: InstantiateMsg, label: string, fees: StdFee | 'auto' | number, initCoins?: readonly Coin[]): Promise; + queryConfig: () => Promise; + queryNftState: (args: NftStateArgs) => Promise; + queryOwnership: () => Promise; + updateConfig: (sender: string, args: UpdateConfigArgs, fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]) => Promise; + updateOwnership: (sender: string, args: UpdateOwnershipArgs, fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]) => Promise; +} diff --git a/ts-client/lib/contractLib/dropNftQuerier.js b/ts-client/lib/contractLib/dropNftQuerier.js new file mode 100644 index 00000000..28bc121a --- /dev/null +++ b/ts-client/lib/contractLib/dropNftQuerier.js @@ -0,0 +1,51 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Client = void 0; +function isSigningCosmWasmClient(client) { + return 'execute' in client; +} +class Client { + client; + contractAddress; + constructor(client, contractAddress) { + this.client = client; + this.contractAddress = contractAddress; + } + mustBeSigningClient() { + return new Error("This client is not a SigningCosmWasmClient"); + } + static async instantiate(client, sender, codeId, initMsg, label, fees, initCoins) { + const res = await client.instantiate(sender, codeId, initMsg, label, fees, { + ...(initCoins && initCoins.length && { funds: initCoins }), + }); + return res; + } + static async instantiate2(client, sender, codeId, salt, initMsg, label, fees, initCoins) { + const res = await client.instantiate2(sender, codeId, new Uint8Array([salt]), initMsg, label, fees, { + ...(initCoins && initCoins.length && { funds: initCoins }), + }); + return res; + } + queryConfig = async () => { + return this.client.queryContractSmart(this.contractAddress, { config: {} }); + }; + queryNftState = async (args) => { + return this.client.queryContractSmart(this.contractAddress, { nft_state: args }); + }; + queryOwnership = async () => { + return this.client.queryContractSmart(this.contractAddress, { ownership: {} }); + }; + updateConfig = async (sender, args, fee, memo, funds) => { + if (!isSigningCosmWasmClient(this.client)) { + throw this.mustBeSigningClient(); + } + return this.client.execute(sender, this.contractAddress, { update_config: args }, fee || "auto", memo, funds); + }; + updateOwnership = async (sender, args, fee, memo, funds) => { + if (!isSigningCosmWasmClient(this.client)) { + throw this.mustBeSigningClient(); + } + return this.client.execute(sender, this.contractAddress, { update_ownership: args }, fee || "auto", memo, funds); + }; +} +exports.Client = Client; diff --git a/ts-client/lib/contractLib/index.d.ts b/ts-client/lib/contractLib/index.d.ts index 393a306b..56863237 100644 --- a/ts-client/lib/contractLib/index.d.ts +++ b/ts-client/lib/contractLib/index.d.ts @@ -10,31 +10,33 @@ import * as _4 from './dropFactory'; export declare const DropFactory: typeof _4; import * as _5 from './dropHookTester'; export declare const DropHookTester: typeof _5; -import * as _6 from './dropPriceProvider'; -export declare const DropPriceProvider: typeof _6; -import * as _7 from './dropProposalVotesPoc'; -export declare const DropProposalVotesPoc: typeof _7; -import * as _8 from './dropProviderProposalsPoc'; -export declare const DropProviderProposalsPoc: typeof _8; -import * as _9 from './dropPump'; -export declare const DropPump: typeof _9; -import * as _10 from './dropPuppeteer'; -export declare const DropPuppeteer: typeof _10; -import * as _11 from './dropRewardsManager'; -export declare const DropRewardsManager: typeof _11; -import * as _12 from './dropSplitter'; -export declare const DropSplitter: typeof _12; -import * as _13 from './dropStaker'; -export declare const DropStaker: typeof _13; -import * as _14 from './dropStrategy'; -export declare const DropStrategy: typeof _14; -import * as _15 from './dropToken'; -export declare const DropToken: typeof _15; -import * as _16 from './dropValidatorsSet'; -export declare const DropValidatorsSet: typeof _16; -import * as _17 from './dropValidatorsStats'; -export declare const DropValidatorsStats: typeof _17; -import * as _18 from './dropWithdrawalManager'; -export declare const DropWithdrawalManager: typeof _18; -import * as _19 from './dropWithdrawalVoucher'; -export declare const DropWithdrawalVoucher: typeof _19; +import * as _6 from './dropNftQuerier'; +export declare const DropNftQuerier: typeof _6; +import * as _7 from './dropPriceProvider'; +export declare const DropPriceProvider: typeof _7; +import * as _8 from './dropProposalVotesPoc'; +export declare const DropProposalVotesPoc: typeof _8; +import * as _9 from './dropProviderProposalsPoc'; +export declare const DropProviderProposalsPoc: typeof _9; +import * as _10 from './dropPump'; +export declare const DropPump: typeof _10; +import * as _11 from './dropPuppeteer'; +export declare const DropPuppeteer: typeof _11; +import * as _12 from './dropRewardsManager'; +export declare const DropRewardsManager: typeof _12; +import * as _13 from './dropSplitter'; +export declare const DropSplitter: typeof _13; +import * as _14 from './dropStaker'; +export declare const DropStaker: typeof _14; +import * as _15 from './dropStrategy'; +export declare const DropStrategy: typeof _15; +import * as _16 from './dropToken'; +export declare const DropToken: typeof _16; +import * as _17 from './dropValidatorsSet'; +export declare const DropValidatorsSet: typeof _17; +import * as _18 from './dropValidatorsStats'; +export declare const DropValidatorsStats: typeof _18; +import * as _19 from './dropWithdrawalManager'; +export declare const DropWithdrawalManager: typeof _19; +import * as _20 from './dropWithdrawalVoucher'; +export declare const DropWithdrawalVoucher: typeof _20; diff --git a/ts-client/lib/contractLib/index.js b/ts-client/lib/contractLib/index.js index f70634a5..23538b3d 100644 --- a/ts-client/lib/contractLib/index.js +++ b/ts-client/lib/contractLib/index.js @@ -23,7 +23,7 @@ var __importStar = (this && this.__importStar) || function (mod) { return result; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.DropWithdrawalVoucher = exports.DropWithdrawalManager = exports.DropValidatorsStats = exports.DropValidatorsSet = exports.DropToken = exports.DropStrategy = exports.DropStaker = exports.DropSplitter = exports.DropRewardsManager = exports.DropPuppeteer = exports.DropPump = exports.DropProviderProposalsPoc = exports.DropProposalVotesPoc = exports.DropPriceProvider = exports.DropHookTester = exports.DropFactory = exports.DropDistribution = exports.DropCore = exports.DropAutoWithdrawer = exports.DropAstroportExchangeHandler = void 0; +exports.DropWithdrawalVoucher = exports.DropWithdrawalManager = exports.DropValidatorsStats = exports.DropValidatorsSet = exports.DropToken = exports.DropStrategy = exports.DropStaker = exports.DropSplitter = exports.DropRewardsManager = exports.DropPuppeteer = exports.DropPump = exports.DropProviderProposalsPoc = exports.DropProposalVotesPoc = exports.DropPriceProvider = exports.DropNftQuerier = exports.DropHookTester = exports.DropFactory = exports.DropDistribution = exports.DropCore = exports.DropAutoWithdrawer = exports.DropAstroportExchangeHandler = void 0; const _0 = __importStar(require("./dropAstroportExchangeHandler")); exports.DropAstroportExchangeHandler = _0; const _1 = __importStar(require("./dropAutoWithdrawer")); @@ -36,31 +36,33 @@ const _4 = __importStar(require("./dropFactory")); exports.DropFactory = _4; const _5 = __importStar(require("./dropHookTester")); exports.DropHookTester = _5; -const _6 = __importStar(require("./dropPriceProvider")); -exports.DropPriceProvider = _6; -const _7 = __importStar(require("./dropProposalVotesPoc")); -exports.DropProposalVotesPoc = _7; -const _8 = __importStar(require("./dropProviderProposalsPoc")); -exports.DropProviderProposalsPoc = _8; -const _9 = __importStar(require("./dropPump")); -exports.DropPump = _9; -const _10 = __importStar(require("./dropPuppeteer")); -exports.DropPuppeteer = _10; -const _11 = __importStar(require("./dropRewardsManager")); -exports.DropRewardsManager = _11; -const _12 = __importStar(require("./dropSplitter")); -exports.DropSplitter = _12; -const _13 = __importStar(require("./dropStaker")); -exports.DropStaker = _13; -const _14 = __importStar(require("./dropStrategy")); -exports.DropStrategy = _14; -const _15 = __importStar(require("./dropToken")); -exports.DropToken = _15; -const _16 = __importStar(require("./dropValidatorsSet")); -exports.DropValidatorsSet = _16; -const _17 = __importStar(require("./dropValidatorsStats")); -exports.DropValidatorsStats = _17; -const _18 = __importStar(require("./dropWithdrawalManager")); -exports.DropWithdrawalManager = _18; -const _19 = __importStar(require("./dropWithdrawalVoucher")); -exports.DropWithdrawalVoucher = _19; +const _6 = __importStar(require("./dropNftQuerier")); +exports.DropNftQuerier = _6; +const _7 = __importStar(require("./dropPriceProvider")); +exports.DropPriceProvider = _7; +const _8 = __importStar(require("./dropProposalVotesPoc")); +exports.DropProposalVotesPoc = _8; +const _9 = __importStar(require("./dropProviderProposalsPoc")); +exports.DropProviderProposalsPoc = _9; +const _10 = __importStar(require("./dropPump")); +exports.DropPump = _10; +const _11 = __importStar(require("./dropPuppeteer")); +exports.DropPuppeteer = _11; +const _12 = __importStar(require("./dropRewardsManager")); +exports.DropRewardsManager = _12; +const _13 = __importStar(require("./dropSplitter")); +exports.DropSplitter = _13; +const _14 = __importStar(require("./dropStaker")); +exports.DropStaker = _14; +const _15 = __importStar(require("./dropStrategy")); +exports.DropStrategy = _15; +const _16 = __importStar(require("./dropToken")); +exports.DropToken = _16; +const _17 = __importStar(require("./dropValidatorsSet")); +exports.DropValidatorsSet = _17; +const _18 = __importStar(require("./dropValidatorsStats")); +exports.DropValidatorsStats = _18; +const _19 = __importStar(require("./dropWithdrawalManager")); +exports.DropWithdrawalManager = _19; +const _20 = __importStar(require("./dropWithdrawalVoucher")); +exports.DropWithdrawalVoucher = _20; diff --git a/ts-client/src/contractLib/dropNftQuerier.ts b/ts-client/src/contractLib/dropNftQuerier.ts new file mode 100644 index 00000000..0b1d1ae7 --- /dev/null +++ b/ts-client/src/contractLib/dropNftQuerier.ts @@ -0,0 +1,158 @@ +import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult, InstantiateResult } from "@cosmjs/cosmwasm-stargate"; +import { StdFee } from "@cosmjs/amino"; +import { Coin } from "@cosmjs/amino"; +export type NftState = "ready" | "unready"; +/** + * Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future) + */ +export type Expiration = + | { + at_height: number; + } + | { + at_time: Timestamp; + } + | { + never: {}; + }; +/** + * A point in time in nanosecond precision. + * + * This type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z. + * + * ## Examples + * + * ``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202); + * + * let ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ``` + */ +export type Timestamp = Uint64; +/** + * A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq. + * + * # Examples + * + * Use `from` to create instances of this and `u64` to get the value out: + * + * ``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42); + * + * let b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ``` + */ +export type Uint64 = string; +/** + * Actions that can be taken to alter the contract's ownership + */ +export type UpdateOwnershipArgs = + | { + transfer_ownership: { + expiry?: Expiration | null; + new_owner: string; + }; + } + | "accept_ownership" + | "renounce_ownership"; + +export interface DropNftQuerierSchema { + responses: Config | NftState | OwnershipForString; + query: NftStateArgs; + execute: UpdateConfigArgs | UpdateOwnershipArgs; + instantiate?: InstantiateMsg; + [k: string]: unknown; +} +export interface Config { + factory_contract: string; +} +/** + * The contract's ownership info + */ +export interface OwnershipForString { + /** + * The contract's current owner. `None` if the ownership has been renounced. + */ + owner?: string | null; + /** + * The deadline for the pending owner to accept the ownership. `None` if there isn't a pending ownership transfer, or if a transfer exists and it doesn't have a deadline. + */ + pending_expiry?: Expiration | null; + /** + * The account who has been proposed to take over the ownership. `None` if there isn't a pending ownership transfer. + */ + pending_owner?: string | null; +} +export interface NftStateArgs { + nft_id: string; +} +export interface UpdateConfigArgs { + new_config: Config1; +} +export interface Config1 { + factory_contract: string; +} +export interface InstantiateMsg { + factory_contract: string; +} + + +function isSigningCosmWasmClient( + client: CosmWasmClient | SigningCosmWasmClient +): client is SigningCosmWasmClient { + return 'execute' in client; +} + +export class Client { + private readonly client: CosmWasmClient | SigningCosmWasmClient; + contractAddress: string; + constructor(client: CosmWasmClient | SigningCosmWasmClient, contractAddress: string) { + this.client = client; + this.contractAddress = contractAddress; + } + mustBeSigningClient() { + return new Error("This client is not a SigningCosmWasmClient"); + } + static async instantiate( + client: SigningCosmWasmClient, + sender: string, + codeId: number, + initMsg: InstantiateMsg, + label: string, + fees: StdFee | 'auto' | number, + initCoins?: readonly Coin[], + ): Promise { + const res = await client.instantiate(sender, codeId, initMsg, label, fees, { + ...(initCoins && initCoins.length && { funds: initCoins }), + }); + return res; + } + static async instantiate2( + client: SigningCosmWasmClient, + sender: string, + codeId: number, + salt: number, + initMsg: InstantiateMsg, + label: string, + fees: StdFee | 'auto' | number, + initCoins?: readonly Coin[], + ): Promise { + const res = await client.instantiate2(sender, codeId, new Uint8Array([salt]), initMsg, label, fees, { + ...(initCoins && initCoins.length && { funds: initCoins }), + }); + return res; + } + queryConfig = async(): Promise => { + return this.client.queryContractSmart(this.contractAddress, { config: {} }); + } + queryNftState = async(args: NftStateArgs): Promise => { + return this.client.queryContractSmart(this.contractAddress, { nft_state: args }); + } + queryOwnership = async(): Promise => { + return this.client.queryContractSmart(this.contractAddress, { ownership: {} }); + } + updateConfig = async(sender:string, args: UpdateConfigArgs, fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]): Promise => { + if (!isSigningCosmWasmClient(this.client)) { throw this.mustBeSigningClient(); } + return this.client.execute(sender, this.contractAddress, { update_config: args }, fee || "auto", memo, funds); + } + updateOwnership = async(sender:string, args: UpdateOwnershipArgs, fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]): Promise => { + if (!isSigningCosmWasmClient(this.client)) { throw this.mustBeSigningClient(); } + return this.client.execute(sender, this.contractAddress, { update_ownership: args }, fee || "auto", memo, funds); + } +} diff --git a/ts-client/src/contractLib/index.ts b/ts-client/src/contractLib/index.ts index 96dd5f5a..da949d70 100644 --- a/ts-client/src/contractLib/index.ts +++ b/ts-client/src/contractLib/index.ts @@ -16,44 +16,47 @@ export const DropFactory = _4; import * as _5 from './dropHookTester'; export const DropHookTester = _5; -import * as _6 from './dropPriceProvider'; -export const DropPriceProvider = _6; +import * as _6 from './dropNftQuerier'; +export const DropNftQuerier = _6; -import * as _7 from './dropProposalVotesPoc'; -export const DropProposalVotesPoc = _7; +import * as _7 from './dropPriceProvider'; +export const DropPriceProvider = _7; -import * as _8 from './dropProviderProposalsPoc'; -export const DropProviderProposalsPoc = _8; +import * as _8 from './dropProposalVotesPoc'; +export const DropProposalVotesPoc = _8; -import * as _9 from './dropPump'; -export const DropPump = _9; +import * as _9 from './dropProviderProposalsPoc'; +export const DropProviderProposalsPoc = _9; -import * as _10 from './dropPuppeteer'; -export const DropPuppeteer = _10; +import * as _10 from './dropPump'; +export const DropPump = _10; -import * as _11 from './dropRewardsManager'; -export const DropRewardsManager = _11; +import * as _11 from './dropPuppeteer'; +export const DropPuppeteer = _11; -import * as _12 from './dropSplitter'; -export const DropSplitter = _12; +import * as _12 from './dropRewardsManager'; +export const DropRewardsManager = _12; -import * as _13 from './dropStaker'; -export const DropStaker = _13; +import * as _13 from './dropSplitter'; +export const DropSplitter = _13; -import * as _14 from './dropStrategy'; -export const DropStrategy = _14; +import * as _14 from './dropStaker'; +export const DropStaker = _14; -import * as _15 from './dropToken'; -export const DropToken = _15; +import * as _15 from './dropStrategy'; +export const DropStrategy = _15; -import * as _16 from './dropValidatorsSet'; -export const DropValidatorsSet = _16; +import * as _16 from './dropToken'; +export const DropToken = _16; -import * as _17 from './dropValidatorsStats'; -export const DropValidatorsStats = _17; +import * as _17 from './dropValidatorsSet'; +export const DropValidatorsSet = _17; -import * as _18 from './dropWithdrawalManager'; -export const DropWithdrawalManager = _18; +import * as _18 from './dropValidatorsStats'; +export const DropValidatorsStats = _18; -import * as _19 from './dropWithdrawalVoucher'; -export const DropWithdrawalVoucher = _19; +import * as _19 from './dropWithdrawalManager'; +export const DropWithdrawalManager = _19; + +import * as _20 from './dropWithdrawalVoucher'; +export const DropWithdrawalVoucher = _20; diff --git a/tx.json b/tx.json deleted file mode 100644 index a974b7ed..00000000 --- a/tx.json +++ /dev/null @@ -1,192 +0,0 @@ -{ - "txBody": { - "messages": [ - { - "typeUrl": "/cosmwasm.wasm.v1.MsgExecuteContract", - "value": { - "sender": "neutron138q98gmx5pqpzmmtsn8j3440zm4jzuf4daxgv5", - "contract": "neutron12jm24l9lr9cupufqjuxpdjnnweana4h66tsx5cl800mke26td26sq7m05p", - "msg": "eyJleGVjdXRlX3N3YXBfb3BlcmF0aW9ucyI6eyJvcGVyYXRpb25zIjpbeyJhc3Ryb19zd2FwIjp7Im9mZmVyX2Fzc2V0X2luZm8iOnsibmF0aXZlX3Rva2VuIjp7ImRlbm9tIjoidW50cm4ifX0sImFza19hc3NldF9pbmZvIjp7Im5hdGl2ZV90b2tlbiI6eyJkZW5vbSI6ImliYy9GOTFFQTJDMEEyMzY5N0ExMDQ4RTA4QzJGNzg3RTNBNThBQzZGNzA2QTFDRDIyNTdBNTA0OTI1MTU4Q0ZDMEYzIn19fX0seyJhc3Ryb19zd2FwIjp7Im9mZmVyX2Fzc2V0X2luZm8iOnsibmF0aXZlX3Rva2VuIjp7ImRlbm9tIjoiaWJjL0Y5MUVBMkMwQTIzNjk3QTEwNDhFMDhDMkY3ODdFM0E1OEFDNkY3MDZBMUNEMjI1N0E1MDQ5MjUxNThDRkMwRjMifX0sImFza19hc3NldF9pbmZvIjp7Im5hdGl2ZV90b2tlbiI6eyJkZW5vbSI6ImliYy9FRkIwMEU3MjhGOThGMEM0QkJFOENBMzYyMTIzQUNBQjQ2NkVEQTI4MjZEQzY4MzdFNDlGNEMxOTAyRjIxQkJBIn19fX0seyJhc3Ryb19zd2FwIjp7Im9mZmVyX2Fzc2V0X2luZm8iOnsibmF0aXZlX3Rva2VuIjp7ImRlbm9tIjoiaWJjL0VGQjAwRTcyOEY5OEYwQzRCQkU4Q0EzNjIxMjNBQ0FCNDY2RURBMjgyNkRDNjgzN0U0OUY0QzE5MDJGMjFCQkEifX0sImFza19hc3NldF9pbmZvIjp7InRva2VuIjp7ImNvbnRyYWN0X2FkZHIiOiJuZXV0cm9uMXZwc2dyemVkd2Q4ZmV6cHN1OWZjZmV3dnA2bm12NGt6ZDdhNm51dHBtZ2V5amszYXJscXN5cG5saG0ifX19fSx7ImFzdHJvX3N3YXAiOnsib2ZmZXJfYXNzZXRfaW5mbyI6eyJ0b2tlbiI6eyJjb250cmFjdF9hZGRyIjoibmV1dHJvbjF2cHNncnplZHdkOGZlenBzdTlmY2Zld3ZwNm5tdjRremQ3YTZudXRwbWdleWprM2FybHFzeXBubGhtIn19LCJhc2tfYXNzZXRfaW5mbyI6eyJuYXRpdmVfdG9rZW4iOnsiZGVub20iOiJpYmMvQzRDRkY0NkZENkRFMzVDQTRDRjRDRTAzMUU2NDNDOEZEQzlCQTRCOTlBRTU5OEU5QjBFRDk4RkUzQTIzMTlGOSJ9fX19XSwibWluaW11bV9yZWNlaXZlIjoiMTE1Njk2In19", - "funds": [ - { - "denom": "untrn", - "amount": "5000" - } - ] - } - } - ], - "memo": "", - "timeoutHeight": "0", - "extensionOptions": [], - "nonCriticalExtensionOptions": [] - }, - "authInfo": { - "signerInfos": [ - { - "publicKey": { - "typeUrl": "/cosmos.crypto.secp256k1.PubKey", - "value": "CiECcGK0AxIYYWCML3mbycn8k9A67LvNjxOpWGq19ysbwyM=" - }, - "modeInfo": { - "single": { - "mode": "SIGN_MODE_DIRECT" - } - }, - "sequence": "0" - } - ], - "fee": { - "amount": [ - { - "denom": "untrn", - "amount": "57043" - } - ], - "gasLimit": "2852133", - "payer": "", - "granter": "" - } - }, - "chainId": "pion-1", - "accountNumber": "8950" -} - - -{ - "execute_swap_operations": { - "operations": [ - { - "astro_swap": { - "offer_asset_info": { - "native_token": { - "denom": "untrn" - } - }, - "ask_asset_info": { - "native_token": { - "denom": "ibc/F91EA2C0A23697A1048E08C2F787E3A58AC6F706A1CD2257A504925158CFC0F3" - } - } - } - }, - { - "astro_swap": { - "offer_asset_info": { - "native_token": { - "denom": "ibc/F91EA2C0A23697A1048E08C2F787E3A58AC6F706A1CD2257A504925158CFC0F3" - } - }, - "ask_asset_info": { - "native_token": { - "denom": "ibc/EFB00E728F98F0C4BBE8CA362123ACAB466EDA2826DC6837E49F4C1902F21BBA" - } - } - } - }, - { - "astro_swap": { - "offer_asset_info": { - "native_token": { - "denom": "ibc/EFB00E728F98F0C4BBE8CA362123ACAB466EDA2826DC6837E49F4C1902F21BBA" - } - }, - "ask_asset_info": { - "token": { - "contract_addr": "neutron1vpsgrzedwd8fezpsu9fcfewvp6nmv4kzd7a6nutpmgeyjk3arlqsypnlhm" - } - } - } - }, - { - "astro_swap": { - "offer_asset_info": { - "token": { - "contract_addr": "neutron1vpsgrzedwd8fezpsu9fcfewvp6nmv4kzd7a6nutpmgeyjk3arlqsypnlhm" - } - }, - "ask_asset_info": { - "native_token": { - "denom": "ibc/C4CFF46FD6DE35CA4CF4CE031E643C8FDC9BA4B99AE598E9B0ED98FE3A2319F9" - } - } - } - } - ], - "minimum_receive": "115696" - } -} - - ----------------------- -{ - "swap": { - "offer_asset": { - "info": { - "native_token": { - "denom": "untrn" - } - }, - "amount": "5000" - }, - "max_spread": "0.005", - "belief_price": "1.168770453482935951" - } -} - -{ - "txBody": { - "messages": [ - { - "typeUrl": "/cosmwasm.wasm.v1.MsgExecuteContract", - "value": { - "sender": "neutron138q98gmx5pqpzmmtsn8j3440zm4jzuf4daxgv5", - "contract": "neutron1udz38ekgxfhgpdnlhjv0h4533rdk5r43a7kcxfunk9nrgwnsc2hqjllny5", - "msg": "eyJzd2FwIjp7Im9mZmVyX2Fzc2V0Ijp7ImluZm8iOnsibmF0aXZlX3Rva2VuIjp7ImRlbm9tIjoidW50cm4ifX0sImFtb3VudCI6IjUwMDAifSwibWF4X3NwcmVhZCI6IjAuMDA1IiwiYmVsaWVmX3ByaWNlIjoiMS4xNjg3NzA0NTM0ODI5MzU5NTEifX0=", - "funds": [ - { - "denom": "untrn", - "amount": "5000" - } - ] - } - } - ], - "memo": "", - "timeoutHeight": "0", - "extensionOptions": [], - "nonCriticalExtensionOptions": [] - }, - "authInfo": { - "signerInfos": [ - { - "publicKey": { - "typeUrl": "/cosmos.crypto.secp256k1.PubKey", - "value": "CiECcGK0AxIYYWCML3mbycn8k9A67LvNjxOpWGq19ysbwyM=" - }, - "modeInfo": { - "single": { - "mode": "SIGN_MODE_DIRECT" - } - }, - "sequence": "1" - } - ], - "fee": { - "amount": [ - { - "denom": "untrn", - "amount": "7157" - } - ], - "gasLimit": "357824", - "payer": "", - "granter": "" - } - }, - "chainId": "pion-1", - "accountNumber": "8950" -} - - -