diff --git a/.github/workflows/check-broken-links.yml b/.github/workflows/check-broken-links.yml new file mode 100644 index 00000000000..1f68178db8b --- /dev/null +++ b/.github/workflows/check-broken-links.yml @@ -0,0 +1,26 @@ +name: Check Links + +on: + schedule: + - cron: '0 0 * * *' # Run daily at 00:00 UTC + workflow_dispatch: # Allow manual triggering of the workflow + +jobs: + check_links: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Link Checker + id: lychee + uses: lycheeverse/lychee-action@v1.6.1 + env: + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + + - name: Create Issue From File + if: env.lychee_exit_code != 0 + uses: peter-evans/create-issue-from-file@v4 + with: + title: Link Checker Report + content-filepath: ./lychee/out.md + labels: report, automated issue diff --git a/.github/workflows/contracts.yml b/.github/workflows/contracts.yml index c63e76c3946..c9c0c96da14 100644 --- a/.github/workflows/contracts.yml +++ b/.github/workflows/contracts.yml @@ -38,6 +38,7 @@ jobs: working-directory: ${{ matrix.contract.workdir }} run: > docker run --rm -v "$(pwd)":/code \ + -e REGISTRY_CONTRACT=osmo14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sq2r9g9 \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ cosmwasm/workspace-optimizer:0.12.10 diff --git a/CHANGELOG.md b/CHANGELOG.md index b5f29ffe574..20c31825c70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Misc Improvements * [#4582](https://github.com/osmosis-labs/osmosis/pull/4582) Consistently generate build tags metadata, to return a comma-separated list without stray quotes. This affects the output from `version` CLI subcommand and server info API calls. + * [#4549](https://github.com/osmosis-labs/osmosis/pull/4549) Add single pool price estimate queries + +### API Breaks ### API breaks diff --git a/README.md b/README.md index c2d1fc71ac7..ca14762bcb4 100644 --- a/README.md +++ b/README.md @@ -78,3 +78,8 @@ running an Osmosis node! The contributing guide for Osmosis explains the branching structure, how to use the SDK fork, and how to make / test updates to SDK branches. + +## LocalOsmosis + +LocalOsmosis is a containerized local Osmosis testnet used for trying out new features locally. +LocalOsmosis documentation can be found [here](https://github.com/osmosis-labs/osmosis/tree/main/tests/localosmosis) diff --git a/cmd/osmosisd/cmd/init.go b/cmd/osmosisd/cmd/init.go index 16d98c88827..f61ce528e1a 100644 --- a/cmd/osmosisd/cmd/init.go +++ b/cmd/osmosisd/cmd/init.go @@ -96,7 +96,7 @@ func InitCmd(mbm module.BasicManager, defaultNodeHome string) *cobra.Command { "24841abfc8fbd401d8c86747eec375649a2e8a7e@osmosis.pbcups.org:26656", // Pbcups "77bb5fb9b6964d6e861e91c1d55cf82b67d838b5@bd-osmosis-seed-mainnet-us-01.bdnodes.net:26656", // Blockdaemon US "3243426ab56b67f794fa60a79cc7f11bc7aa752d@bd-osmosis-seed-mainnet-eu-02.bdnodes.net:26656", // Blockdaemon EU - "ebc272824924ea1a27ea3183dd0b9ba713494f83@osmosis-mainnet-seed.autostake.net:26716", // AutoStake + "ebc272824924ea1a27ea3183dd0b9ba713494f83@osmosis-mainnet-seed.autostake.com:26716", // AutoStake.com "7c66126b64cd66bafd9ccfc721f068df451d31a3@osmosis-seed.sunshinevalidation.io:9393", // Sunshine Validation } config.P2P.Seeds = strings.Join(seeds, ",") diff --git a/cmd/querygen/templates/grpcTemplate.go b/cmd/querygen/templates/grpcTemplate.go index 0a112d58dd9..a0c129f7c0a 100644 --- a/cmd/querygen/templates/grpcTemplate.go +++ b/cmd/querygen/templates/grpcTemplate.go @@ -10,12 +10,13 @@ type GrpcTemplate struct { type GrpcQuery struct { QueryName string + Response string } func GrpcTemplateFromQueryYml(queryYml QueryYml) GrpcTemplate { GrpcQueries := []GrpcQuery{} for queryName := range queryYml.Queries { - GrpcQueries = append(GrpcQueries, GrpcQuery{QueryName: queryName}) + GrpcQueries = append(GrpcQueries, GrpcQuery{QueryName: queryName, Response: queryYml.Queries[queryName].ProtoWrapper.Response}) } sort.Slice(GrpcQueries, func(i, j int) bool { return GrpcQueries[i].QueryName > GrpcQueries[j].QueryName diff --git a/cmd/querygen/templates/grpc_template.tmpl b/cmd/querygen/templates/grpc_template.tmpl index 9007fb99e40..0b63645e478 100644 --- a/cmd/querygen/templates/grpc_template.tmpl +++ b/cmd/querygen/templates/grpc_template.tmpl @@ -24,7 +24,7 @@ var _ queryproto.QueryServer = Querier{} func (q Querier) {{.QueryName}}(grpcCtx context.Context, req *queryproto.{{.QueryName}}Request, -) (*queryproto.{{.QueryName}}Response, error) { +) ({{ if .Response }}{{.Response}}{{else}}*queryproto.{{.QueryName}}Response{{end}}, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") } diff --git a/cmd/querygen/templates/queryyml.go b/cmd/querygen/templates/queryyml.go index bff18c3d8ea..1eaa0ef6588 100644 --- a/cmd/querygen/templates/queryyml.go +++ b/cmd/querygen/templates/queryyml.go @@ -33,7 +33,7 @@ type YmlQueryDescriptor struct { type ProtoWrapperDescriptor struct { DefaultValues map[string]string `yaml:"default_values"` QueryFunc string `yaml:"query_func"` - Response string + Response string `yaml:"response"` } type CliDescriptor struct{} diff --git a/cosmwasm/Cargo.lock b/cosmwasm/Cargo.lock index a69bffc1b7a..c75bcbd938c 100644 --- a/cosmwasm/Cargo.lock +++ b/cosmwasm/Cargo.lock @@ -348,22 +348,14 @@ dependencies = [ name = "crosschain-registry" version = "0.1.0" dependencies = [ - "bech32", "cosmwasm-schema", "cosmwasm-std", - "crosschain-swaps", "cw-multi-test", "cw-storage-plus", "cw-utils", "cw2", - "hex", - "itertools", - "osmosis-std-derive", - "prost 0.11.6", - "schemars", + "registry", "serde", - "serde-json-wasm", - "sha2 0.10.6", "thiserror", ] @@ -379,10 +371,11 @@ dependencies = [ "cw-utils", "cw2", "enum-repr", + "itertools", "osmosis-std", - "osmosis-std-derive", "osmosis-testing", "prost 0.11.6", + "registry", "schemars", "serde", "serde-cw-value", @@ -1046,6 +1039,7 @@ dependencies = [ "cw-storage-plus", "cw-utils", "cw2", + "registry", "serde-json-wasm", "swaprouter", "thiserror", @@ -1200,6 +1194,26 @@ version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +[[package]] +name = "registry" +version = "0.1.0" +dependencies = [ + "bech32", + "cosmwasm-schema", + "cosmwasm-std", + "hex", + "itertools", + "osmosis-std", + "osmosis-std-derive", + "prost 0.11.6", + "schemars", + "serde", + "serde-cw-value", + "serde-json-wasm", + "sha2 0.10.6", + "thiserror", +] + [[package]] name = "rfc6979" version = "0.3.1" diff --git a/cosmwasm/Cargo.toml b/cosmwasm/Cargo.toml index f36b45d7267..710423c7b63 100644 --- a/cosmwasm/Cargo.toml +++ b/cosmwasm/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ 'contracts/*', + 'packages/*' ] [workspace.package] @@ -36,3 +37,4 @@ serde-json-wasm = "0.5.0" serde-cw-value = "0.7.0" bech32 = "0.9.1" cw-utils = "1.0.0" +itertools = "0.10" diff --git a/cosmwasm/contracts/crosschain-registry/Cargo.toml b/cosmwasm/contracts/crosschain-registry/Cargo.toml index f64bcbcf9fb..9bb9baab6ac 100644 --- a/cosmwasm/contracts/crosschain-registry/Cargo.toml +++ b/cosmwasm/contracts/crosschain-registry/Cargo.toml @@ -48,19 +48,10 @@ cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } cw-storage-plus = { workspace = true } cw2 = { workspace = true } -schemars = { workspace = true } serde = { workspace = true } thiserror = { workspace = true } -bech32 = { workspace = true } cw-utils = { workspace = true } -serde-json-wasm = { workspace = true } -sha2 = "0.10.6" -hex = "0.4.3" -prost = {version = "0.11.2", default-features = false, features = ["prost-derive"]} -osmosis-std-derive = "0.13.2" -itertools = "0.10.5" - -crosschain-swaps = { path = "../crosschain-swaps", features = ["imported"]} +registry = { path = "../../packages/registry"} [dev-dependencies] cw-multi-test = "0.16.2" diff --git a/cosmwasm/contracts/crosschain-registry/src/contract.rs b/cosmwasm/contracts/crosschain-registry/src/contract.rs index 9a0391b5517..49f4de35c40 100644 --- a/cosmwasm/contracts/crosschain-registry/src/contract.rs +++ b/cosmwasm/contracts/crosschain-registry/src/contract.rs @@ -6,7 +6,8 @@ use cw2::set_contract_version; use crate::error::ContractError; use crate::msg::{ExecuteMsg, GetAddressFromAliasResponse, InstantiateMsg, QueryMsg}; use crate::state::{Config, CONFIG, CONTRACT_ALIAS_MAP}; -use crate::{execute, query, Registries}; +use crate::{execute, query}; +use registry::Registry; // version info for migration const CONTRACT_NAME: &str = "crates.io:crosschain-registry"; @@ -60,8 +61,9 @@ pub fn execute( ExecuteMsg::UnwrapCoin { receiver, into_chain, + with_memo, } => { - let registries = Registries::new(deps.as_ref(), env.contract.address.to_string())?; + let registries = Registry::new(deps.as_ref(), env.contract.address.to_string())?; let coin = cw_utils::one_coin(&info)?; let transfer_msg = registries.unwrap_coin_into( coin, @@ -69,6 +71,7 @@ pub fn execute( into_chain.as_deref(), env.contract.address.to_string(), env.block.time, + with_memo, )?; deps.api.debug(&format!("transfer_msg: {transfer_msg:?}")); Ok(Response::new() @@ -113,6 +116,9 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { QueryMsg::GetDenomTrace { ibc_denom } => { to_binary(&query::query_denom_trace_from_ibc_denom(deps, ibc_denom)?) } + QueryMsg::GetChainNameFromBech32Prefix { prefix } => { + to_binary(&query::query_chain_name_from_bech32_prefix(deps, prefix)?) + } } } @@ -327,7 +333,7 @@ mod test { new_channel_id: None, }], }; - let result = execute(deps.as_mut(), mock_env(), info_creator.clone(), msg); + let result = execute(deps.as_mut(), mock_env(), info_creator, msg); assert!(result.is_ok()); // Retrieve osmo<>juno link again, but this time it should be enabled diff --git a/cosmwasm/contracts/crosschain-registry/src/error.rs b/cosmwasm/contracts/crosschain-registry/src/error.rs index 94bce3bfb84..18ad07f9b56 100644 --- a/cosmwasm/contracts/crosschain-registry/src/error.rs +++ b/cosmwasm/contracts/crosschain-registry/src/error.rs @@ -1,90 +1,7 @@ use cosmwasm_std::StdError; +use registry::RegistryError; use thiserror::Error; -#[derive(Error, Debug, PartialEq)] -pub enum RegistryError { - #[error("{0}")] - Std(#[from] StdError), - - // Validation errors - #[error("Invalid channel id: {0}")] - InvalidChannelId(String), - - #[error("error {action} {addr}")] - Bech32Error { - action: String, - addr: String, - #[source] - source: bech32::Error, - }, - - #[error("serialization error: {error}")] - SerialiaztionError { error: String }, - - #[error("denom {denom:?} is not an IBC denom")] - InvalidIBCDenom { denom: String }, - - #[error("No deom trace found for: {denom:?}")] - NoDenomTrace { denom: String }, - - #[error("Invalid denom trace: {error}")] - InvalidDenomTrace { error: String }, - - #[error("Invalid path {path:?} for denom {denom:?}")] - InvalidDenomTracePath { path: String, denom: String }, - - #[error("Invalid transfer port {port:?}")] - InvalidTransferPort { port: String }, - - #[error("Invalid multihop length {length:?}. Must be >={min}")] - InvalidMultiHopLengthMin { length: usize, min: usize }, - - #[error("Invalid multihop length {length:?}. Must be <={max}")] - InvalidMultiHopLengthMax { length: usize, max: usize }, - - #[error( - "receiver prefix for {receiver} must match the bech32 prefix of the destination chain {chain}" - )] - InvalidReceiverPrefix { receiver: String, chain: String }, - - // Registry loading errors - #[error("contract alias does not exist: {alias:?}")] - AliasDoesNotExist { alias: String }, - - #[error("no authorized address found for source chain: {source_chain:?}")] - ChainAuthorizedAddressDoesNotExist { source_chain: String }, - - #[error("chain channel link does not exist: {source_chain:?} -> {destination_chain:?}")] - ChainChannelLinkDoesNotExist { - source_chain: String, - destination_chain: String, - }, - - #[error("channel chain link does not exist: {channel_id:?} on {source_chain:?} -> chain")] - ChannelChainLinkDoesNotExist { - channel_id: String, - source_chain: String, - }, - - #[error("channel chain link does not exist: {channel_id:?} on {source_chain:?} -> chain")] - ChannelToChainChainLinkDoesNotExist { - channel_id: String, - source_chain: String, - }, - - #[error("native denom link does not exist: {native_denom:?}")] - NativeDenomLinkDoesNotExist { native_denom: String }, - - #[error("bech32 prefix does not exist for chain: {chain}")] - Bech32PrefixDoesNotExist { chain: String }, -} - -impl From for StdError { - fn from(e: RegistryError) -> Self { - StdError::generic_err(e.to_string()) - } -} - #[derive(Error, Debug, PartialEq)] pub enum ContractError { #[error("{0}")] diff --git a/cosmwasm/contracts/crosschain-registry/src/execute.rs b/cosmwasm/contracts/crosschain-registry/src/execute.rs index fa7acc9b7c3..84829b9720e 100644 --- a/cosmwasm/contracts/crosschain-registry/src/execute.rs +++ b/cosmwasm/contracts/crosschain-registry/src/execute.rs @@ -1,12 +1,13 @@ -use crate::error::RegistryError; use crate::helpers::*; use crate::state::{ - CHAIN_ADMIN_MAP, CHAIN_MAINTAINER_MAP, CHAIN_TO_BECH32_PREFIX_MAP, CHAIN_TO_CHAIN_CHANNEL_MAP, - CHANNEL_ON_CHAIN_CHAIN_MAP, CONTRACT_ALIAS_MAP, GLOBAL_ADMIN_MAP, + CHAIN_ADMIN_MAP, CHAIN_MAINTAINER_MAP, CHAIN_TO_BECH32_PREFIX_MAP, + CHAIN_TO_BECH32_PREFIX_REVERSE_MAP, CHAIN_TO_CHAIN_CHANNEL_MAP, CHANNEL_ON_CHAIN_CHAIN_MAP, + CONTRACT_ALIAS_MAP, GLOBAL_ADMIN_MAP, }; use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, DepsMut, Response}; use cw_storage_plus::Map; +use registry::RegistryError; use crate::ContractError; @@ -244,7 +245,7 @@ pub fn connection_operations( } response.clone().add_attribute( "change_connection", - format!("{}-{}", source_chain, destination_chain), + format!("{source_chain}-{destination_chain}"), ); } FullOperation::Remove => { @@ -260,7 +261,7 @@ pub fn connection_operations( .remove(deps.storage, (&chain_to_chain_map.value, &source_chain)); response.clone().add_attribute( "remove_connection", - format!("{}-{}", source_chain, destination_chain), + format!("{source_chain}-{destination_chain}"), ); } FullOperation::Enable => { @@ -288,7 +289,7 @@ pub fn connection_operations( )?; response.clone().add_attribute( "enable_connection", - format!("{}-{}", source_chain, destination_chain), + format!("{source_chain}-{destination_chain}"), ); } FullOperation::Disable => { @@ -316,7 +317,7 @@ pub fn connection_operations( )?; response.clone().add_attribute( "disable_connection", - format!("{}-{}", source_chain, destination_chain), + format!("{source_chain}-{destination_chain}"), ); } } @@ -329,7 +330,7 @@ pub fn connection_operations( pub struct ChainToBech32PrefixInput { pub operation: FullOperation, pub chain_name: String, - pub prefix: Option, + pub prefix: String, pub new_prefix: Option, } @@ -355,17 +356,20 @@ pub fn chain_to_prefix_operations( if CHAIN_TO_BECH32_PREFIX_MAP.has(deps.storage, &chain_name) { return Err(ContractError::AliasAlreadyExists { alias: chain_name }); } - let prefix = operation - .prefix - .clone() - .unwrap_or_default() - .to_string() - .to_lowercase(); + let prefix = operation.prefix.to_lowercase(); CHAIN_TO_BECH32_PREFIX_MAP.save( deps.storage, &chain_name, - &(prefix, true).into(), + &(prefix.clone(), true).into(), + )?; + + push_to_map_value( + deps.storage, + &CHAIN_TO_BECH32_PREFIX_REVERSE_MAP, + &prefix, + chain_name.clone(), )?; + response .clone() .add_attribute("set_chain_to_prefix", chain_name); @@ -379,17 +383,34 @@ pub fn chain_to_prefix_operations( let is_enabled = map_entry.enabled; + let old_prefix = operation.prefix.to_lowercase(); let new_prefix = operation .new_prefix - .clone() .unwrap_or_default() .to_string() .to_lowercase(); CHAIN_TO_BECH32_PREFIX_MAP.save( deps.storage, &chain_name, - &(new_prefix, is_enabled).into(), + &(new_prefix.clone(), is_enabled).into(), + )?; + + // Remove from the reverse map of the old prefix + remove_from_map_value( + deps.storage, + &CHAIN_TO_BECH32_PREFIX_REVERSE_MAP, + &old_prefix, + chain_name.clone(), + )?; + + // Add to the reverse map of the new prefix + push_to_map_value( + deps.storage, + &CHAIN_TO_BECH32_PREFIX_REVERSE_MAP, + &new_prefix, + chain_name.clone(), )?; + response .clone() .add_attribute("change_chain_to_prefix", chain_name); @@ -401,6 +422,16 @@ pub fn chain_to_prefix_operations( alias: chain_name.clone(), })?; CHAIN_TO_BECH32_PREFIX_MAP.remove(deps.storage, &chain_name); + + let old_prefix = operation.prefix.to_lowercase(); + // Remove from the reverse map of the old prefix + remove_from_map_value( + deps.storage, + &CHAIN_TO_BECH32_PREFIX_REVERSE_MAP, + &old_prefix, + chain_name.clone(), + )?; + response .clone() .add_attribute("remove_chain_to_prefix", chain_name); @@ -414,7 +445,14 @@ pub fn chain_to_prefix_operations( CHAIN_TO_BECH32_PREFIX_MAP.save( deps.storage, &chain_name, - &(map_entry.value, true).into(), + &(map_entry.value.clone(), true).into(), + )?; + // Add to the reverse map of the enabled prefix + push_to_map_value( + deps.storage, + &CHAIN_TO_BECH32_PREFIX_REVERSE_MAP, + &map_entry.value, + chain_name.clone(), )?; response .clone() @@ -429,8 +467,16 @@ pub fn chain_to_prefix_operations( CHAIN_TO_BECH32_PREFIX_MAP.save( deps.storage, &chain_name, - &(map_entry.value, false).into(), + &(map_entry.value.clone(), false).into(), + )?; + // Remove from the reverse map of the disabled prefix + remove_from_map_value( + deps.storage, + &CHAIN_TO_BECH32_PREFIX_REVERSE_MAP, + &map_entry.value, + chain_name.clone(), )?; + response .clone() .add_attribute("disable_chain_to_prefix", chain_name); @@ -496,10 +542,9 @@ pub fn authorized_address_operations( } address_map.save(deps.storage, &source_chain, &addr)?; - response.clone().add_attribute( - "set_authorized_address", - format!("{}-{}", source_chain, addr), - ); + response + .clone() + .add_attribute("set_authorized_address", format!("{source_chain}-{addr}")); } Operation::Change => { address_map.load(deps.storage, &source_chain).map_err(|_| { @@ -514,7 +559,7 @@ pub fn authorized_address_operations( address_map.save(deps.storage, &source_chain, &new_addr)?; response.clone().add_attribute( "change_authorized_address", - format!("{}-{}", source_chain, addr), + format!("{source_chain}-{addr}"), ); } Operation::Remove => { @@ -527,7 +572,7 @@ pub fn authorized_address_operations( address_map.remove(deps.storage, &source_chain); response.clone().add_attribute( "remove_authorized_address", - format!("{}-{}", source_chain, addr), + format!("{source_chain}-{addr}"), ); } } @@ -564,7 +609,7 @@ mod tests { }; let info = mock_info(CREATOR_ADDRESS, &[]); - let res = contract::execute(deps.as_mut(), mock_env(), info.clone(), msg.clone()).unwrap(); + let res = contract::execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); assert_eq!(0, res.messages.len()); assert_eq!( CONTRACT_ALIAS_MAP @@ -582,8 +627,7 @@ mod tests { new_alias: None, }], }; - let res = - contract::execute(deps.as_mut(), mock_env(), info.clone(), msg.clone()).unwrap_err(); + let res = contract::execute(deps.as_mut(), mock_env(), info, msg).unwrap_err(); assert_eq!(res, ContractError::AliasAlreadyExists { alias }); // Verify that the alias was not updated @@ -604,13 +648,7 @@ mod tests { }], }; let unauthorized_info = mock_info(UNAUTHORIZED_ADDRESS, &[]); - let res = contract::execute( - deps.as_mut(), - mock_env(), - unauthorized_info.clone(), - msg.clone(), - ) - .unwrap_err(); + let res = contract::execute(deps.as_mut(), mock_env(), unauthorized_info, msg).unwrap_err(); assert_eq!(res, ContractError::Unauthorized {}); // Verify that the new alias was not set @@ -666,10 +704,8 @@ mod tests { // Verify that the contract alias has changed from "swap_router" to "new_swap_router" assert_eq!( - CONTRACT_ALIAS_MAP - .load(&deps.storage, &new_alias.clone()) - .unwrap(), - address.clone() + CONTRACT_ALIAS_MAP.load(&deps.storage, &new_alias).unwrap(), + address ); // Attempt to change an alias that does not exist @@ -681,12 +717,8 @@ mod tests { new_alias: Some(new_alias.clone()), }], }; - let invalid_alias_result = contract::execute( - deps.as_mut(), - mock_env(), - creator_info.clone(), - invalid_alias_msg, - ); + let invalid_alias_result = + contract::execute(deps.as_mut(), mock_env(), creator_info, invalid_alias_msg); let expected_error = ContractError::from(RegistryError::AliasDoesNotExist { alias }); assert_eq!(invalid_alias_result.unwrap_err(), expected_error); @@ -694,7 +726,7 @@ mod tests { let unauthorized_alias_msg = ExecuteMsg::ModifyContractAlias { operations: vec![ContractAliasInput { operation: Operation::Change, - alias: new_alias.clone(), + alias: new_alias, address: None, new_alias: Some(new_alias_unauthorized.clone()), }], @@ -702,7 +734,7 @@ mod tests { let unauthorized_alias_result = contract::execute( deps.as_mut(), mock_env(), - external_unauthorized_info.clone(), + external_unauthorized_info, unauthorized_alias_msg, ); let expected_error = ContractError::Unauthorized {}; @@ -749,7 +781,7 @@ mod tests { deps.as_mut(), mock_env(), creator_info.clone(), - remove_alias_msg.clone(), + remove_alias_msg, ) .unwrap(); @@ -790,20 +822,14 @@ mod tests { new_alias: None, }], }; - contract::execute( - deps.as_mut(), - mock_env(), - creator_info.clone(), - reset_alias_msg, - ) - .unwrap(); + contract::execute(deps.as_mut(), mock_env(), creator_info, reset_alias_msg).unwrap(); // Attempt to remove an alias with an unauthorized address let unauthorized_remove_msg = ExecuteMsg::ModifyContractAlias { operations: vec![ContractAliasInput { operation: Operation::Remove, - alias: alias.clone(), - address: Some(address.clone()), + alias: alias, + address: Some(address), new_alias: None, }], }; @@ -811,7 +837,7 @@ mod tests { let result = contract::execute( deps.as_mut(), mock_env(), - unauthorized_info.clone(), + unauthorized_info, unauthorized_remove_msg, ); @@ -868,7 +894,7 @@ mod tests { }], }; let info_creator = mock_info(CREATOR_ADDRESS, &[]); - let result = contract::execute(deps.as_mut(), mock_env(), info_creator.clone(), msg); + let result = contract::execute(deps.as_mut(), mock_env(), info_creator, msg); assert!(result.is_err()); let expected_error = ContractError::ChainToChainChannelLinkAlreadyExists { @@ -902,12 +928,7 @@ mod tests { }], }; let info_unauthorized = mock_info(UNAUTHORIZED_ADDRESS, &[]); - let result = contract::execute( - deps.as_mut(), - mock_env(), - info_unauthorized.clone(), - msg.clone(), - ); + let result = contract::execute(deps.as_mut(), mock_env(), info_unauthorized, msg.clone()); assert!(result.is_err()); let expected_error = ContractError::Unauthorized {}; @@ -981,8 +1002,8 @@ mod tests { let result = contract::execute( deps.as_mut(), mock_env(), - chain_admin_and_maintainer_info.clone(), - msg.clone(), + chain_admin_and_maintainer_info, + msg, ); assert!(result.is_err()); @@ -1002,12 +1023,7 @@ mod tests { new_channel_id: None, }], }; - let result = contract::execute( - deps.as_mut(), - mock_env(), - chain_admin_info.clone(), - msg.clone(), - ); + let result = contract::execute(deps.as_mut(), mock_env(), chain_admin_info, msg); assert!(result.is_err()); let expected_error = ContractError::Unauthorized {}; @@ -1092,7 +1108,7 @@ mod tests { new_channel_id: None, }], }; - let result = contract::execute(deps.as_mut(), mock_env(), info_creator.clone(), msg); + let result = contract::execute(deps.as_mut(), mock_env(), info_creator, msg); assert!(result.is_ok()); // Verify that channel-150 on osmosis is linked to regen @@ -1116,12 +1132,7 @@ mod tests { }], }; let info_unauthorized = mock_info(UNAUTHORIZED_ADDRESS, &[]); - let result = contract::execute( - deps.as_mut(), - mock_env(), - info_unauthorized.clone(), - msg.clone(), - ); + let result = contract::execute(deps.as_mut(), mock_env(), info_unauthorized, msg.clone()); assert!(result.is_err()); let expected_error = ContractError::Unauthorized {}; @@ -1172,12 +1183,7 @@ mod tests { new_channel_id: Some("channel-4".to_string()), }], }; - let result = contract::execute( - deps.as_mut(), - mock_env(), - chain_maintainer_info.clone(), - msg.clone(), - ); + let result = contract::execute(deps.as_mut(), mock_env(), chain_maintainer_info, msg); assert!(result.is_err()); let expected_error = ContractError::Unauthorized {}; @@ -1256,12 +1262,7 @@ mod tests { new_channel_id: None, }], }; - let result = contract::execute( - deps.as_mut(), - mock_env(), - chain_maintainer_info.clone(), - msg.clone(), - ); + let result = contract::execute(deps.as_mut(), mock_env(), chain_maintainer_info, msg); assert!(result.is_err()); let expected_error = ContractError::Unauthorized {}; @@ -1278,18 +1279,121 @@ mod tests { operations: vec![ChainToBech32PrefixInput { operation: FullOperation::Set, chain_name: "OSMOSIS".to_string(), - prefix: Some("OSMO".to_string()), + prefix: "OSMO".to_string(), new_prefix: None, }], }; let info = mock_info(CREATOR_ADDRESS, &[]); - contract::execute(deps.as_mut(), mock_env(), info, msg).unwrap(); + contract::execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); + + assert_eq!( + CHAIN_TO_BECH32_PREFIX_MAP + .load(&deps.storage, "osmosis") + .unwrap(), + ("osmo", true).into() + ); + assert_eq!( + CHAIN_TO_BECH32_PREFIX_REVERSE_MAP + .load(&deps.storage, "osmo") + .unwrap(), + vec!["osmosis"] + ); + + // Set another chain with the same prefix + let msg = ExecuteMsg::ModifyBech32Prefixes { + operations: vec![ChainToBech32PrefixInput { + operation: FullOperation::Set, + chain_name: "ISMISIS".to_string(), + prefix: "OSMO".to_string(), + new_prefix: None, + }], + }; + contract::execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); + + assert_eq!( + CHAIN_TO_BECH32_PREFIX_MAP + .load(&deps.storage, "ismisis") + .unwrap(), + ("osmo", true).into() + ); + assert_eq!( + CHAIN_TO_BECH32_PREFIX_REVERSE_MAP + .load(&deps.storage, "osmo") + .unwrap(), + vec!["osmosis", "ismisis"] + ); + + // Set another chain with the same prefix + let msg = ExecuteMsg::ModifyBech32Prefixes { + operations: vec![ChainToBech32PrefixInput { + operation: FullOperation::Disable, + chain_name: "OSMOSIS".to_string(), + prefix: "OSMO".to_string(), + new_prefix: None, + }], + }; + contract::execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); + assert_eq!( + CHAIN_TO_BECH32_PREFIX_MAP + .load(&deps.storage, "osmosis") + .unwrap(), + ("osmo", false).into() + ); + assert_eq!( + CHAIN_TO_BECH32_PREFIX_REVERSE_MAP + .load(&deps.storage, "osmo") + .unwrap(), + vec!["ismisis"] + ); + // Set another chain with the same prefix + let msg = ExecuteMsg::ModifyBech32Prefixes { + operations: vec![ChainToBech32PrefixInput { + operation: FullOperation::Enable, + chain_name: "OSMOSIS".to_string(), + prefix: "OSMO".to_string(), + new_prefix: None, + }], + }; + contract::execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); assert_eq!( CHAIN_TO_BECH32_PREFIX_MAP .load(&deps.storage, "osmosis") .unwrap(), ("osmo", true).into() ); + assert_eq!( + CHAIN_TO_BECH32_PREFIX_REVERSE_MAP + .load(&deps.storage, "osmo") + .unwrap(), + vec!["ismisis", "osmosis"] + ); + + // Set another chain with the same prefix + let msg = ExecuteMsg::ModifyBech32Prefixes { + operations: vec![ChainToBech32PrefixInput { + operation: FullOperation::Remove, + chain_name: "OSMOSIS".to_string(), + prefix: "OSMO".to_string(), + new_prefix: None, + }], + }; + contract::execute(deps.as_mut(), mock_env(), info, msg).unwrap(); + assert_eq!( + CHAIN_TO_BECH32_PREFIX_MAP + .load(&deps.storage, "ismisis") + .unwrap(), + ("osmo", true).into() + ); + assert_eq!( + CHAIN_TO_BECH32_PREFIX_REVERSE_MAP + .load(&deps.storage, "osmo") + .unwrap(), + vec!["ismisis"] + ); + + CHAIN_TO_BECH32_PREFIX_MAP + .load(&deps.storage, "osmosis") + .unwrap_err(); } } diff --git a/cosmwasm/contracts/crosschain-registry/src/exports.rs b/cosmwasm/contracts/crosschain-registry/src/exports.rs index d040c31a619..8b137891791 100644 --- a/cosmwasm/contracts/crosschain-registry/src/exports.rs +++ b/cosmwasm/contracts/crosschain-registry/src/exports.rs @@ -1,439 +1 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Coin, Deps, Timestamp}; -use crosschain_swaps::ibc::MsgTransfer; -use itertools::Itertools; -pub use crate::error::RegistryError; -use crate::{ - helpers::{hash_denom_trace, DenomTrace, QueryDenomTraceRequest}, - msg::QueryMsg, -}; -use std::convert::AsRef; - -// IBC transfer port -const TRANSFER_PORT: &str = "transfer"; -// IBC timeout -pub const PACKET_LIFETIME: u64 = 604_800u64; // One week in seconds - -#[cw_serde] -pub struct Chain(String); -#[cw_serde] -pub struct ChannelId(String); - -impl ChannelId { - pub fn new(channel_id: &str) -> Result { - if !ChannelId::validate(channel_id) { - return Err(RegistryError::InvalidChannelId(channel_id.to_string())); - } - Ok(Self(channel_id.to_string())) - } - - pub fn validate(channel_id: &str) -> bool { - if !channel_id.starts_with("channel-") { - return false; - } - // Check that what comes after "channel-" is a valid int - let channel_num = &channel_id[8..]; - if channel_num.parse::().is_err() { - return false; - } - true - } -} - -impl AsRef for ChannelId { - fn as_ref(&self) -> &str { - &self.0 - } -} - -impl AsRef for Chain { - fn as_ref(&self) -> &str { - &self.0 - } -} - -#[cw_serde] -pub struct ForwardingMemo { - pub receiver: String, - pub port: String, - pub channel: ChannelId, - #[serde(skip_serializing_if = "Option::is_none")] - pub next: Option>, -} - -#[cw_serde] -pub struct Memo { - forward: ForwardingMemo, -} - -// We will assume here that chains use the standard ibc-go formats. This is ok -// because we will be checking the channels in the registry and failing if they -// are not valid. We also need to enforce that all ports are explicitly "transfer" -#[cw_serde] -pub struct MultiHopDenom { - pub local_denom: String, - pub on: Chain, - pub via: Option, // This is optional because native tokens have no channel -} - -pub struct Registries<'a> { - pub deps: Deps<'a>, - pub registry_contract: String, -} - -impl<'a> Registries<'a> { - pub fn new(deps: Deps<'a>, registry_contract: String) -> Result { - deps.api.addr_validate(®istry_contract)?; - Ok(Self { - deps, - registry_contract, - }) - } - - #[allow(dead_code)] - fn default(deps: Deps<'a>) -> Self { - Self { - deps, - registry_contract: "todo: hard code the addr here".to_string(), - } - } - - /// Get a contract address by its alias - /// Example: get_contract("registries") -> "osmo1..." - pub fn get_contract(self, alias: String) -> Result { - self.deps - .querier - .query_wasm_smart( - &self.registry_contract, - &QueryMsg::GetAddressFromAlias { - contract_alias: alias.clone(), - }, - ) - .map_err(|_e| RegistryError::AliasDoesNotExist { alias }) - } - - /// Get a the name of the chain connected via channel `via_channel` on chain `on_chain`. - /// Example: get_connected_chain("osmosis", "channel-42") -> "juno" - pub fn get_connected_chain( - &self, - on_chain: &str, - via_channel: &str, - ) -> Result { - self.deps - .querier - .query_wasm_smart( - &self.registry_contract, - &QueryMsg::GetDestinationChainFromSourceChainViaChannel { - on_chain: on_chain.to_string(), - via_channel: via_channel.to_string(), - }, - ) - .map_err(|_e| RegistryError::ChannelToChainChainLinkDoesNotExist { - channel_id: via_channel.to_string(), - source_chain: on_chain.to_string(), - }) - } - - /// Get the channel id for the channel connecting chain `on_chain` to chain `for_chain`. - /// Example: get_channel("osmosis", "juno") -> "channel-42" - /// Example: get_channel("juno", "osmosis") -> "channel-0" - pub fn get_channel(&self, for_chain: &str, on_chain: &str) -> Result { - self.deps - .querier - .query_wasm_smart( - &self.registry_contract, - &QueryMsg::GetChannelFromChainPair { - source_chain: on_chain.to_string(), - destination_chain: for_chain.to_string(), - }, - ) - .map_err(|_e| RegistryError::ChainChannelLinkDoesNotExist { - source_chain: on_chain.to_string(), - destination_chain: for_chain.to_string(), - }) - } - - /// Re-encodes the bech32 address for the receiving chain - /// Example: encode_addr_for_chain("osmo1...", "juno") -> "juno1..." - pub fn encode_addr_for_chain(&self, addr: &str, chain: &str) -> Result { - let (_, data, variant) = bech32::decode(addr).map_err(|e| RegistryError::Bech32Error { - action: "decoding".into(), - addr: addr.into(), - source: e, - })?; - - let response: String = self.deps.querier.query_wasm_smart( - &self.registry_contract, - &QueryMsg::GetBech32PrefixFromChainName { - chain_name: chain.to_string(), - }, - )?; - - let receiver = - bech32::encode(&response, data, variant).map_err(|e| RegistryError::Bech32Error { - action: "encoding".into(), - addr: addr.into(), - source: e, - })?; - - Ok(receiver) - } - - /// Get the bech32 prefix for the given chain - /// Example: get_bech32_prefix("osmosis") -> "osmo" - pub fn get_bech32_prefix(&self, chain: &str) -> Result { - self.deps - .api - .debug(&format!("Getting prefix for chain: {chain}")); - let prefix: String = self - .deps - .querier - .query_wasm_smart( - &self.registry_contract, - &QueryMsg::GetBech32PrefixFromChainName { - chain_name: chain.to_string(), - }, - ) - .map_err(|e| { - self.deps.api.debug(&format!("Got error: {e}")); - RegistryError::Bech32PrefixDoesNotExist { - chain: chain.into(), - } - })?; - if prefix.is_empty() { - return Err(RegistryError::Bech32PrefixDoesNotExist { - chain: chain.into(), - }); - } - Ok(prefix) - } - - /// Returns the IBC path the denom has taken to get to the current chain - /// Example: unwrap_denom_path("ibc/0A...") -> [{"local_denom":"ibc/0A","on":"osmosis","via":"channel-17"},{"local_denom":"ibc/1B","on":"middle_chain","via":"channel-75"},{"local_denom":"token0","on":"source_chain","via":null} - pub fn unwrap_denom_path(&self, denom: &str) -> Result, RegistryError> { - self.deps.api.debug(&format!("Unwrapping denom {denom}")); - // Check that the denom is an IBC denom - if !denom.starts_with("ibc/") { - return Err(RegistryError::InvalidIBCDenom { - denom: denom.into(), - }); - } - - // Get the denom trace - let res = QueryDenomTraceRequest { - hash: denom.to_string(), - } - .query(&self.deps.querier)?; - - let DenomTrace { path, base_denom } = match res.denom_trace { - Some(denom_trace) => Ok(denom_trace), - None => Err(RegistryError::NoDenomTrace { - denom: denom.into(), - }), - }?; - - self.deps - .api - .debug(&format!("procesing denom trace {path}")); - // Let's iterate over the parts of the denom trace and extract the - // chain/channels into a more useful structure: MultiHopDenom - let mut hops: Vec = vec![]; - let mut current_chain = "osmosis".to_string(); // The initial chain is always osmosis - let mut rest: &str = &path; - let parts = path.split('/'); - - for chunk in &parts.chunks(2) { - let Some((port, channel)) = chunk.take(2).collect_tuple() else { - return Err(RegistryError::InvalidDenomTracePath{ path: path.clone(), denom: denom.into() }); - }; - - // Check that the port is "transfer" - if port != TRANSFER_PORT { - return Err(RegistryError::InvalidTransferPort { port: port.into() }); - } - - // Check that the channel is valid - let full_trace = rest.to_owned() + "/" + &base_denom; - hops.push(MultiHopDenom { - local_denom: hash_denom_trace(&full_trace), - on: Chain(current_chain.clone().to_string()), - via: Some(ChannelId::new(channel)?), - }); - - current_chain = self.get_connected_chain(¤t_chain, channel)?; - rest = rest - .trim_start_matches(&format!("{port}/{channel}")) - .trim_start_matches('/'); // hops other than first and last will have this slash - } - - hops.push(MultiHopDenom { - local_denom: base_denom, - on: Chain(current_chain), - via: None, - }); - - Ok(hops) - } - - /// Returns an IBC MsgTransfer that with a packet forward middleware memo - /// that will send the coin back to its original chain and then to the - /// receiver in `into_chain`. - /// - /// If the receiver `into_chain` is not specified, we assume the receiver is - /// the current chain (where the the registries are hosted and the denom - /// original denom exists) - /// - /// `own_addr` must the the address of the contract that is calling this - /// function. - /// - /// `block_time` is the current block time. This is needed to calculate the - /// timeout timestamp. - pub fn unwrap_coin_into( - &self, - coin: Coin, - receiver: String, - into_chain: Option<&str>, - own_addr: String, - block_time: Timestamp, - ) -> Result { - let path = self.unwrap_denom_path(&coin.denom)?; - self.deps - .api - .debug(&format!("Generating unwrap transfer message for: {path:?}")); - if path.len() < 2 { - return Err(RegistryError::InvalidMultiHopLengthMin { - length: path.len(), - min: 2, - }); - } - - let MultiHopDenom { - local_denom: _, - on: first_chain, - via: first_channel, - } = path - .first() - .ok_or_else(|| RegistryError::InvalidDenomTracePath { - path: format!("{:?}", path.clone()), - denom: coin.denom.clone(), - })?; - - let first_channel = match first_channel { - Some(channel) => Ok(channel), - None => Err(RegistryError::InvalidDenomTrace { - error: "First hop must contain a channel".to_string(), - }), - }?; - - // default the receiver chain to the first chain if it isn't provided - let receiver_chain = match into_chain { - Some(chain) => chain, - None => first_chain.as_ref(), - }; - let receiver_chain: &str = &receiver_chain.to_lowercase(); - - // validate the receiver matches the chain - let receiver_prefix = self.get_bech32_prefix(receiver_chain)?; - if receiver[..receiver_prefix.len()] != receiver_prefix { - return Err(RegistryError::InvalidReceiverPrefix { - receiver, - chain: receiver_chain.into(), - }); - } - - let ts = block_time.plus_seconds(PACKET_LIFETIME); - let path_iter = path.iter().skip(1); - - let mut next: Option> = None; - let mut prev_chain: &str = receiver_chain; - - for hop in path_iter.rev() { - // If the last hop is the same as the receiver chain, we don't need - // to forward anymore - if hop.via.is_none() && hop.on.as_ref() == receiver_chain { - continue; - } - - // To unwrap we use the channel through which the token came, but once on the native - // chain, we need to get the channel that connects that chain to the receiver. - let channel = match &hop.via { - Some(channel) => channel.to_owned(), - None => ChannelId(self.get_channel(prev_chain, hop.on.as_ref())?), - }; - - next = Some(Box::new(Memo { - forward: ForwardingMemo { - receiver: self.encode_addr_for_chain(&receiver, prev_chain)?, - port: TRANSFER_PORT.to_string(), - channel, - next, - }, - })); - prev_chain = hop.on.as_ref(); - } - - let memo = - serde_json_wasm::to_string(&next).map_err(|e| RegistryError::SerialiaztionError { - error: e.to_string(), - })?; - - // encode the receiver address for the first chain - let first_receiver = self.encode_addr_for_chain(&receiver, first_chain.as_ref())?; - - Ok(MsgTransfer { - source_port: TRANSFER_PORT.to_string(), - source_channel: first_channel.to_owned().as_ref().to_string(), - token: Some(coin.into()), - sender: own_addr, - receiver: first_receiver, - timeout_height: None, - timeout_timestamp: Some(ts.nanos()), - memo, - }) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_channel_id() { - assert!(ChannelId::validate("channel-0")); - assert!(ChannelId::validate("channel-1")); - assert!(ChannelId::validate("channel-1234567890")); - assert!(!ChannelId::validate("channel-")); - assert!(!ChannelId::validate("channel-abc")); - assert!(!ChannelId::validate("channel-1234567890a")); - assert!(!ChannelId::validate("channel-1234567890-")); - assert!(!ChannelId::validate("channel-1234567890-abc")); - assert!(!ChannelId::validate("channel-1234567890-1234567890")); - } - - #[test] - fn test_forwarding_memo() { - let memo = Memo { - forward: ForwardingMemo { - receiver: "receiver".to_string(), - port: "port".to_string(), - channel: ChannelId::new("channel-0").unwrap(), - next: Some(Box::new(Memo { - forward: ForwardingMemo { - receiver: "receiver2".to_string(), - port: "port2".to_string(), - channel: ChannelId::new("channel-1").unwrap(), - next: None, - }, - })), - }, - }; - let encoded = serde_json_wasm::to_string(&memo).unwrap(); - let decoded: Memo = serde_json_wasm::from_str(&encoded).unwrap(); - assert_eq!(memo, decoded); - assert_eq!( - encoded, - r#"{"forward":{"receiver":"receiver","port":"port","channel":"channel-0","next":{"forward":{"receiver":"receiver2","port":"port2","channel":"channel-1"}}}}"# - ) - } -} diff --git a/cosmwasm/contracts/crosschain-registry/src/helpers.rs b/cosmwasm/contracts/crosschain-registry/src/helpers.rs index dbaff4f7331..9b3a6052583 100644 --- a/cosmwasm/contracts/crosschain-registry/src/helpers.rs +++ b/cosmwasm/contracts/crosschain-registry/src/helpers.rs @@ -1,6 +1,5 @@ use cosmwasm_std::{Addr, Deps}; -use osmosis_std_derive::CosmwasmExt; -use sha2::{Digest, Sha256}; +use cw_storage_plus::Map; use crate::execute::{FullOperation, Permission}; use crate::state::{CHAIN_ADMIN_MAP, CHAIN_MAINTAINER_MAP, CONFIG, GLOBAL_ADMIN_MAP}; @@ -128,6 +127,51 @@ pub fn check_is_chain_maintainer( Err(ContractError::Unauthorized {}) } +// Helper functions to deal with Vec values in cosmwasm maps +pub fn push_to_map_value<'a, K, T>( + storage: &mut dyn cosmwasm_std::Storage, + map: &Map<'a, K, Vec>, + key: K, + value: T, +) -> Result<(), ContractError> +where + T: serde::Serialize + serde::de::DeserializeOwned + Clone, + K: cw_storage_plus::PrimaryKey<'a>, +{ + map.update(storage, key, |existing| -> Result<_, ContractError> { + match existing { + Some(mut v) => { + v.push(value); + Ok(v) + } + None => Ok(vec![value]), + } + })?; + Ok(()) +} + +pub fn remove_from_map_value<'a, K, T>( + storage: &mut dyn cosmwasm_std::Storage, + map: &Map<'a, K, Vec>, + key: K, + value: T, +) -> Result<(), ContractError> +where + T: serde::Serialize + serde::de::DeserializeOwned + Clone + PartialEq, + K: cw_storage_plus::PrimaryKey<'a>, +{ + map.update(storage, key, |existing| -> Result<_, ContractError> { + match existing { + Some(mut v) => { + v.retain(|val| *val != value); + Ok(v) + } + None => Ok(vec![value]), + } + })?; + Ok(()) +} + #[cfg(test)] pub mod test { use crate::execute; @@ -193,7 +237,7 @@ pub mod test { ], }; let chain_admin_info = mock_info(CHAIN_ADMIN, &[]); - contract::execute(deps.branch(), mock_env(), chain_admin_info.clone(), msg).unwrap(); + contract::execute(deps.branch(), mock_env(), chain_admin_info, msg).unwrap(); // Set the CHAIN_ADMIN address as the juno chain maintainer // This is used to ensure that permissions don't bleed over from one chain to another @@ -270,70 +314,8 @@ pub mod test { new_channel_id: None, }, ]; - execute::connection_operations(deps.as_mut(), info.sender.clone(), operations)?; + execute::connection_operations(deps.as_mut(), info.sender, operations)?; Ok(deps) } } - -// takes a transfer message and returns ibc/ -pub fn hash_denom_trace(unwrapped: &str) -> String { - let mut hasher = Sha256::new(); - hasher.update(unwrapped.as_bytes()); - let result = hasher.finalize(); - let hash = hex::encode(result); - format!("ibc/{}", hash.to_uppercase()) -} - -// DenomTrace query message definition. -#[derive( - Clone, - PartialEq, - Eq, - ::prost::Message, - serde::Serialize, - serde::Deserialize, - schemars::JsonSchema, - CosmwasmExt, -)] -#[proto_message(type_url = "/ibc.applications.transfer.v1.QueryDenomTraceRequest")] -#[proto_query( - path = "/ibc.applications.transfer.v1.Query/DenomTrace", - response_type = QueryDenomTraceResponse -)] -pub struct QueryDenomTraceRequest { - #[prost(string, tag = "1")] - pub hash: ::prost::alloc::string::String, -} - -#[derive( - Clone, - PartialEq, - Eq, - ::prost::Message, - serde::Serialize, - serde::Deserialize, - schemars::JsonSchema, - CosmwasmExt, -)] -#[proto_message(type_url = "/ibc.applications.transfer.v1.QueryDenomTraceResponse")] -pub struct QueryDenomTraceResponse { - #[prost(message, optional, tag = "1")] - pub denom_trace: Option, -} - -#[derive( - Clone, - PartialEq, - Eq, - ::prost::Message, - serde::Serialize, - serde::Deserialize, - schemars::JsonSchema, -)] -pub struct DenomTrace { - #[prost(string, tag = "1")] - pub path: ::prost::alloc::string::String, - #[prost(string, tag = "2")] - pub base_denom: ::prost::alloc::string::String, -} diff --git a/cosmwasm/contracts/crosschain-registry/src/lib.rs b/cosmwasm/contracts/crosschain-registry/src/lib.rs index 0eedaa750c7..ac94946c4d1 100644 --- a/cosmwasm/contracts/crosschain-registry/src/lib.rs +++ b/cosmwasm/contracts/crosschain-registry/src/lib.rs @@ -8,4 +8,3 @@ pub mod query; pub mod state; pub use crate::error::ContractError; -pub use crate::exports::Registries; diff --git a/cosmwasm/contracts/crosschain-registry/src/msg.rs b/cosmwasm/contracts/crosschain-registry/src/msg.rs index 22b2b70400e..c7de423f2ee 100644 --- a/cosmwasm/contracts/crosschain-registry/src/msg.rs +++ b/cosmwasm/contracts/crosschain-registry/src/msg.rs @@ -1,7 +1,6 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_schema::cw_serde; use crate::execute; -use crate::exports::MultiHopDenom; #[cw_serde] pub struct InstantiateMsg { @@ -33,60 +32,15 @@ pub enum ExecuteMsg { UnwrapCoin { receiver: String, into_chain: Option, + #[serde(default = "String::new")] + with_memo: String, }, } -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - #[returns(GetAddressFromAliasResponse)] - GetAddressFromAlias { contract_alias: String }, - - #[returns(GetChannelFromChainPairResponse)] - GetChannelFromChainPair { - source_chain: String, - destination_chain: String, - }, - - #[returns(GetDestinationChainFromSourceChainViaChannelResponse)] - GetDestinationChainFromSourceChainViaChannel { - on_chain: String, - via_channel: String, - }, - - #[returns(QueryGetBech32PrefixFromChainNameResponse)] - GetBech32PrefixFromChainName { chain_name: String }, - - #[returns(crate::helpers::QueryDenomTraceResponse)] - GetDenomTrace { ibc_denom: String }, -} - -// Response for GetAddressFromAlias query -#[cw_serde] -pub struct GetAddressFromAliasResponse { - pub address: String, -} - -// Response for GetChannelFromChainPair query -#[cw_serde] -pub struct GetChannelFromChainPairResponse { - pub channel_id: String, -} - -// Response for GetDestinationChainFromSourceChainViaChannel query -#[cw_serde] -pub struct GetDestinationChainFromSourceChainViaChannelResponse { - pub destination_chain: String, -} - -// Response for GetBech32PrefixFromChainName query -#[cw_serde] -pub struct QueryGetBech32PrefixFromChainNameResponse { - pub bech32_prefix: String, -} - -// Response for UnwrapDenom query -#[cw_serde] -pub struct UnwrapDenomResponse { - pub hops: MultiHopDenom, -} +// Import the queries from the package to avoid cyclic dependencies +pub use registry::msg::QueryMsg; +pub use registry::msg::{ + GetAddressFromAliasResponse, GetChannelFromChainPairResponse, + GetDestinationChainFromSourceChainViaChannelResponse, + QueryGetBech32PrefixFromChainNameResponse, +}; diff --git a/cosmwasm/contracts/crosschain-registry/src/query.rs b/cosmwasm/contracts/crosschain-registry/src/query.rs index 6c0448e33f2..d507d62837c 100644 --- a/cosmwasm/contracts/crosschain-registry/src/query.rs +++ b/cosmwasm/contracts/crosschain-registry/src/query.rs @@ -1,9 +1,10 @@ -use crate::helpers::*; use crate::state::{ - CHAIN_TO_BECH32_PREFIX_MAP, CHAIN_TO_CHAIN_CHANNEL_MAP, CHANNEL_ON_CHAIN_CHAIN_MAP, + CHAIN_TO_BECH32_PREFIX_MAP, CHAIN_TO_BECH32_PREFIX_REVERSE_MAP, CHAIN_TO_CHAIN_CHANNEL_MAP, + CHANNEL_ON_CHAIN_CHAIN_MAP, }; use cosmwasm_std::{Deps, StdError}; +use registry::proto::{DenomTrace, QueryDenomTraceRequest}; pub fn query_denom_trace_from_ibc_denom( deps: Deps, @@ -25,14 +26,29 @@ pub fn query_bech32_prefix_from_chain_name( if !chain_to_bech32_prefix_map.enabled { return Err(StdError::generic_err(format!( - "Chain {} to bech32 prefix mapping is disabled", - chain_name + "Chain {chain_name} to bech32 prefix mapping is disabled" ))); } Ok(chain_to_bech32_prefix_map.value) } +pub fn query_chain_name_from_bech32_prefix(deps: Deps, prefix: String) -> Result { + let chains = CHAIN_TO_BECH32_PREFIX_REVERSE_MAP.load(deps.storage, &prefix)?; + if chains.len() > 1 { + return Err(StdError::generic_err(format!( + "Bech32 prefix {prefix} is not unique" + ))); + } + + match chains.first() { + Some(chain) => Ok(chain.to_string()), + None => Err(StdError::generic_err(format!( + "Bech32 prefix {prefix} is not found" + ))), + } +} + pub fn query_channel_from_chain_pair( deps: Deps, source_chain: String, @@ -48,8 +64,7 @@ pub fn query_channel_from_chain_pair( if !channel.enabled { return Err(StdError::generic_err(format!( - "Channel from {} to {} mapping is disabled", - source_chain, destination_chain + "Channel from {source_chain} to {destination_chain} mapping is disabled" ))); } @@ -68,8 +83,7 @@ pub fn query_chain_from_channel_chain_pair( if !chain.enabled { return Err(StdError::generic_err(format!( - "Destination chain from channel {} on source chain {} mapping is disabled", - on_chain, via_channel + "Destination chain from channel {on_chain} on source chain {via_channel} mapping is disabled" ))); } diff --git a/cosmwasm/contracts/crosschain-registry/src/state.rs b/cosmwasm/contracts/crosschain-registry/src/state.rs index 503217c077b..1fafa24192c 100644 --- a/cosmwasm/contracts/crosschain-registry/src/state.rs +++ b/cosmwasm/contracts/crosschain-registry/src/state.rs @@ -8,6 +8,7 @@ enum StorageKey { ChainToChainChannelMap, ChannelOnChainChainMap, ChainToBech32PrefixMap, + ChainToBech32PrefixReverseMap, Config, GlobalAdminMap, ChainAdminMap, @@ -22,6 +23,7 @@ impl StorageKey { StorageKey::ChainToChainChannelMap => "ctccm", StorageKey::ChannelOnChainChainMap => "cotccm", StorageKey::ChainToBech32PrefixMap => "ctbpm", + StorageKey::ChainToBech32PrefixReverseMap => "ctbprm", StorageKey::Config => "cfg", StorageKey::GlobalAdminMap => "gam", StorageKey::ChainAdminMap => "cam", @@ -67,6 +69,12 @@ pub const CHANNEL_ON_CHAIN_CHAIN_MAP: Map<(&str, &str), RegistryValue> = pub const CHAIN_TO_BECH32_PREFIX_MAP: Map<&str, RegistryValue> = Map::new(StorageKey::ChainToBech32PrefixMap.to_string()); +// CHAIN_TO_BECH32_PREFIX_MAP is a map from a chain id to its respective bech32 prefix. +// The boolean value indicates whether the mapping is enabled or not. +// CHAIN_ID -> (BECH32_PREFIX, ENABLED) +pub const CHAIN_TO_BECH32_PREFIX_REVERSE_MAP: Map<&str, Vec> = + Map::new(StorageKey::ChainToBech32PrefixReverseMap.to_string()); + // CONFIG stores the contract owner pub const CONFIG: Item = Item::new(StorageKey::Config.to_string()); diff --git a/cosmwasm/contracts/crosschain-swaps/Cargo.toml b/cosmwasm/contracts/crosschain-swaps/Cargo.toml index a2107db20d9..ea5f572463c 100644 --- a/cosmwasm/contracts/crosschain-swaps/Cargo.toml +++ b/cosmwasm/contracts/crosschain-swaps/Cargo.toml @@ -57,10 +57,11 @@ serde-json-wasm = { workspace = true } serde-cw-value = { workspace = true } bech32 = { workspace = true } cw-utils = { workspace = true } +itertools = {workspace = true} swaprouter = { path = "../swaprouter", features = ["imported"]} +registry = { path = "../../packages/registry"} prost = {version = "0.11.2", default-features = false, features = ["prost-derive"]} -osmosis-std-derive = "0.13.2" enum-repr = "0.2.6" [dev-dependencies] diff --git a/cosmwasm/contracts/crosschain-swaps/src/checks.rs b/cosmwasm/contracts/crosschain-swaps/src/checks.rs index dcc3eebeff4..785f50dd16a 100644 --- a/cosmwasm/contracts/crosschain-swaps/src/checks.rs +++ b/cosmwasm/contracts/crosschain-swaps/src/checks.rs @@ -1,9 +1,9 @@ use cosmwasm_std::{Addr, Deps}; +use itertools::{self, Itertools}; +use registry::Registry; -use crate::{ - state::{CHANNEL_MAP, CONFIG, DISABLED_PREFIXES}, - ContractError, -}; +use crate::state::CONFIG; +use crate::ContractError; pub fn check_is_contract_governor(deps: Deps, sender: Addr) -> Result<(), ContractError> { let config = CONFIG.load(deps.storage).unwrap(); @@ -16,7 +16,7 @@ pub fn check_is_contract_governor(deps: Deps, sender: Addr) -> Result<(), Contra /// If the specified receiver is an explicit channel+addr, extract the parts /// and use the strings as provided -fn validate_explicit_receiver(receiver: &str) -> Result<(String, Addr), ContractError> { +fn validate_explicit_receiver(deps: Deps, receiver: &str) -> Result<(String, Addr), ContractError> { let (channel, address) = receiver .strip_prefix("ibc:") .and_then(|s| s.split_once('/')) @@ -31,46 +31,45 @@ fn validate_explicit_receiver(receiver: &str) -> Result<(String, Addr), Contract receiver: receiver.to_string(), }); } - let channel_id = &channel[8..]; - if channel_id.is_empty() || channel_id.parse::().is_err() { - return Err(ContractError::InvalidReceiver { - receiver: receiver.to_string(), - }); - }; let Ok(_) = bech32::decode(&address) else { return Err(ContractError::InvalidReceiver { receiver: receiver.to_string() }) }; - Ok((channel.to_string(), Addr::unchecked(address))) + let registry = Registry::default(deps); + let chain = registry.get_connected_chain("osmosis", &channel)?; + // TODO: validate that the prefix of the receiver matches the chain + + Ok((chain, Addr::unchecked(address))) } /// If the specified receiver is not explicit, validate that the receiver /// address is a valid address for the destination chain. This will prevent IBC /// transfers from failing after forwarding -fn validate_simplified_receiver( - deps: Deps, - receiver: &str, -) -> Result<(String, Addr), ContractError> { +fn validate_bech32_receiver(deps: Deps, receiver: &str) -> Result<(String, Addr), ContractError> { let Ok((prefix, _, _)) = bech32::decode(receiver) else { return Err(ContractError::InvalidReceiver { receiver: receiver.to_string() }) }; - // Check if the prefix has been disabled - if DISABLED_PREFIXES.has(deps.storage, &prefix) { - return Err(ContractError::InvalidReceiver { - receiver: receiver.to_string(), - }); + let registry = Registry::default(deps); + let chain = registry.get_chain_for_bech32_prefix(&prefix)?; + + Ok((chain, Addr::unchecked(receiver))) +} + +fn validate_chain_receiver(deps: Deps, receiver: &str) -> Result<(String, Addr), ContractError> { + let Some((chain, addr)) = receiver.split('/').collect_tuple() else { + return Err(ContractError::InvalidReceiver { receiver: receiver.to_string() }) }; - let channel = - CHANNEL_MAP - .load(deps.storage, &prefix) - .map_err(|_| ContractError::InvalidReceiver { - receiver: receiver.to_string(), - })?; + // TODO: validate that the prefix of the receiver matches the chain + let _registry = Registry::default(deps); + + let Ok(_) = bech32::decode(addr) else { + return Err(ContractError::InvalidReceiver { receiver: receiver.to_string() }) + }; - Ok((channel, Addr::unchecked(receiver))) + Ok((chain.to_string(), Addr::unchecked(addr))) } /// The receiver can be specified explicitly (ibc:channel-n/osmo1...) or in a @@ -84,24 +83,20 @@ fn validate_simplified_receiver( /// appropriate channel for the addr pub fn validate_receiver(deps: Deps, receiver: &str) -> Result<(String, Addr), ContractError> { if receiver.starts_with("ibc:channel-") { - validate_explicit_receiver(receiver) + validate_explicit_receiver(deps, receiver) + } else if receiver.contains('/') { + validate_chain_receiver(deps, receiver) } else { - validate_simplified_receiver(deps, receiver) + validate_bech32_receiver(deps, receiver) } } -fn stringify(json: &serde_cw_value::Value) -> Result { - serde_json_wasm::to_string(&json).map_err(|_| ContractError::CustomError { - msg: "invalid value".to_string(), // This shouldn't happen. - }) -} - pub fn ensure_map(json: &serde_cw_value::Value) -> Result<(), ContractError> { match json { serde_cw_value::Value::Map(_) => Ok(()), _ => Err(ContractError::InvalidJson { error: format!("invalid json: expected an object"), - json: stringify(json)?, + json: registry::utils::stringify(json)?, }), } } @@ -120,7 +115,7 @@ pub fn ensure_key_missing( { Err(ContractError::InvalidJson { error: format!("invalid json: {key} key not allowed"), - json: stringify(json_object)?, + json: registry::utils::stringify(json_object)?, }) } else { Ok(()) diff --git a/cosmwasm/contracts/crosschain-swaps/src/consts.rs b/cosmwasm/contracts/crosschain-swaps/src/consts.rs index b4938b11f28..ba7ecfb5728 100644 --- a/cosmwasm/contracts/crosschain-swaps/src/consts.rs +++ b/cosmwasm/contracts/crosschain-swaps/src/consts.rs @@ -7,8 +7,5 @@ pub enum MsgReplyID { Forward = 2, } -// IBC timeout -pub const PACKET_LIFETIME: u64 = 604_800u64; // One week in seconds - // Callback key pub const CALLBACK_KEY: &str = "ibc_callback"; diff --git a/cosmwasm/contracts/crosschain-swaps/src/contract.rs b/cosmwasm/contracts/crosschain-swaps/src/contract.rs index dbefb18764f..016bb49ea17 100644 --- a/cosmwasm/contracts/crosschain-swaps/src/contract.rs +++ b/cosmwasm/contracts/crosschain-swaps/src/contract.rs @@ -8,7 +8,7 @@ use cw2::set_contract_version; use crate::consts::MsgReplyID; use crate::error::ContractError; use crate::msg::{ExecuteMsg, IBCLifecycleComplete, InstantiateMsg, MigrateMsg, QueryMsg, SudoMsg}; -use crate::state::{Config, CHANNEL_MAP, CONFIG, RECOVERY_STATES}; +use crate::state::{Config, CONFIG, RECOVERY_STATES}; use crate::{execute, ibc_lifecycle}; // version info for migration info @@ -33,9 +33,6 @@ pub fn instantiate( governor, }; CONFIG.save(deps.storage, &state)?; - for (prefix, channel) in msg.channels.into_iter() { - CHANNEL_MAP.save(deps.storage, &prefix, &channel)?; - } Ok(Response::new().add_attribute("method", "instantiate")) } @@ -76,13 +73,6 @@ pub fn execute( ) } ExecuteMsg::Recover {} => execute::recover(deps, info.sender), - ExecuteMsg::SetChannel { prefix, channel } => { - execute::set_channel(deps, info.sender, prefix, channel) - } - ExecuteMsg::DisablePrefix { prefix } => execute::disable_prefix(deps, info.sender, prefix), - ExecuteMsg::ReEnablePrefix { prefix } => { - execute::re_enable_prefix(deps, info.sender, prefix) - } ExecuteMsg::TransferOwnership { new_governor } => { execute::transfer_ownership(deps, info.sender, new_governor) } @@ -120,11 +110,11 @@ pub fn sudo(deps: DepsMut, _env: Env, msg: SudoMsg) -> Result Result { +pub fn reply(deps: DepsMut, env: Env, reply: Reply) -> Result { deps.api .debug(&format!("executing crosschain reply: {reply:?}")); match MsgReplyID::from_repr(reply.id) { - Some(MsgReplyID::Swap) => execute::handle_swap_reply(deps, reply), + Some(MsgReplyID::Swap) => execute::handle_swap_reply(deps, env, reply), Some(MsgReplyID::Forward) => execute::handle_forward_reply(deps, reply), None => Err(ContractError::InvalidReplyID { id: reply.id }), } diff --git a/cosmwasm/contracts/crosschain-swaps/src/error.rs b/cosmwasm/contracts/crosschain-swaps/src/error.rs index 921677f503a..b0b8f3e2ae0 100644 --- a/cosmwasm/contracts/crosschain-swaps/src/error.rs +++ b/cosmwasm/contracts/crosschain-swaps/src/error.rs @@ -1,4 +1,5 @@ use cosmwasm_std::StdError; +use registry::RegistryError; use thiserror::Error; #[derive(Error, Debug)] @@ -6,6 +7,9 @@ pub enum ContractError { #[error("{0}")] Std(#[from] StdError), + #[error("{0}")] + RegistryError(#[from] RegistryError), + #[error("{0}")] Payment(#[from] cw_utils::PaymentError), diff --git a/cosmwasm/contracts/crosschain-swaps/src/execute.rs b/cosmwasm/contracts/crosschain-swaps/src/execute.rs index a31054987f2..8d75f841fa2 100644 --- a/cosmwasm/contracts/crosschain-swaps/src/execute.rs +++ b/cosmwasm/contracts/crosschain-swaps/src/execute.rs @@ -1,15 +1,16 @@ -use cosmwasm_std::{coins, to_binary, wasm_execute, BankMsg, Empty, Timestamp}; +use cosmwasm_std::{coins, to_binary, wasm_execute, BankMsg, Env, Timestamp}; use cosmwasm_std::{Addr, Coin, DepsMut, Response, SubMsg, SubMsgResponse, SubMsgResult}; +use registry::Registry; use swaprouter::msg::ExecuteMsg as SwapRouterExecute; use crate::checks::{check_is_contract_governor, ensure_key_missing, validate_receiver}; -use crate::consts::{MsgReplyID, CALLBACK_KEY, PACKET_LIFETIME}; -use crate::ibc::{MsgTransfer, MsgTransferResponse}; +use crate::consts::{MsgReplyID, CALLBACK_KEY}; use crate::msg::{CrosschainSwapResponse, FailedDeliveryAction, SerializableJson}; +use registry::proto::MsgTransferResponse; -use crate::state::{self, Config, CHANNEL_MAP, DISABLED_PREFIXES}; +use crate::state; use crate::state::{ - ForwardMsgReplyState, ForwardTo, SwapMsgReplyState, CONFIG, FORWARD_REPLY_STATE, + Config, ForwardMsgReplyState, ForwardTo, SwapMsgReplyState, CONFIG, FORWARD_REPLY_STATE, INFLIGHT_PACKETS, RECOVERY_STATES, SWAP_REPLY_STATE, }; use crate::utils::{build_memo, parse_swaprouter_reply}; @@ -43,11 +44,12 @@ pub fn swap_and_forward( let msg = wasm_execute(config.swap_contract, &swap_msg, vec![swap_coin])?; // Check that the received is valid and retrieve its channel - let (valid_channel, valid_receiver) = validate_receiver(deps.as_ref(), receiver)?; + let (valid_chain, valid_receiver) = validate_receiver(deps.as_ref(), receiver)?; // If there is a memo, check that it is valid (i.e. a valud json object that // doesn't contain the key that we will insert later) if let Some(memo) = &next_memo { // Ensure the json is an object ({...}) and that it does not contain the CALLBACK_KEY + deps.api.debug(&format!("checking memo: {memo:?}")); ensure_key_missing(memo.as_value(), CALLBACK_KEY)?; } @@ -62,6 +64,8 @@ pub fn swap_and_forward( }); } + // TODO: Unwrap before swap + // Store information about the original message to be used in the reply SWAP_REPLY_STATE.save( deps.storage, @@ -70,7 +74,7 @@ pub fn swap_and_forward( block_time, contract_addr, forward_to: ForwardTo { - channel: valid_channel, + chain: valid_chain, receiver: valid_receiver, next_memo, on_failed_delivery: failed_delivery_action, @@ -84,6 +88,7 @@ pub fn swap_and_forward( // The swap has succeeded and we need to generate the forward IBC transfer pub fn handle_swap_reply( deps: DepsMut, + env: Env, msg: cosmwasm_std::Reply, ) -> Result { deps.api.debug(&format!("handle_swap_reply")); @@ -95,32 +100,25 @@ pub fn handle_swap_reply( // Build an IBC packet to forward the swap. let contract_addr = &swap_msg_state.contract_addr; - let ts = swap_msg_state.block_time.plus_seconds(PACKET_LIFETIME); // If the memo is provided we want to include it in the IBC message. If not, // we default to an empty object. The resulting memo will always include the // callback so this contract can track the IBC send let memo = build_memo(swap_msg_state.forward_to.next_memo, contract_addr.as_str())?; - // Cosmwasm's IBCMsg::Transfer does not support memo. - // To build and send the packet properly, we need to send it using stargate messages. - // See https://github.com/CosmWasm/cosmwasm/issues/1477 - let ibc_transfer = MsgTransfer { - source_port: "transfer".to_string(), - source_channel: swap_msg_state.forward_to.channel.clone(), - token: Some( - Coin::new( - swap_response.amount.into(), - swap_response.token_out_denom.clone(), - ) - .into(), + let registry = Registry::default(deps.as_ref()); + let ibc_transfer = registry.unwrap_coin_into( + Coin::new( + swap_response.amount.into(), + swap_response.token_out_denom.clone(), ), - sender: contract_addr.to_string(), - receiver: swap_msg_state.forward_to.receiver.clone().into(), - timeout_height: None, - timeout_timestamp: Some(ts.nanos()), + swap_msg_state.forward_to.receiver.clone().to_string(), + Some(&swap_msg_state.forward_to.chain), + env.contract.address.to_string(), + env.block.time, memo, - }; + )?; + deps.api.debug(&format!("IBC transfer: {ibc_transfer:?}")); // Base response let response = Response::new() @@ -142,7 +140,7 @@ pub fn handle_swap_reply( FORWARD_REPLY_STATE.save( deps.storage, &ForwardMsgReplyState { - channel_id: swap_msg_state.forward_to.channel, + channel_id: ibc_transfer.source_channel.clone(), to_address: swap_msg_state.forward_to.receiver.into(), amount: swap_response.amount.into(), denom: swap_response.token_out_denom, @@ -223,6 +221,8 @@ pub fn handle_forward_reply( /// Transfers any tokens stored in RECOVERY_STATES[sender] to the sender. pub fn recover(deps: DepsMut, sender: Addr) -> Result { let recoveries = RECOVERY_STATES.load(deps.storage, &sender)?; + // Remove the recoveries from the store. If the sends fail, the whole tx should be reverted. + RECOVERY_STATES.remove(deps.storage, &sender); let msgs = recoveries.into_iter().map(|r| BankMsg::Send { to_address: r.recovery_addr.into(), amount: coins(r.amount, r.denom), @@ -230,49 +230,6 @@ pub fn recover(deps: DepsMut, sender: Addr) -> Result { Ok(Response::new().add_messages(msgs)) } -/// Set a prefix->channel map in the registry -pub fn set_channel( - deps: DepsMut, - sender: Addr, - prefix: String, - channel: String, -) -> Result { - check_is_contract_governor(deps.as_ref(), sender)?; - if CHANNEL_MAP.has(deps.storage, &prefix) { - return Err(ContractError::PrefixAlreadyExists { prefix }); - } - CHANNEL_MAP.save(deps.storage, &prefix, &channel)?; - Ok(Response::new().add_attribute("method", "set_channel")) -} - -/// Disable a prefix -pub fn disable_prefix( - deps: DepsMut, - sender: Addr, - prefix: String, -) -> Result { - check_is_contract_governor(deps.as_ref(), sender)?; - if !CHANNEL_MAP.has(deps.storage, &prefix) { - return Err(ContractError::PrefixDoesNotExist { prefix }); - } - DISABLED_PREFIXES.save(deps.storage, &prefix, &Empty {})?; - Ok(Response::new().add_attribute("method", "disable_prefix")) -} - -/// Re-enable a prefix -pub fn re_enable_prefix( - deps: DepsMut, - sender: Addr, - prefix: String, -) -> Result { - check_is_contract_governor(deps.as_ref(), sender)?; - if !DISABLED_PREFIXES.has(deps.storage, &prefix) { - return Err(ContractError::PrefixNotDisabled { prefix }); - } - DISABLED_PREFIXES.remove(deps.storage, &prefix); - Ok(Response::new().add_attribute("method", "re_enable_prefix")) -} - // Transfer ownership of this contract pub fn transfer_ownership( deps: DepsMut, @@ -324,16 +281,12 @@ mod tests { static CREATOR_ADDRESS: &str = "creator"; static SWAPCONTRACT_ADDRESS: &str = "swapcontract"; - static RECEIVER_ADDRESS1: &str = "prefix12smx2wdlyttvyzvzg54y2vnqwq2qjatel8rck9"; - static RECEIVER_ADDRESS2: &str = "other12smx2wdlyttvyzvzg54y2vnqwq2qjatere840z"; - // test helper #[allow(unused_assignments)] fn initialize_contract(deps: DepsMut) -> Addr { let msg = InstantiateMsg { governor: String::from(CREATOR_ADDRESS), swap_contract: String::from(SWAPCONTRACT_ADDRESS), - channels: vec![("prefix".to_string(), "channel1".to_string())], }; let info = mock_info(CREATOR_ADDRESS, &[]); @@ -349,10 +302,6 @@ mod tests { let governor = initialize_contract(deps.as_mut()); let config = CONFIG.load(&deps.storage).unwrap(); assert_eq!(config.governor, governor); - assert_eq!( - CHANNEL_MAP.load(&deps.storage, "prefix").unwrap(), - "channel1" - ); } #[test] @@ -391,92 +340,6 @@ mod tests { assert_eq!(governor, config.governor); } - #[test] - fn modify_channel_registry() { - let mut deps = mock_dependencies(); - - let governor = initialize_contract(deps.as_mut()); - let governor_info = mock_info(governor.as_str(), &vec![] as &Vec); - validate_receiver(deps.as_ref(), RECEIVER_ADDRESS1).unwrap(); - - // and new channel - let msg = ExecuteMsg::SetChannel { - prefix: "other".to_string(), - channel: "channel2".to_string(), - }; - contract::execute(deps.as_mut(), mock_env(), governor_info.clone(), msg).unwrap(); - - assert_eq!( - CHANNEL_MAP.load(&deps.storage, "prefix").unwrap(), - "channel1" - ); - assert_eq!( - CHANNEL_MAP.load(&deps.storage, "other").unwrap(), - "channel2" - ); - validate_receiver(deps.as_ref(), RECEIVER_ADDRESS1).unwrap(); - validate_receiver(deps.as_ref(), RECEIVER_ADDRESS2).unwrap(); - - // Can't override an existing channel - let msg = ExecuteMsg::SetChannel { - prefix: "prefix".to_string(), - channel: "new_channel".to_string(), - }; - contract::execute(deps.as_mut(), mock_env(), governor_info.clone(), msg).unwrap_err(); - - assert_eq!( - CHANNEL_MAP.load(&deps.storage, "prefix").unwrap(), - "channel1" - ); - - // remove channel - let msg = ExecuteMsg::DisablePrefix { - prefix: "prefix".to_string(), - }; - contract::execute(deps.as_mut(), mock_env(), governor_info.clone(), msg).unwrap(); - - assert!(DISABLED_PREFIXES.load(&deps.storage, "prefix").is_ok()); - // The prefix no longer validates - validate_receiver(deps.as_ref(), RECEIVER_ADDRESS1).unwrap_err(); - - // Re enable the prefix - let msg = ExecuteMsg::ReEnablePrefix { - prefix: "prefix".to_string(), - }; - contract::execute(deps.as_mut(), mock_env(), governor_info, msg).unwrap(); - assert!(DISABLED_PREFIXES.load(&deps.storage, "prefix").is_err()); - - // The prefix is allowed again - validate_receiver(deps.as_ref(), RECEIVER_ADDRESS1).unwrap(); - } - - #[test] - fn modify_channel_registry_unauthorized() { - let mut deps = mock_dependencies(); - initialize_contract(deps.as_mut()); - - // A user other than the owner cannot modify the channel registry - let other_info = mock_info("other_sender", &vec![] as &Vec); - let msg = ExecuteMsg::SetChannel { - prefix: "other".to_string(), - channel: "something_else".to_string(), - }; - contract::execute(deps.as_mut(), mock_env(), other_info.clone(), msg).unwrap_err(); - assert_eq!( - CHANNEL_MAP.load(&deps.storage, "prefix").unwrap(), - "channel1" - ); - - let msg = ExecuteMsg::DisablePrefix { - prefix: "prefix".to_string(), - }; - contract::execute(deps.as_mut(), mock_env(), other_info, msg).unwrap_err(); - assert_eq!( - CHANNEL_MAP.load(&deps.storage, "prefix").unwrap(), - "channel1" - ); - } - #[test] fn set_swap_contract() { let mut deps = mock_dependencies(); diff --git a/cosmwasm/contracts/crosschain-swaps/src/lib.rs b/cosmwasm/contracts/crosschain-swaps/src/lib.rs index 09a2d82276f..3db805673ac 100644 --- a/cosmwasm/contracts/crosschain-swaps/src/lib.rs +++ b/cosmwasm/contracts/crosschain-swaps/src/lib.rs @@ -5,7 +5,6 @@ pub mod consts; pub mod contract; mod error; mod execute; -pub mod ibc; mod ibc_lifecycle; pub mod msg; pub mod state; diff --git a/cosmwasm/contracts/crosschain-swaps/src/msg.rs b/cosmwasm/contracts/crosschain-swaps/src/msg.rs index d1f3c8cb394..7d3a1d12f49 100644 --- a/cosmwasm/contracts/crosschain-swaps/src/msg.rs +++ b/cosmwasm/contracts/crosschain-swaps/src/msg.rs @@ -11,11 +11,6 @@ pub struct InstantiateMsg { /// This should be an instance of the Osmosis swaprouter contract pub swap_contract: String, - - /// These are the channels that will be accepted by the contract. This is - /// needed to avoid sending packets to addresses not supported by the - /// receiving chain. The channels are specified as (bech32_prefix, channel_id) - pub channels: Vec<(String, String)>, } /// An enum specifying what resolution the user expects in the case of a bad IBC @@ -90,16 +85,6 @@ pub enum ExecuteMsg { Recover {}, // Contract Management - SetChannel { - prefix: String, - channel: String, - }, - DisablePrefix { - prefix: String, - }, - ReEnablePrefix { - prefix: String, - }, TransferOwnership { new_governor: String, }, diff --git a/cosmwasm/contracts/crosschain-swaps/src/state.rs b/cosmwasm/contracts/crosschain-swaps/src/state.rs index 16bfb20336c..1514be24fee 100644 --- a/cosmwasm/contracts/crosschain-swaps/src/state.rs +++ b/cosmwasm/contracts/crosschain-swaps/src/state.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, Empty, Timestamp}; +use cosmwasm_std::{Addr, Timestamp}; use cw_storage_plus::{Item, Map}; use swaprouter::msg::ExecuteMsg as SwapRouterExecute; @@ -13,7 +13,7 @@ pub struct Config { #[cw_serde] pub struct ForwardTo { - pub channel: String, + pub chain: String, pub receiver: Addr, pub next_memo: Option, pub on_failed_delivery: FailedDeliveryAction, @@ -69,7 +69,3 @@ pub const INFLIGHT_PACKETS: Map<(&str, u64), ibc::IBCTransfer> = Map::new("infli /// Recovery. This tracks any recovery that an addr can execute. pub const RECOVERY_STATES: Map<&Addr, Vec> = Map::new("recovery"); - -/// A mapping of knwon IBC channels accepted by the contract. bech32_prefix => channel -pub const CHANNEL_MAP: Map<&str, String> = Map::new("chain_map"); -pub const DISABLED_PREFIXES: Map<&str, Empty> = Map::new("disabled_prefixes"); diff --git a/cosmwasm/contracts/crosschain-swaps/tests/test_env.rs b/cosmwasm/contracts/crosschain-swaps/tests/test_env.rs index 0784d57362d..e37d6b1e919 100644 --- a/cosmwasm/contracts/crosschain-swaps/tests/test_env.rs +++ b/cosmwasm/contracts/crosschain-swaps/tests/test_env.rs @@ -71,7 +71,6 @@ impl TestEnv { get_crosschain_swaps_wasm(), &CrosschainInstantiate { swap_contract: swaprouter_address.clone(), - channels: vec![("osmo".to_string(), "channel-0".to_string())], governor: owner.address(), }, ); diff --git a/cosmwasm/contracts/outpost/Cargo.toml b/cosmwasm/contracts/outpost/Cargo.toml index b427e8b97d7..fb00b8c439c 100644 --- a/cosmwasm/contracts/outpost/Cargo.toml +++ b/cosmwasm/contracts/outpost/Cargo.toml @@ -53,6 +53,7 @@ cw-utils = { workspace = true } swaprouter = { path = "../swaprouter", features = ["imported"]} crosschain-swaps = { path = "../crosschain-swaps", features = ["imported"]} +registry = { path = "../../packages/registry"} [dev-dependencies] cw-multi-test = { workspace = true } diff --git a/cosmwasm/contracts/outpost/src/execute.rs b/cosmwasm/contracts/outpost/src/execute.rs index db5e387234c..0fbdd615fc9 100644 --- a/cosmwasm/contracts/outpost/src/execute.rs +++ b/cosmwasm/contracts/outpost/src/execute.rs @@ -63,7 +63,7 @@ pub fn execute_swap( error: e.to_string(), })?; - let ibc_transfer_msg = crosschain_swaps::ibc::MsgTransfer { + let ibc_transfer_msg = registry::proto::MsgTransfer { source_port: "transfer".to_string(), source_channel: "channel-0".to_string(), token: Some(Coin::new(coin.amount.into(), coin.denom).into()), diff --git a/cosmwasm/packages/registry/Cargo.toml b/cosmwasm/packages/registry/Cargo.toml new file mode 100644 index 00000000000..f0c461411f7 --- /dev/null +++ b/cosmwasm/packages/registry/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "registry" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } +bech32 = { workspace = true } +serde-json-wasm = { workspace = true } +serde-cw-value = { workspace = true } +sha2 = "0.10.6" +hex = "0.4.3" +schemars = { workspace = true } +osmosis-std = { workspace = true } +osmosis-std-derive = "0.13.2" +prost = {version = "0.11.2", default-features = false, features = ["prost-derive"]} +itertools = {workspace = true} diff --git a/cosmwasm/packages/registry/src/error.rs b/cosmwasm/packages/registry/src/error.rs new file mode 100644 index 00000000000..6e3ed5765a9 --- /dev/null +++ b/cosmwasm/packages/registry/src/error.rs @@ -0,0 +1,98 @@ +use cosmwasm_std::StdError; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum RegistryError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("{0}")] + Json(#[from] serde_json_wasm::de::Error), + + // Validation errors + #[error("Invalid channel id: {0}")] + InvalidChannelId(String), + + #[error("error {action} {addr}")] + Bech32Error { + action: String, + addr: String, + #[source] + source: bech32::Error, + }, + + #[error("serialization error: {error}")] + SerialiaztionError { error: String }, + + #[error("denom {denom:?} is not an IBC denom")] + InvalidIBCDenom { denom: String }, + + #[error("No deom trace found for: {denom:?}")] + NoDenomTrace { denom: String }, + + #[error("Invalid denom trace: {error}")] + InvalidDenomTrace { error: String }, + + #[error("Invalid path {path:?} for denom {denom:?}")] + InvalidDenomTracePath { path: String, denom: String }, + + #[error("Invalid transfer port {port:?}")] + InvalidTransferPort { port: String }, + + #[error("Invalid multihop length {length:?}. Must be >={min}")] + InvalidMultiHopLengthMin { length: usize, min: usize }, + + #[error("Invalid multihop length {length:?}. Must be <={max}")] + InvalidMultiHopLengthMax { length: usize, max: usize }, + + #[error( + "receiver prefix for {receiver} must match the bech32 prefix of the destination chain {chain}" + )] + InvalidReceiverPrefix { receiver: String, chain: String }, + + #[error("trying to transfer from chain {chain} to itself. This is not allowed.")] + InvalidHopSameChain { chain: String }, + + #[error("invalid json: {error}. Got: {json}")] + InvalidJson { error: String, json: String }, + + // Registry loading errors + #[error("contract alias does not exist: {alias:?}")] + AliasDoesNotExist { alias: String }, + + #[error("no authorized address found for source chain: {source_chain:?}")] + ChainAuthorizedAddressDoesNotExist { source_chain: String }, + + #[error("chain channel link does not exist: {source_chain:?} -> {destination_chain:?}")] + ChainChannelLinkDoesNotExist { + source_chain: String, + destination_chain: String, + }, + + #[error("channel chain link does not exist: {channel_id:?} on {source_chain:?} -> chain")] + ChannelChainLinkDoesNotExist { + channel_id: String, + source_chain: String, + }, + + #[error("channel chain link does not exist: {channel_id:?} on {source_chain:?} -> chain")] + ChannelToChainChainLinkDoesNotExist { + channel_id: String, + source_chain: String, + }, + + #[error("native denom link does not exist: {native_denom:?}")] + NativeDenomLinkDoesNotExist { native_denom: String }, + + #[error("bech32 prefix does not exist for chain: {chain}")] + Bech32PrefixDoesNotExist { chain: String }, +} + +impl From for StdError { + fn from(e: RegistryError) -> Self { + match e { + RegistryError::Std(e) => e, + _ => StdError::generic_err(e.to_string()), + } + } +} diff --git a/cosmwasm/packages/registry/src/lib.rs b/cosmwasm/packages/registry/src/lib.rs new file mode 100644 index 00000000000..35d6aba584c --- /dev/null +++ b/cosmwasm/packages/registry/src/lib.rs @@ -0,0 +1,9 @@ +mod error; +mod registry; + +pub use crate::registry::Registry; +pub use error::RegistryError; + +pub mod msg; +pub mod proto; +pub mod utils; diff --git a/cosmwasm/packages/registry/src/msg.rs b/cosmwasm/packages/registry/src/msg.rs new file mode 100644 index 00000000000..a503740c5fa --- /dev/null +++ b/cosmwasm/packages/registry/src/msg.rs @@ -0,0 +1,59 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(GetAddressFromAliasResponse)] + GetAddressFromAlias { contract_alias: String }, + + #[returns(GetChannelFromChainPairResponse)] + GetChannelFromChainPair { + source_chain: String, + destination_chain: String, + }, + + #[returns(GetDestinationChainFromSourceChainViaChannelResponse)] + GetDestinationChainFromSourceChainViaChannel { + on_chain: String, + via_channel: String, + }, + + #[returns(QueryGetBech32PrefixFromChainNameResponse)] + GetBech32PrefixFromChainName { chain_name: String }, + + #[returns(QueryGetChainNameFromBech32PrefixResponse)] + GetChainNameFromBech32Prefix { prefix: String }, + + #[returns(crate::proto::QueryDenomTraceResponse)] + GetDenomTrace { ibc_denom: String }, +} + +// Response for GetAddressFromAlias query +#[cw_serde] +pub struct GetAddressFromAliasResponse { + pub address: String, +} + +// Response for GetChannelFromChainPair query +#[cw_serde] +pub struct GetChannelFromChainPairResponse { + pub channel_id: String, +} + +// Response for GetDestinationChainFromSourceChainViaChannel query +#[cw_serde] +pub struct GetDestinationChainFromSourceChainViaChannelResponse { + pub destination_chain: String, +} + +// Response for GetBech32PrefixFromChainName query +#[cw_serde] +pub struct QueryGetBech32PrefixFromChainNameResponse { + pub bech32_prefix: String, +} + +// Response for GetChainNameFromBech32Prefix query +#[cw_serde] +pub struct QueryGetChainNameFromBech32PrefixResponse { + pub chain_name: String, +} diff --git a/cosmwasm/contracts/crosschain-swaps/src/ibc.rs b/cosmwasm/packages/registry/src/proto.rs similarity index 57% rename from cosmwasm/contracts/crosschain-swaps/src/ibc.rs rename to cosmwasm/packages/registry/src/proto.rs index 817e0801e57..16ed2d61c2a 100644 --- a/cosmwasm/contracts/crosschain-swaps/src/ibc.rs +++ b/cosmwasm/packages/registry/src/proto.rs @@ -54,3 +54,56 @@ pub struct MsgTransferResponse { #[prost(uint64, tag = "1")] pub sequence: u64, } + +// DenomTrace query message definition. +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/ibc.applications.transfer.v1.QueryDenomTraceRequest")] +#[proto_query( + path = "/ibc.applications.transfer.v1.Query/DenomTrace", + response_type = QueryDenomTraceResponse +)] +pub struct QueryDenomTraceRequest { + #[prost(string, tag = "1")] + pub hash: ::prost::alloc::string::String, +} + +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/ibc.applications.transfer.v1.QueryDenomTraceResponse")] +pub struct QueryDenomTraceResponse { + #[prost(message, optional, tag = "1")] + pub denom_trace: Option, +} + +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, +)] +pub struct DenomTrace { + #[prost(string, tag = "1")] + pub path: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub base_denom: ::prost::alloc::string::String, +} diff --git a/cosmwasm/packages/registry/src/registry.rs b/cosmwasm/packages/registry/src/registry.rs new file mode 100644 index 00000000000..4505fa28cba --- /dev/null +++ b/cosmwasm/packages/registry/src/registry.rs @@ -0,0 +1,487 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Coin, Deps, Timestamp}; +use itertools::Itertools; +use sha2::Digest; +use sha2::Sha256; + +use crate::proto; +use crate::utils::merge_json; +use crate::{error::RegistryError, msg::QueryMsg}; +use std::convert::AsRef; + +// takes a transfer message and returns ibc/ +pub fn hash_denom_trace(unwrapped: &str) -> String { + let mut hasher = Sha256::new(); + hasher.update(unwrapped.as_bytes()); + let result = hasher.finalize(); + let hash = hex::encode(result); + format!("ibc/{}", hash.to_uppercase()) +} + +// IBC transfer port +const TRANSFER_PORT: &str = "transfer"; +// IBC timeout +pub const PACKET_LIFETIME: u64 = 604_800u64; // One week in seconds + +#[cw_serde] +pub struct Chain(String); +#[cw_serde] +pub struct ChannelId(String); + +impl ChannelId { + pub fn new(channel_id: &str) -> Result { + if !ChannelId::validate(channel_id) { + return Err(RegistryError::InvalidChannelId(channel_id.to_string())); + } + Ok(Self(channel_id.to_string())) + } + + pub fn validate(channel_id: &str) -> bool { + if !channel_id.starts_with("channel-") { + return false; + } + // Check that what comes after "channel-" is a valid int + let channel_num = &channel_id[8..]; + if channel_num.parse::().is_err() { + return false; + } + true + } +} + +impl AsRef for ChannelId { + fn as_ref(&self) -> &str { + &self.0 + } +} + +impl AsRef for Chain { + fn as_ref(&self) -> &str { + &self.0 + } +} + +#[cw_serde] +pub struct ForwardingMemo { + pub receiver: String, + pub port: String, + pub channel: ChannelId, + #[serde(skip_serializing_if = "Option::is_none")] + pub next: Option>, +} + +#[cw_serde] +pub struct Memo { + forward: ForwardingMemo, +} + +// We will assume here that chains use the standard ibc-go formats. This is ok +// because we will be checking the channels in the registry and failing if they +// are not valid. We also need to enforce that all ports are explicitly "transfer" +#[cw_serde] +pub struct MultiHopDenom { + pub local_denom: String, + pub on: Chain, + pub via: Option, // This is optional because native tokens have no channel +} + +pub struct Registry<'a> { + pub deps: Deps<'a>, + pub registry_contract: String, +} + +impl<'a> Registry<'a> { + pub fn new(deps: Deps<'a>, registry_contract: String) -> Result { + deps.api.addr_validate(®istry_contract)?; + Ok(Self { + deps, + registry_contract, + }) + } + + #[allow(dead_code)] + pub fn default(deps: Deps<'a>) -> Self { + Self { + deps, + registry_contract: match option_env!("REGISTRY_CONTRACT") { + Some(registry_contract) => registry_contract.to_string(), + None => { + "REGISTRY_CONTRACT not set at compile time. Use Registry::new(contract_addr)." + .to_string() + } + }, + } + } + + /// Get a contract address by its alias + /// Example: get_contract("registries") -> "osmo1..." + pub fn get_contract(self, alias: String) -> Result { + self.deps + .querier + .query_wasm_smart( + &self.registry_contract, + &QueryMsg::GetAddressFromAlias { + contract_alias: alias.clone(), + }, + ) + .map_err(|_e| RegistryError::AliasDoesNotExist { alias }) + } + + /// Get a the name of the chain connected via channel `via_channel` on chain `on_chain`. + /// Example: get_connected_chain("osmosis", "channel-42") -> "juno" + pub fn get_connected_chain( + &self, + on_chain: &str, + via_channel: &str, + ) -> Result { + self.deps + .querier + .query_wasm_smart( + &self.registry_contract, + &QueryMsg::GetDestinationChainFromSourceChainViaChannel { + on_chain: on_chain.to_string(), + via_channel: via_channel.to_string(), + }, + ) + .map_err(|_e| RegistryError::ChannelToChainChainLinkDoesNotExist { + channel_id: via_channel.to_string(), + source_chain: on_chain.to_string(), + }) + } + + /// Get the channel id for the channel connecting chain `on_chain` to chain `for_chain`. + /// Example: get_channel("osmosis", "juno") -> "channel-0" + /// Example: get_channel("juno", "osmosis") -> "channel-42" + pub fn get_channel(&self, for_chain: &str, on_chain: &str) -> Result { + self.deps + .querier + .query_wasm_smart( + &self.registry_contract, + &QueryMsg::GetChannelFromChainPair { + source_chain: on_chain.to_string(), + destination_chain: for_chain.to_string(), + }, + ) + .map_err(|_e| RegistryError::ChainChannelLinkDoesNotExist { + source_chain: on_chain.to_string(), + destination_chain: for_chain.to_string(), + }) + } + + /// Re-encodes the bech32 address for the receiving chain + /// Example: encode_addr_for_chain("osmo1...", "juno") -> "juno1..." + pub fn encode_addr_for_chain(&self, addr: &str, chain: &str) -> Result { + let (_, data, variant) = bech32::decode(addr).map_err(|e| RegistryError::Bech32Error { + action: "decoding".into(), + addr: addr.into(), + source: e, + })?; + + let response: String = self.deps.querier.query_wasm_smart( + &self.registry_contract, + &QueryMsg::GetBech32PrefixFromChainName { + chain_name: chain.to_string(), + }, + )?; + + let receiver = + bech32::encode(&response, data, variant).map_err(|e| RegistryError::Bech32Error { + action: "encoding".into(), + addr: addr.into(), + source: e, + })?; + + Ok(receiver) + } + + /// Get the bech32 prefix for the given chain + /// Example: get_bech32_prefix("osmosis") -> "osmo" + pub fn get_bech32_prefix(&self, chain: &str) -> Result { + self.deps + .api + .debug(&format!("Getting prefix for chain: {chain}")); + let prefix: String = self + .deps + .querier + .query_wasm_smart( + &self.registry_contract, + &QueryMsg::GetBech32PrefixFromChainName { + chain_name: chain.to_string(), + }, + ) + .map_err(|e| { + self.deps.api.debug(&format!("Got error: {e}")); + RegistryError::Bech32PrefixDoesNotExist { + chain: chain.into(), + } + })?; + if prefix.is_empty() { + return Err(RegistryError::Bech32PrefixDoesNotExist { + chain: chain.into(), + }); + } + Ok(prefix) + } + + /// Get the chain that uses a bech32 prefix. If more than one chain uses the + /// same prefix, return an error + /// + /// Example: get_chain_for_bech32_prefix("osmo") -> "osmosis" + pub fn get_chain_for_bech32_prefix(&self, prefix: &str) -> Result { + self.deps + .querier + .query_wasm_smart( + &self.registry_contract, + &QueryMsg::GetChainNameFromBech32Prefix { + prefix: prefix.to_lowercase(), + }, + ) + .map_err(RegistryError::Std) + } + + /// Returns the IBC path the denom has taken to get to the current chain + /// Example: unwrap_denom_path("ibc/0A...") -> [{"local_denom":"ibc/0A","on":"osmosis","via":"channel-17"},{"local_denom":"ibc/1B","on":"middle_chain","via":"channel-75"},{"local_denom":"token0","on":"source_chain","via":null} + pub fn unwrap_denom_path(&self, denom: &str) -> Result, RegistryError> { + self.deps.api.debug(&format!("Unwrapping denom {denom}")); + + let mut current_chain = "osmosis".to_string(); // The initial chain is always osmosis + + // Check that the denom is an IBC denom + if !denom.starts_with("ibc/") { + return Ok(vec![MultiHopDenom { + local_denom: denom.to_string(), + on: Chain(current_chain), + via: None, + }]); + } + + // Get the denom trace + let res = proto::QueryDenomTraceRequest { + hash: denom.to_string(), + } + .query(&self.deps.querier)?; + + let proto::DenomTrace { path, base_denom } = match res.denom_trace { + Some(denom_trace) => Ok(denom_trace), + None => Err(RegistryError::NoDenomTrace { + denom: denom.into(), + }), + }?; + + self.deps + .api + .debug(&format!("procesing denom trace {path}")); + // Let's iterate over the parts of the denom trace and extract the + // chain/channels into a more useful structure: MultiHopDenom + let mut hops: Vec = vec![]; + let mut rest: &str = &path; + let parts = path.split('/'); + + for chunk in &parts.chunks(2) { + let Some((port, channel)) = chunk.take(2).collect_tuple() else { + return Err(RegistryError::InvalidDenomTracePath{ path: path.clone(), denom: denom.into() }); + }; + + // Check that the port is "transfer" + if port != TRANSFER_PORT { + return Err(RegistryError::InvalidTransferPort { port: port.into() }); + } + + // Check that the channel is valid + let full_trace = rest.to_owned() + "/" + &base_denom; + hops.push(MultiHopDenom { + local_denom: hash_denom_trace(&full_trace), + on: Chain(current_chain.clone().to_string()), + via: Some(ChannelId::new(channel)?), + }); + + current_chain = self.get_connected_chain(¤t_chain, channel)?; + rest = rest + .trim_start_matches(&format!("{port}/{channel}")) + .trim_start_matches('/'); // hops other than first and last will have this slash + } + + hops.push(MultiHopDenom { + local_denom: base_denom, + on: Chain(current_chain), + via: None, + }); + + Ok(hops) + } + + /// Returns an IBC MsgTransfer that with a packet forward middleware memo + /// that will send the coin back to its original chain and then to the + /// receiver in `into_chain`. + /// + /// If the receiver `into_chain` is not specified, we assume the receiver is + /// the current chain (where the the registries are hosted and the denom + /// original denom exists) + /// + /// `own_addr` must the the address of the contract that is calling this + /// function. + /// + /// `block_time` is the current block time. This is needed to calculate the + /// timeout timestamp. + pub fn unwrap_coin_into( + &self, + coin: Coin, + receiver: String, + into_chain: Option<&str>, + own_addr: String, + block_time: Timestamp, + with_memo: String, + ) -> Result { + let path = self.unwrap_denom_path(&coin.denom)?; + self.deps + .api + .debug(&format!("Generating unwrap transfer message for: {path:?}")); + + let MultiHopDenom { + local_denom: _, + on: first_chain, + via: first_channel, + } = path + .first() + .ok_or_else(|| RegistryError::InvalidDenomTracePath { + path: format!("{:?}", path.clone()), + denom: coin.denom.clone(), + })?; + + // default the receiver chain to the first chain if it isn't provided + let receiver_chain = match into_chain { + Some(chain) => chain, + None => first_chain.as_ref(), + }; + let receiver_chain: &str = &receiver_chain.to_lowercase(); + + // If the token we're sending is native, we need the receiver to be + // different than the origin chain. Otherwise, we will try to make an + // ibc transfer to the same chain and it will fail. This may be possible + // in the future when we have IBC localhost channels + if first_channel.is_none() && first_chain.as_ref() == receiver_chain { + return Err(RegistryError::InvalidHopSameChain { + chain: receiver_chain.into(), + }); + } + + // validate the receiver matches the chain + let receiver_prefix = self.get_bech32_prefix(receiver_chain)?; + if receiver[..receiver_prefix.len()] != receiver_prefix { + return Err(RegistryError::InvalidReceiverPrefix { + receiver, + chain: receiver_chain.into(), + }); + } + + let ts = block_time.plus_seconds(PACKET_LIFETIME); + let path_iter = path.iter().skip(1); + + let mut next: Option> = None; + let mut prev_chain: &str = receiver_chain; + + for hop in path_iter.rev() { + // If the last hop is the same as the receiver chain, we don't need + // to forward anymore + if hop.via.is_none() && hop.on.as_ref() == receiver_chain { + continue; + } + + // To unwrap we use the channel through which the token came, but once on the native + // chain, we need to get the channel that connects that chain to the receiver. + let channel = match &hop.via { + Some(channel) => channel.to_owned(), + None => ChannelId(self.get_channel(prev_chain, hop.on.as_ref())?), + }; + + next = Some(Box::new(Memo { + forward: ForwardingMemo { + receiver: self.encode_addr_for_chain(&receiver, prev_chain)?, + port: TRANSFER_PORT.to_string(), + channel, + next, + }, + })); + prev_chain = hop.on.as_ref(); + } + + let forward = + serde_json_wasm::to_string(&next).map_err(|e| RegistryError::SerialiaztionError { + error: e.to_string(), + })?; + // Use the provided memo as a base. Only the forward key would be overwritten + self.deps.api.debug(&format!("Forward memo: {forward}")); + self.deps.api.debug(&format!("With memo: {with_memo}")); + let memo = merge_json(&with_memo, &forward)?; + self.deps.api.debug(&format!("merge: {memo:?}")); + + // encode the receiver address for the first chain + let first_receiver = self.encode_addr_for_chain(&receiver, first_chain.as_ref())?; + // If the + let first_channel = match first_channel { + Some(channel) => Ok::(channel.as_ref().to_string()), + None => { + let channel = self.get_channel(receiver_chain, first_chain.as_ref())?; + Ok(channel) + } + }?; + + // Cosmwasm's IBCMsg::Transfer does not support memo. + // To build and send the packet properly, we need to send it using stargate messages. + // See https://github.com/CosmWasm/cosmwasm/issues/1477 + Ok(proto::MsgTransfer { + source_port: TRANSFER_PORT.to_string(), + source_channel: first_channel, + token: Some(coin.into()), + sender: own_addr, + receiver: first_receiver, + timeout_height: None, + timeout_timestamp: Some(ts.nanos()), + memo, + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_channel_id() { + assert!(ChannelId::validate("channel-0")); + assert!(ChannelId::validate("channel-1")); + assert!(ChannelId::validate("channel-1234567890")); + assert!(!ChannelId::validate("channel-")); + assert!(!ChannelId::validate("channel-abc")); + assert!(!ChannelId::validate("channel-1234567890a")); + assert!(!ChannelId::validate("channel-1234567890-")); + assert!(!ChannelId::validate("channel-1234567890-abc")); + assert!(!ChannelId::validate("channel-1234567890-1234567890")); + } + + #[test] + fn test_forwarding_memo() { + let memo = Memo { + forward: ForwardingMemo { + receiver: "receiver".to_string(), + port: "port".to_string(), + channel: ChannelId::new("channel-0").unwrap(), + next: Some(Box::new(Memo { + forward: ForwardingMemo { + receiver: "receiver2".to_string(), + port: "port2".to_string(), + channel: ChannelId::new("channel-1").unwrap(), + next: None, + }, + })), + }, + }; + let encoded = serde_json_wasm::to_string(&memo).unwrap(); + let decoded: Memo = serde_json_wasm::from_str(&encoded).unwrap(); + assert_eq!(memo, decoded); + assert_eq!( + encoded, + r#"{"forward":{"receiver":"receiver","port":"port","channel":"channel-0","next":{"forward":{"receiver":"receiver2","port":"port2","channel":"channel-1"}}}}"# + ) + } +} diff --git a/cosmwasm/packages/registry/src/utils.rs b/cosmwasm/packages/registry/src/utils.rs new file mode 100644 index 00000000000..6ee78561268 --- /dev/null +++ b/cosmwasm/packages/registry/src/utils.rs @@ -0,0 +1,87 @@ +use std::collections::BTreeMap; + +use cosmwasm_std::StdError; +use serde_cw_value::Value; + +use crate::RegistryError; + +pub fn stringify(json: &serde_cw_value::Value) -> Result { + serde_json_wasm::to_string(&json).map_err(|_| { + RegistryError::Std(StdError::generic_err( + "invalid value".to_string(), // This shouldn't happen. + )) + }) +} + +pub fn extract_map(json: Value) -> Result, RegistryError> { + match json { + serde_cw_value::Value::Map(m) => Ok(m), + _ => Err(RegistryError::InvalidJson { + error: "invalid json: expected an object".to_string(), + json: stringify(&json)?, + }), + } +} + +pub fn merge_json(first: &str, second: &str) -> Result { + // replacing some potential empty values we want to accept with an empty object + let first = match first { + "" => "{}", + "null" => "{}", + _ => first, + }; + let second = match second { + "" => "{}", + "null" => "{}", + _ => second, + }; + + let first_val: Value = serde_json_wasm::from_str(first)?; + let second_val: Value = serde_json_wasm::from_str(second)?; + + // checking potential "empty" values we want to accept + + let mut first_map = extract_map(first_val)?; + let second_map = extract_map(second_val)?; + + first_map.extend(second_map); + + stringify(&Value::Map(first_map)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_merge_json() { + // some examples + assert_eq!( + merge_json(r#"{"a": 1}"#, r#"{"b": 2}"#).unwrap(), + r#"{"a":1,"b":2}"# + ); + assert_eq!( + merge_json(r#"{"a": 1}"#, r#"{"a": 2}"#).unwrap(), + r#"{"a":2}"# + ); + assert_eq!( + merge_json(r#"{"a": 1}"#, r#"{"a": {"b": 2}}"#).unwrap(), + r#"{"a":{"b":2}}"# + ); + assert_eq!( + merge_json(r#"{"a": {"b": 2}}"#, r#"{"a": 1}"#).unwrap(), + r#"{"a":1}"# + ); + assert_eq!( + merge_json(r#"{"a": {"b": 2}}"#, r#"{"a": {"c": 3}}"#).unwrap(), + r#"{"a":{"c":3}}"# + ); + // Empties + assert_eq!(merge_json(r#"{"a": 1}"#, r#""#).unwrap(), r#"{"a":1}"#); + assert_eq!(merge_json(r#""#, r#"{"a": 1}"#).unwrap(), r#"{"a":1}"#); + assert_eq!(merge_json(r#"{"a": 1}"#, r#"null"#).unwrap(), r#"{"a":1}"#); + assert_eq!(merge_json(r#"null"#, r#"{"a": 1}"#).unwrap(), r#"{"a":1}"#); + assert_eq!(merge_json(r#"{"a": 1}"#, r#"{}"#).unwrap(), r#"{"a":1}"#); + assert_eq!(merge_json(r#"{}"#, r#"{"a": 1}"#).unwrap(), r#"{"a":1}"#); + } +} diff --git a/go.mod b/go.mod index f4d8a2de216..9b3248ee032 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ go 1.19 require ( github.com/CosmWasm/wasmd v0.30.0 - github.com/Jeffail/gabs/v2 v2.7.0 github.com/cosmos/cosmos-proto v1.0.0-alpha8 github.com/cosmos/cosmos-sdk v0.47.1 github.com/cosmos/go-bip39 v1.0.0 @@ -36,6 +35,7 @@ require ( github.com/tendermint/tendermint v0.34.24 github.com/tendermint/tm-db v0.6.8-0.20220506192307-f628bb5dc95b github.com/tidwall/btree v1.6.0 + github.com/tidwall/gjson v1.14.0 go.uber.org/multierr v1.10.0 golang.org/x/exp v0.0.0-20221212164502-fae10dda9338 google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef @@ -67,6 +67,8 @@ require ( github.com/sashamelentyev/usestdlibvars v1.23.0 // indirect github.com/sivchari/nosnakecase v1.7.0 // indirect github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect github.com/timonwong/loggercheck v0.9.4 // indirect github.com/zimmski/go-mutesting v0.0.0-20210610104036-6d9217011a00 // indirect github.com/zondax/ledger-go v0.14.1 // indirect diff --git a/go.sum b/go.sum index 68532c4cdc9..675c9edb5f0 100644 --- a/go.sum +++ b/go.sum @@ -77,8 +77,6 @@ github.com/Djarvur/go-err113 v0.1.0 h1:uCRZZOdMQ0TZPHYTdYpoC0bLYJKPEHPUJ8MeAa51l github.com/Djarvur/go-err113 v0.1.0/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= github.com/GaijinEntertainment/go-exhaustruct/v2 v2.3.0 h1:+r1rSv4gvYn0wmRjC8X7IAzX8QezqtFV9m0MUHFJgts= github.com/GaijinEntertainment/go-exhaustruct/v2 v2.3.0/go.mod h1:b3g59n2Y+T5xmcxJL+UEG2f8cQploZm1mR/v6BW0mU0= -github.com/Jeffail/gabs/v2 v2.7.0 h1:Y2edYaTcE8ZpRsR2AtmPu5xQdFDIthFG0jYhu5PY8kg= -github.com/Jeffail/gabs/v2 v2.7.0/go.mod h1:dp5ocw1FvBBQYssgHsG7I1WYsiLRtkUaB1FEtSwvNUw= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= @@ -1184,8 +1182,11 @@ github.com/tetafro/godot v1.4.11/go.mod h1:LR3CJpxDVGlYOWn3ZZg1PgNZdTUvzsZWu8xaE github.com/tidwall/btree v1.6.0 h1:LDZfKfQIBHGHWSwckhXI0RPSXzlo+KYdjK7FWSqOzzg= github.com/tidwall/btree v1.6.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY= github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.14.0 h1:6aeJ0bzojgWLa82gDQHcx3S0Lr/O51I9bJ5nv6JFx5w= github.com/tidwall/gjson v1.14.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/sjson v1.2.4/go.mod h1:098SZ494YoMWPmMO6ct4dcFnqxwj9r/gF0Etp19pSNM= github.com/timakin/bodyclose v0.0.0-20221125081123-e39cf3fc478e h1:MV6KaVu/hzByHP0UvJ4HcMGE/8a6A4Rggc/0wx2AvJo= diff --git a/go.work b/go.work index ff647e81ee3..9745e53f6d7 100644 --- a/go.work +++ b/go.work @@ -9,4 +9,5 @@ use ./x/ibc-hooks use ./tests/cl-go-client use . -use ./x/epochs \ No newline at end of file + +use ./x/epochs diff --git a/proto/osmosis/poolmanager/v1beta1/query.proto b/proto/osmosis/poolmanager/v1beta1/query.proto index e2b784a3961..a71152f1707 100644 --- a/proto/osmosis/poolmanager/v1beta1/query.proto +++ b/proto/osmosis/poolmanager/v1beta1/query.proto @@ -24,14 +24,28 @@ service Query { rpc EstimateSwapExactAmountIn(EstimateSwapExactAmountInRequest) returns (EstimateSwapExactAmountInResponse) { option (google.api.http).get = - "/osmosis/gamm/v1beta1/{pool_id}/estimate/swap_exact_amount_in"; + "/osmosis/poolmanager/v1beta1/{pool_id}/estimate/swap_exact_amount_in"; + } + + rpc EstimateSinglePoolSwapExactAmountIn( + EstimateSinglePoolSwapExactAmountInRequest) + returns (EstimateSwapExactAmountInResponse) { + option (google.api.http).get = + "/osmosis/poolmanager/v1beta1/{pool_id}/estimate/single_pool_swap_exact_amount_in"; } // Estimates swap amount in given out. rpc EstimateSwapExactAmountOut(EstimateSwapExactAmountOutRequest) returns (EstimateSwapExactAmountOutResponse) { option (google.api.http).get = - "/osmosis/gamm/v1beta1/{pool_id}/estimate/swap_exact_amount_out"; + "/osmosis/poolmanager/v1beta1/{pool_id}/estimate/swap_exact_amount_out"; + } + + rpc EstimateSinglePoolSwapExactAmountOut( + EstimateSinglePoolSwapExactAmountOutRequest) + returns (EstimateSwapExactAmountOutResponse) { + option (google.api.http).get = + "/osmosis/poolmanager/v1beta1/{pool_id}/estimate_out/single_pool_swap_exact_amount_out"; } // Returns the total number of pools existing in Osmosis. @@ -64,8 +78,8 @@ message ParamsResponse { Params params = 1 [ (gogoproto.nullable) = false ]; } //=============================== EstimateSwapExactAmountIn message EstimateSwapExactAmountInRequest { - // TODO: CHANGE THIS TO RESERVED IN A PATCH RELEASE - string sender = 1 [ (gogoproto.moretags) = "yaml:\"sender\"" ]; + reserved 1; + reserved "sender"; uint64 pool_id = 2 [ (gogoproto.moretags) = "yaml:\"pool_id\"" ]; string token_in = 3 [ (gogoproto.moretags) = "yaml:\"token_in\"" ]; repeated SwapAmountInRoute routes = 4 [ @@ -74,6 +88,13 @@ message EstimateSwapExactAmountInRequest { ]; } +message EstimateSinglePoolSwapExactAmountInRequest { + uint64 pool_id = 1 [ (gogoproto.moretags) = "yaml:\"pool_id\"" ]; + string token_in = 2 [ (gogoproto.moretags) = "yaml:\"token_in\"" ]; + string token_out_denom = 3 + [ (gogoproto.moretags) = "yaml:\"token_out_denom\"" ]; +} + message EstimateSwapExactAmountInResponse { string token_out_amount = 1 [ (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", @@ -84,8 +105,8 @@ message EstimateSwapExactAmountInResponse { //=============================== EstimateSwapExactAmountOut message EstimateSwapExactAmountOutRequest { - // TODO: CHANGE THIS TO RESERVED IN A PATCH RELEASE - string sender = 1 [ (gogoproto.moretags) = "yaml:\"sender\"" ]; + reserved 1; + reserved "sender"; uint64 pool_id = 2 [ (gogoproto.moretags) = "yaml:\"pool_id\"" ]; repeated SwapAmountOutRoute routes = 3 [ (gogoproto.moretags) = "yaml:\"routes\"", @@ -94,6 +115,13 @@ message EstimateSwapExactAmountOutRequest { string token_out = 4 [ (gogoproto.moretags) = "yaml:\"token_out\"" ]; } +message EstimateSinglePoolSwapExactAmountOutRequest { + uint64 pool_id = 1 [ (gogoproto.moretags) = "yaml:\"pool_id\"" ]; + string token_in_denom = 2 + [ (gogoproto.moretags) = "yaml:\"token_in_denom\"" ]; + string token_out = 3 [ (gogoproto.moretags) = "yaml:\"token_out\"" ]; +} + message EstimateSwapExactAmountOutResponse { string token_in_amount = 1 [ (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", diff --git a/proto/osmosis/poolmanager/v1beta1/query.yml b/proto/osmosis/poolmanager/v1beta1/query.yml index a69ac9ba17c..4321837be25 100644 --- a/proto/osmosis/poolmanager/v1beta1/query.yml +++ b/proto/osmosis/poolmanager/v1beta1/query.yml @@ -18,6 +18,18 @@ queries: query_func: "k.EstimateSwapExactAmountOut" cli: cmd: "EstimateSwapExactAmountOut" + EstimateSinglePoolSwapExactAmountIn: + proto_wrapper: + query_func: "k.EstimateSinglePoolSwapExactAmountIn" + response: "*queryproto.EstimateSwapExactAmountInResponse" + cli: + cmd: "EstimateSinglePoolSwapExactAmountIn" + EstimateSinglePoolSwapExactAmountOut: + proto_wrapper: + query_func: "k.EstimateSinglePoolSwapExactAmountOutTEST" + response: "*queryproto.EstimateSwapExactAmountOutResponse" + cli: + cmd: "EstimateSinglePoolSwapExactAmountOut" NumPools: proto_wrapper: query_func: "k.NumPools" diff --git a/proto/osmosis/poolmanager/v1beta1/swap_route.proto b/proto/osmosis/poolmanager/v1beta1/swap_route.proto index 6e6b52e1044..78d6984ff41 100644 --- a/proto/osmosis/poolmanager/v1beta1/swap_route.proto +++ b/proto/osmosis/poolmanager/v1beta1/swap_route.proto @@ -14,5 +14,5 @@ message SwapAmountInRoute { message SwapAmountOutRoute { uint64 pool_id = 1 [ (gogoproto.moretags) = "yaml:\"pool_id\"" ]; string token_in_denom = 2 - [ (gogoproto.moretags) = "yaml:\"token_out_denom\"" ]; + [ (gogoproto.moretags) = "yaml:\"token_in_denom\"" ]; } diff --git a/tests/e2e/configurer/chain/commands.go b/tests/e2e/configurer/chain/commands.go index 0b88a71d072..4f46cd92723 100644 --- a/tests/e2e/configurer/chain/commands.go +++ b/tests/e2e/configurer/chain/commands.go @@ -69,17 +69,21 @@ func (n *NodeConfig) CreateConcentratedPool(from, denom1, denom2 string, tickSpa return poolID } -func (n *NodeConfig) CreateConcentratedPosition(from, lowerTick, upperTick string, token0, token1 string, token0MinAmt, token1MinAmt int64, freezeDuration string, poolId uint64) uint64 { +func (n *NodeConfig) CreateConcentratedPosition(from, lowerTick, upperTick string, token0, token1 string, token0MinAmt, token1MinAmt int64, poolId uint64) uint64 { n.LogActionF("creating concentrated position") - cmd := []string{"osmosisd", "tx", "concentratedliquidity", "create-position", lowerTick, upperTick, token0, token1, fmt.Sprintf("%d", token0MinAmt), fmt.Sprintf("%d", token1MinAmt), freezeDuration, fmt.Sprintf("--from=%s", from), fmt.Sprintf("--pool-id=%d", poolId)} - outBuf, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) + cmd := []string{"osmosisd", "tx", "concentratedliquidity", "create-position", lowerTick, upperTick, token0, token1, fmt.Sprintf("%d", token0MinAmt), fmt.Sprintf("%d", token1MinAmt), fmt.Sprintf("--from=%s", from), fmt.Sprintf("--pool-id=%d", poolId), "-o json"} + outJson, _, err := n.containerManager.ExecTxCmdWithSuccessString(n.t, n.chainId, n.Name, cmd, "code\":0") require.NoError(n.t, err) - prefix := "position_id\",\"value\":\"" - startIndex := strings.Index(outBuf.String(), prefix) + len(prefix) - endIndex := startIndex + strings.Index(outBuf.String()[startIndex:], "\"}") - positionID, err := strconv.ParseUint(outBuf.String()[startIndex:endIndex], 10, 64) + var txResponse map[string]interface{} + err = json.Unmarshal(outJson.Bytes(), &txResponse) + require.NoError(n.t, err) + + positionIDString, err := GetPositionID(txResponse) + require.NoError(n.t, err) + + positionID, err := strconv.ParseUint(positionIDString, 10, 64) require.NoError(n.t, err) n.LogActionF("successfully created concentrated position from %s to %s", lowerTick, upperTick) @@ -97,7 +101,7 @@ func (n *NodeConfig) StoreWasmCode(wasmFile, from string) { func (n *NodeConfig) WithdrawPosition(from, liquidityOut string, positionId uint64) { n.LogActionF("withdrawing liquidity from position") - cmd := []string{"osmosisd", "tx", "concentratedliquidity", "withdraw-position", fmt.Sprint(positionId), liquidityOut, fmt.Sprintf("--from=%s", from)} + cmd := []string{"osmosisd", "tx", "concentratedliquidity", "withdraw-position", fmt.Sprint(positionId), liquidityOut, fmt.Sprintf("--from=%s", from), "--gas=auto", "--gas-prices=0.1uosmo", "--gas-adjustment=1.3"} _, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) require.NoError(n.t, err) n.LogActionF("successfully withdrew %s liquidity from position %d", liquidityOut, positionId) @@ -447,3 +451,48 @@ func (n *NodeConfig) Status() (resultStatus, error) { } return result, nil } + +func GetPositionID(responseJson map[string]interface{}) (string, error) { + logs, ok := responseJson["logs"].([]interface{}) + if !ok { + return "", fmt.Errorf("logs field not found in response") + } + + if len(logs) == 0 { + return "", fmt.Errorf("empty logs field in response") + } + + log, ok := logs[0].(map[string]interface{}) + if !ok { + return "", fmt.Errorf("invalid format of logs field") + } + + events, ok := log["events"].([]interface{}) + if !ok { + return "", fmt.Errorf("events field not found in logs") + } + + for _, event := range events { + attributes, ok := event.(map[string]interface{})["attributes"].([]interface{}) + if !ok { + return "", fmt.Errorf("attributes field not found in event") + } + + for _, attr := range attributes { + switch v := attr.(type) { + case map[string]interface{}: + if v["key"] == "position_id" { + positionID, ok := v["value"].(string) + if !ok { + return "", fmt.Errorf("invalid format of position_id field") + } + return positionID, nil + } + default: + return "", fmt.Errorf("invalid type for attributes field") + } + } + } + + return "", fmt.Errorf("position_id field not found in response") +} diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go index 3b89b531896..765180fd7fd 100644 --- a/tests/e2e/e2e_test.go +++ b/tests/e2e/e2e_test.go @@ -197,7 +197,6 @@ func (s *IntegrationTestSuite) TestConcentratedLiquidity() { denom1 string = "uosmo" tickSpacing uint64 = 1 precisionFactorAtPriceOne int64 = -1 - freezeDuration = time.Duration(time.Second) swapFee = "0.01" ) @@ -230,15 +229,15 @@ func (s *IntegrationTestSuite) TestConcentratedLiquidity() { address3 := node.CreateWalletAndFund("addr3", fundTokens) // Create 2 positions for address1: overlap together, overlap with 2 address3 positions - addr1PosId := node.CreateConcentratedPosition(address1, "[-1200]", "400", fmt.Sprintf("1000%s", denom0), fmt.Sprintf("1000%s", denom1), 0, 0, freezeDuration.String(), poolID) - node.CreateConcentratedPosition(address1, "[-400]", "400", fmt.Sprintf("1000%s", denom0), fmt.Sprintf("1000%s", denom1), 0, 0, freezeDuration.String(), poolID) + addr1PosId := node.CreateConcentratedPosition(address1, "[-1200]", "400", fmt.Sprintf("1000%s", denom0), fmt.Sprintf("1000%s", denom1), 0, 0, poolID) + node.CreateConcentratedPosition(address1, "[-400]", "400", fmt.Sprintf("1000%s", denom0), fmt.Sprintf("1000%s", denom1), 0, 0, poolID) // Create 1 position for address2: does not overlap with anything, ends at maximum - addr2PosId := node.CreateConcentratedPosition(address2, "2200", fmt.Sprintf("%d", maxTick), fmt.Sprintf("1000%s", denom0), fmt.Sprintf("1000%s", denom1), 0, 0, freezeDuration.String(), poolID) + addr2PosId := node.CreateConcentratedPosition(address2, "2200", fmt.Sprintf("%d", maxTick), fmt.Sprintf("1000%s", denom0), fmt.Sprintf("1000%s", denom1), 0, 0, poolID) // Create 2 positions for address3: overlap together, overlap with 2 address1 positions, one position starts from minimum - addr3PosId := node.CreateConcentratedPosition(address3, "[-1600]", "[-200]", fmt.Sprintf("1000%s", denom0), fmt.Sprintf("1000%s", denom1), 0, 0, freezeDuration.String(), poolID) - node.CreateConcentratedPosition(address3, fmt.Sprintf("[%d]", minTick), "1400", fmt.Sprintf("1000%s", denom0), fmt.Sprintf("1000%s", denom1), 0, 0, freezeDuration.String(), poolID) + addr3PosId := node.CreateConcentratedPosition(address3, "[-1600]", "[-200]", fmt.Sprintf("1000%s", denom0), fmt.Sprintf("1000%s", denom1), 0, 0, poolID) + node.CreateConcentratedPosition(address3, fmt.Sprintf("[%d]", minTick), "1400", fmt.Sprintf("1000%s", denom0), fmt.Sprintf("1000%s", denom1), 0, 0, poolID) // get newly created positions positionsAddress1 := node.QueryConcentratedPositions(address1) diff --git a/tests/ibc-hooks/bytecode/crosschain_registry.wasm b/tests/ibc-hooks/bytecode/crosschain_registry.wasm index cac70d27607..ec14799923a 100644 Binary files a/tests/ibc-hooks/bytecode/crosschain_registry.wasm and b/tests/ibc-hooks/bytecode/crosschain_registry.wasm differ diff --git a/tests/ibc-hooks/bytecode/crosschain_swaps.wasm b/tests/ibc-hooks/bytecode/crosschain_swaps.wasm index 46b1e65d134..9dd387a8574 100644 Binary files a/tests/ibc-hooks/bytecode/crosschain_swaps.wasm and b/tests/ibc-hooks/bytecode/crosschain_swaps.wasm differ diff --git a/tests/ibc-hooks/bytecode/outpost.wasm b/tests/ibc-hooks/bytecode/outpost.wasm index 26660c5a40a..243bdf125e4 100644 Binary files a/tests/ibc-hooks/bytecode/outpost.wasm and b/tests/ibc-hooks/bytecode/outpost.wasm differ diff --git a/tests/ibc-hooks/ibc_middleware_test.go b/tests/ibc-hooks/ibc_middleware_test.go index be4251b7ea0..03aaa72215b 100644 --- a/tests/ibc-hooks/ibc_middleware_test.go +++ b/tests/ibc-hooks/ibc_middleware_test.go @@ -3,6 +3,8 @@ package ibc_hooks_test import ( "encoding/json" "fmt" + "github.com/tidwall/gjson" + "strings" "testing" "time" @@ -454,6 +456,11 @@ func (suite *HooksTestSuite) RelayPacket(packet channeltypes.Packet, direction D ack, err := ibctesting.ParseAckFromEvents(receiveResult.GetEvents()) suite.Require().NoError(err) + if strings.Contains(string(ack), "error") { + errorCtx := gjson.Get(receiveResult.Log, "0.events.#(type==ibc-acknowledgement-error)#.attributes.#(key==error-context)#.value") + fmt.Println("ibc-ack-error:", errorCtx) + } + // sender Acknowledges err = sender.AcknowledgePacket(packet, ack) suite.Require().NoError(err) @@ -501,7 +508,10 @@ func (suite *HooksTestSuite) FullSend(msg sdk.Msg, direction Direction) (*sdk.Re suite.Require().NoError(err) receiveResult, ack := suite.RelayPacket(packet, direction) - + if strings.Contains(string(ack), "error") { + errorCtx := gjson.Get(receiveResult.Log, "0.events.#(type==ibc-acknowledgement-error)#.attributes.#(key==error-context)#.value") + fmt.Println("ibc-ack-error:", errorCtx) + } return sendResult, receiveResult, string(ack), err } @@ -639,6 +649,10 @@ func (suite *HooksTestSuite) SetupCrosschainSwaps(chainName Chain) (sdk.AccAddre chain := suite.GetChain(chainName) owner := chain.SenderAccount.GetAddress() + registryAddr, _, _, _ := suite.SetupCrosschainRegistry(chainName) + suite.setChainChannelLinks(registryAddr, chainName) + fmt.Println("registryAddr", registryAddr) + // Fund the account with some uosmo and some stake bankKeeper := chain.GetOsmosisApp().BankKeeper i, ok := sdk.NewIntFromString("20000000000000000000000") @@ -654,22 +668,32 @@ func (suite *HooksTestSuite) SetupCrosschainSwaps(chainName Chain) (sdk.AccAddre // Setup contract chain.StoreContractCode(&suite.Suite, "./bytecode/swaprouter.wasm") swaprouterAddr := chain.InstantiateContract(&suite.Suite, - fmt.Sprintf(`{"owner": "%s"}`, owner), 1) + fmt.Sprintf(`{"owner": "%s"}`, owner), 2) chain.StoreContractCode(&suite.Suite, "./bytecode/crosschain_swaps.wasm") - // Configuring two prefixes for the same channel here. This is so that we can test bad acks when the receiver can't handle the receiving addr - channels := `[["osmo", "channel-0"],["juno", "channel-0"]]` crosschainAddr := chain.InstantiateContract(&suite.Suite, - fmt.Sprintf(`{"swap_contract": "%s", "channels": %s, "governor": "%s"}`, swaprouterAddr, channels, owner), - 2) + fmt.Sprintf(`{"swap_contract": "%s", "governor": "%s"}`, swaprouterAddr, owner), + 3) osmosisApp := chain.GetOsmosisApp() contractKeeper := wasmkeeper.NewDefaultPermissionKeeper(osmosisApp.WasmKeeper) - ctx := chain.GetContext() + // Configuring two prefixes for the same channel here. This is so that we can test bad acks when the receiver can't handle the receiving addr + msg := fmt.Sprintf(`{ + "modify_bech32_prefixes": { + "operations": [ + {"operation": "set", "chain_name": "osmosis", "prefix": "osmo"}, + {"operation": "set", "chain_name": "chainB", "prefix": "osmo"} + ] + } + } + `) + _, err = contractKeeper.Execute(ctx, registryAddr, owner, []byte(msg), sdk.NewCoins()) + suite.Require().NoError(err) + // ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, msg []byte, coins sdk.Coins - msg := `{"set_route":{"input_denom":"token0","output_denom":"token1","pool_route":[{"pool_id":"1","token_out_denom":"stake"},{"pool_id":"2","token_out_denom":"token1"}]}}` + msg = `{"set_route":{"input_denom":"token0","output_denom":"token1","pool_route":[{"pool_id":"1","token_out_denom":"stake"},{"pool_id":"2","token_out_denom":"token1"}]}}` _, err = contractKeeper.Execute(ctx, swaprouterAddr, owner, []byte(msg), sdk.NewCoins()) suite.Require().NoError(err) @@ -711,7 +735,7 @@ func (suite *HooksTestSuite) SetupCrosschainRegistry(chainName Chain) (sdk.AccAd suite.SetupPools(chainName, []sdk.Dec{sdk.NewDec(20), sdk.NewDec(20)}) // Setup contract - suite.chainA.StoreContractCode(&suite.Suite, "./bytecode/crosschain_registry.wasm") + chain.StoreContractCode(&suite.Suite, "./bytecode/crosschain_registry.wasm") registryAddr := chain.InstantiateContract(&suite.Suite, fmt.Sprintf(`{"owner": "%s"}`, owner), 1) _, err := sdk.Bech32ifyAddressBytes("osmo", registryAddr) suite.Require().NoError(err) @@ -847,7 +871,7 @@ func (suite *HooksTestSuite) TestCrosschainRegistry() { channelQuery := `{"get_channel_from_chain_pair": {"source_chain": "osmosis", "destination_chain": "chainB"}}` channelQueryResponse := suite.chainA.QueryContractJson(&suite.Suite, registryAddr, []byte(channelQuery)) - suite.Require().Equal("channel-0", channelQueryResponse.Data()) + suite.Require().Equal("channel-0", channelQueryResponse.Str) // Remove, set, and change links on the registry on chain A suite.modifyChainChannelLinks(registryAddr, ChainA) @@ -859,7 +883,7 @@ func (suite *HooksTestSuite) TestCrosschainRegistry() { // Unwrap token0CB and check that the path has changed channelQuery = `{"get_channel_from_chain_pair": {"source_chain": "osmosis", "destination_chain": "chainD"}}` channelQueryResponse = suite.chainA.QueryContractJson(&suite.Suite, registryAddr, []byte(channelQuery)) - suite.Require().Equal("channel-0", channelQueryResponse.Data()) + suite.Require().Equal("channel-0", channelQueryResponse.Str) } func (suite *HooksTestSuite) TestUnwrapToken() { @@ -961,7 +985,7 @@ func (suite *HooksTestSuite) TestCrosschainSwaps() { ctx := suite.chainA.GetContext() - msg := fmt.Sprintf(`{"osmosis_swap":{"output_denom":"token1","slippage":{"twap": {"window_seconds": 1, "slippage_percentage":"20"}},"receiver":"%s", "on_failed_delivery": "do_nothing"}}`, + msg := fmt.Sprintf(`{"osmosis_swap":{"output_denom":"token1","slippage":{"twap": {"window_seconds": 1, "slippage_percentage":"20"}},"receiver":"chainB/%s", "on_failed_delivery": "do_nothing"}}`, suite.chainB.SenderAccount.GetAddress(), ) res, err := contractKeeper.Execute(ctx, crosschainAddr, owner, []byte(msg), sdk.NewCoins(sdk.NewCoin("token0", sdk.NewInt(1000)))) @@ -1000,7 +1024,7 @@ func (suite *HooksTestSuite) TestCrosschainSwapsViaIBCTest() { suite.Require().Equal(int64(0), balanceToken1.Amount.Int64()) // Generate swap instructions for the contract - swapMsg := fmt.Sprintf(`{"osmosis_swap":{"output_denom":"token1","slippage":{"twap": {"window_seconds": 1, "slippage_percentage":"20"}},"receiver":"%s", "on_failed_delivery": "do_nothing", "next_memo":{}}}`, + swapMsg := fmt.Sprintf(`{"osmosis_swap":{"output_denom":"token1","slippage":{"twap": {"window_seconds": 1, "slippage_percentage":"20"}},"receiver":"chainB/%s", "on_failed_delivery": "do_nothing", "next_memo":{}}}`, receiver, ) // Generate full memo @@ -1042,13 +1066,15 @@ func (suite *HooksTestSuite) TestCrosschainSwapsViaIBCBadAck() { osmosisAppB := suite.chainB.GetOsmosisApp() balanceToken0 := osmosisAppB.BankKeeper.GetBalance(suite.chainB.GetContext(), initializer, token0IBC) - receiver := "juno1ka8v934kgrw6679fs9cuu0kesyl0ljjy4tmycx" // Will not exist on chainB + receiver := suite.chainB.SenderAccounts[5].SenderAccount.GetAddress().String() // Generate swap instructions for the contract. This will send correctly on chainA, but fail to be received on chainB recoverAddr := suite.chainA.SenderAccounts[8].SenderAccount.GetAddress() - swapMsg := fmt.Sprintf(`{"osmosis_swap":{"output_denom":"token1","slippage":{"twap": {"window_seconds": 1, "slippage_percentage":"20"}},"receiver":"%s","on_failed_delivery": {"local_recovery_addr": "%s"}}}`, - receiver, // Note that this is the chain A account, which does not exist on chain B + // we can no longer test by using a bad prefix as this is checked by the contracts. We will use a bad wasm memo to ensure the forward fails + swapMsg := fmt.Sprintf(`{"osmosis_swap":{"output_denom":"token1","slippage":{"twap": {"window_seconds": 1, "slippage_percentage":"20"}},"receiver":"chainB/%s","on_failed_delivery": {"local_recovery_addr": "%s"}, "next_memo": %s }}`, + receiver, recoverAddr, + `{"wasm": "bad wasm specifier"}`, ) // Generate full memo msg := fmt.Sprintf(`{"wasm": {"contract": "%s", "msg": %s } }`, crosschainAddr, swapMsg) @@ -1074,6 +1100,31 @@ func (suite *HooksTestSuite) TestCrosschainSwapsViaIBCBadAck() { balanceContract := osmosisAppA.BankKeeper.GetBalance(suite.chainA.GetContext(), crosschainAddr, "token1") suite.Require().Greater(balanceContract.Amount.Int64(), int64(0)) + // Send a second bad transfer from with another recovery addr + recoverAddr2 := suite.chainA.SenderAccounts[9].SenderAccount.GetAddress() + swapMsg2 := fmt.Sprintf(`{"osmosis_swap":{"output_denom":"token1","slippage":{"twap": {"window_seconds": 1, "slippage_percentage":"20"}},"receiver":"chainB/%s","on_failed_delivery": {"local_recovery_addr": "%s"}, "next_memo": %s }}`, + receiver, + recoverAddr2, + `{"wasm": "bad wasm specifier"}`, + ) + // Generate full memo + msg2 := fmt.Sprintf(`{"wasm": {"contract": "%s", "msg": %s } }`, crosschainAddr, swapMsg2) + transferMsg = NewMsgTransfer(sdk.NewCoin(token0IBC, sdk.NewInt(1000)), suite.chainB.SenderAccount.GetAddress().String(), crosschainAddr.String(), msg2) + _, receiveResult, _, err = suite.FullSend(transferMsg, BtoA) + + // We use the receive result here because the receive adds another packet to be sent back + suite.Require().NoError(err) + suite.Require().NotNil(receiveResult) + + // "Relay the packet" by executing the receive on chain B + packet, err = ibctesting.ParsePacketFromEvents(receiveResult.GetEvents()) + suite.Require().NoError(err) + _, ack2 = suite.RelayPacket(packet, AtoB) + fmt.Println(string(ack2)) + + balanceContract2 := osmosisAppA.BankKeeper.GetBalance(suite.chainA.GetContext(), crosschainAddr, "token1") + suite.Require().Greater(balanceContract2.Amount.Int64(), balanceContract.Amount.Int64()) + // check that the contract knows this state := suite.chainA.QueryContract( &suite.Suite, crosschainAddr, @@ -1084,11 +1135,16 @@ func (suite *HooksTestSuite) TestCrosschainSwapsViaIBCBadAck() { // Recover the stuck amount recoverMsg := `{"recover": {}}` contractKeeper := wasmkeeper.NewDefaultPermissionKeeper(osmosisAppA.WasmKeeper) - _, err = contractKeeper.Execute(suite.chainA.GetContext(), crosschainAddr, recoverAddr, []byte(recoverMsg), sdk.NewCoins()) + _, err = contractKeeper.Execute(suite.chainA.GetContext(), crosschainAddr, recoverAddr2, []byte(recoverMsg), sdk.NewCoins()) suite.Require().NoError(err) - balanceRecovery := osmosisAppA.BankKeeper.GetBalance(suite.chainA.GetContext(), recoverAddr, "token1") - suite.Require().Greater(balanceRecovery.Amount.Int64(), int64(0)) + balanceRecovery := osmosisAppA.BankKeeper.GetBalance(suite.chainA.GetContext(), recoverAddr2, "token1") + suite.Require().Equal(balanceContract2.Sub(balanceContract).Amount.Int64(), balanceRecovery.Amount.Int64()) + + // Calling recovery again should fail + _, err = contractKeeper.Execute(suite.chainA.GetContext(), crosschainAddr, recoverAddr2, []byte(recoverMsg), sdk.NewCoins()) + suite.Require().Error(err) + } // CrosschainSwapsViaIBCBadSwap tests that if the crosschain-swap fails, the tokens are returned to the sender @@ -1114,7 +1170,7 @@ func (suite *HooksTestSuite) TestCrosschainSwapsViaIBCBadSwap() { suite.Require().Equal(int64(0), balanceToken1.Amount.Int64()) // Generate swap instructions for the contract. The min output amount here is too high, so the swap will fail - swapMsg := fmt.Sprintf(`{"osmosis_swap":{"output_denom":"token1","slippage":{"min_output_amount":"50000"},"receiver":"%s", "on_failed_delivery": "do_nothing"}}`, + swapMsg := fmt.Sprintf(`{"osmosis_swap":{"output_denom":"token1","slippage":{"min_output_amount":"50000"},"receiver":"chainB/%s", "on_failed_delivery": "do_nothing"}}`, receiver, ) // Generate full memo @@ -1150,7 +1206,7 @@ func (suite *HooksTestSuite) TestBadCrosschainSwapsNextMemoMessages() { receiver := initializer // next_memo is set to `%s` after the SprintF. It is then format replaced in each test case. - innerMsg := fmt.Sprintf(`{"osmosis_swap":{"output_denom":"token1","slippage":{"twap": {"window_seconds": 1, "slippage_percentage":"20"}},"receiver":"%s","on_failed_delivery": {"local_recovery_addr": "%s"},"next_memo":%%s}}`, + innerMsg := fmt.Sprintf(`{"osmosis_swap":{"output_denom":"token1","slippage":{"twap": {"window_seconds": 1, "slippage_percentage":"20"}},"receiver":"chainB/%s","on_failed_delivery": {"local_recovery_addr": "%s"},"next_memo":%%s}}`, receiver, // Note that this is the chain A account, which does not exist on chain B recoverAddr) @@ -1185,7 +1241,7 @@ func (suite *HooksTestSuite) TestBadCrosschainSwapsNextMemoMessages() { } } -func (suite *HooksTestSuite) CreateIBCPoolOnChainB() { +func (suite *HooksTestSuite) CreateIBCPoolOnChainB() uint64 { chain := suite.GetChain(ChainB) acc1 := chain.SenderAccount.GetAddress() bondDenom := chain.GetOsmosisApp().StakingKeeper.BondDenom(chain.GetContext()) @@ -1221,19 +1277,19 @@ func (suite *HooksTestSuite) CreateIBCPoolOnChainB() { _, err = chain.GetOsmosisApp().GAMMKeeper.GetPoolAndPoke(chain.GetContext(), poolId) suite.Require().NoError(err) - + return poolId } -func (suite *HooksTestSuite) SetupIBCRouteOnChainB(poolmanagerAddr, owner sdk.AccAddress) { +func (suite *HooksTestSuite) SetupIBCRouteOnChainB(swaprouterAddr, owner sdk.AccAddress, poolId uint64) { chain := suite.GetChain(ChainB) denomTrace1 := transfertypes.ParseDenomTrace(transfertypes.GetPrefixedDenom("transfer", "channel-0", "token1")) token1IBC := denomTrace1.IBCDenom() - msg := fmt.Sprintf(`{"set_route":{"input_denom":"%s","output_denom":"token0","pool_route":[{"pool_id":"3","token_out_denom":"stake"},{"pool_id":"1","token_out_denom":"token0"}]}}`, - token1IBC) + msg := fmt.Sprintf(`{"set_route":{"input_denom":"%s","output_denom":"token0","pool_route":[{"pool_id":"%v","token_out_denom":"stake"},{"pool_id":"1","token_out_denom":"token0"}]}}`, + token1IBC, poolId) osmosisApp := chain.GetOsmosisApp() contractKeeper := wasmkeeper.NewDefaultPermissionKeeper(osmosisApp.WasmKeeper) - _, err := contractKeeper.Execute(chain.GetContext(), poolmanagerAddr, owner, []byte(msg), sdk.NewCoins()) + _, err := contractKeeper.Execute(chain.GetContext(), swaprouterAddr, owner, []byte(msg), sdk.NewCoins()) suite.Require().NoError(err) // Move forward one block @@ -1252,17 +1308,17 @@ func (suite *HooksTestSuite) SetupIBCRouteOnChainB(poolmanagerAddr, owner sdk.Ac // The second chain also has crosschain swaps setup and will execute a crosschain swap on receiving the response func (suite *HooksTestSuite) TestCrosschainForwardWithMemo() { initializer := suite.chainB.SenderAccount.GetAddress() - receiver := suite.chainA.SenderAccount.GetAddress() + receiver := suite.chainA.SenderAccounts[5].SenderAccount.GetAddress() _, crosschainAddrA := suite.SetupCrosschainSwaps(ChainA) - poolManagerAddrB, crosschainAddrB := suite.SetupCrosschainSwaps(ChainB) + swaprouterAddrB, crosschainAddrB := suite.SetupCrosschainSwaps(ChainB) // Send some token0 and token1 tokens to B so that there are ibc token0 to send to A and crosschain-swap, and token1 to create the pool transferMsg := NewMsgTransfer(sdk.NewCoin("token0", sdk.NewInt(500000)), suite.chainA.SenderAccount.GetAddress().String(), initializer.String(), "") suite.FullSend(transferMsg, AtoB) transferMsg1 := NewMsgTransfer(sdk.NewCoin("token1", sdk.NewInt(500000)), suite.chainA.SenderAccount.GetAddress().String(), initializer.String(), "") suite.FullSend(transferMsg1, AtoB) - suite.CreateIBCPoolOnChainB() - suite.SetupIBCRouteOnChainB(poolManagerAddrB, suite.chainB.SenderAccount.GetAddress()) + poolId := suite.CreateIBCPoolOnChainB() + suite.SetupIBCRouteOnChainB(swaprouterAddrB, suite.chainB.SenderAccount.GetAddress(), poolId) // Calculate the names of the tokens when swapped via IBC denomTrace0 := transfertypes.ParseDenomTrace(transfertypes.GetPrefixedDenom("transfer", "channel-0", "token0")) @@ -1275,11 +1331,18 @@ func (suite *HooksTestSuite) TestCrosschainForwardWithMemo() { //suite.Require().Equal(int64(0), balanceToken1.Amount.Int64()) // Generate swap instructions for the contract - nextMemo := fmt.Sprintf(`{"wasm": {"contract": "%s", "msg": {"osmosis_swap":{"output_denom":"token0","slippage":{"twap": {"window_seconds": 1, "slippage_percentage":"20"}},"receiver":"%s", "on_failed_delivery": "do_nothing"}}}}`, + // + // Note: Both chains think of themselves as "osmosis" and the other as "chainB". That is, the registry + // contracts on each test chain are not in sync. That's ok for this test, but a bit confusing. + // + // There is still an open question about how to handle verification and + // forwarding if the user has manually specified the channel and/or memo that may + // be relevant here + nextMemo := fmt.Sprintf(`{"wasm": {"contract": "%s", "msg": {"osmosis_swap":{"output_denom":"token0","slippage":{"twap": {"window_seconds": 1, "slippage_percentage":"20"}},"receiver":"ibc:channel-0/%s", "on_failed_delivery": "do_nothing"}}}}`, crosschainAddrB, receiver, ) - swapMsg := fmt.Sprintf(`{"osmosis_swap":{"output_denom":"token1","slippage":{"twap": {"window_seconds": 1, "slippage_percentage":"20"}},"receiver":"%s", "on_failed_delivery": "do_nothing", "next_memo": %s}}`, + swapMsg := fmt.Sprintf(`{"osmosis_swap":{"output_denom":"token1","slippage":{"twap": {"window_seconds": 1, "slippage_percentage":"20"}},"receiver":"ibc:channel-0/%s", "on_failed_delivery": "do_nothing", "next_memo": %s}}`, crosschainAddrB, nextMemo, ) @@ -1374,7 +1437,7 @@ func (suite *HooksTestSuite) ExecuteOutpostSwap(initializer, receiverAddr sdk.Ac func (suite *HooksTestSuite) TestOutpostSimplified() { initializer := suite.chainB.SenderAccount.GetAddress() - suite.ExecuteOutpostSwap(initializer, initializer, initializer.String()) + suite.ExecuteOutpostSwap(initializer, initializer, fmt.Sprintf(`chainB/%s`, initializer.String())) } func (suite *HooksTestSuite) TestOutpostExplicit() { diff --git a/tests/osmosisibctesting/wasm.go b/tests/osmosisibctesting/wasm.go index 5303d8c0cf5..8df13e0909a 100644 --- a/tests/osmosisibctesting/wasm.go +++ b/tests/osmosisibctesting/wasm.go @@ -5,6 +5,8 @@ import ( "fmt" "os" + "github.com/tidwall/gjson" + "github.com/stretchr/testify/require" wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" @@ -14,8 +16,6 @@ import ( transfertypes "github.com/cosmos/ibc-go/v4/modules/apps/transfer/types" "github.com/osmosis-labs/osmosis/v15/x/ibc-rate-limit/types" "github.com/stretchr/testify/suite" - - "github.com/Jeffail/gabs/v2" ) func (chain *TestChain) StoreContractCode(suite *suite.Suite, path string) { @@ -79,11 +79,12 @@ func (chain *TestChain) QueryContract(suite *suite.Suite, contract sdk.AccAddres return string(state) } -func (chain *TestChain) QueryContractJson(suite *suite.Suite, contract sdk.AccAddress, key []byte) *gabs.Container { +func (chain *TestChain) QueryContractJson(suite *suite.Suite, contract sdk.AccAddress, key []byte) gjson.Result { osmosisApp := chain.GetOsmosisApp() state, err := osmosisApp.WasmKeeper.QuerySmart(chain.GetContext(), contract, key) suite.Require().NoError(err) - json, err := gabs.ParseJSON(state) + suite.Require().True(gjson.Valid(string(state))) + json := gjson.Parse(string(state)) suite.Require().NoError(err) return json } diff --git a/wasmbinding/stargate_whitelist.go b/wasmbinding/stargate_whitelist.go index 96bab9389b0..25e446c5fcc 100644 --- a/wasmbinding/stargate_whitelist.go +++ b/wasmbinding/stargate_whitelist.go @@ -14,7 +14,7 @@ import ( stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ibctransfertypes "github.com/cosmos/ibc-go/v4/modules/apps/transfer/types" - concentratedliquidityqueryproto "github.com/osmosis-labs/osmosis/v15/x/concentrated-liquidity/types/query" + concentratedliquidityquery "github.com/osmosis-labs/osmosis/v15/x/concentrated-liquidity/types/query" downtimequerytypes "github.com/osmosis-labs/osmosis/v15/x/downtime-detector/client/queryproto" gammtypes "github.com/osmosis-labs/osmosis/v15/x/gamm/types" gammv2types "github.com/osmosis-labs/osmosis/v15/x/gamm/v2types" @@ -127,6 +127,9 @@ func init() { setWhitelistedQuery("/osmosis.poolmanager.v1beta1.Query/NumPools", &poolmanagerqueryproto.NumPoolsResponse{}) setWhitelistedQuery("/osmosis.poolmanager.v1beta1.Query/EstimateSwapExactAmountIn", &poolmanagerqueryproto.EstimateSwapExactAmountInResponse{}) setWhitelistedQuery("/osmosis.poolmanager.v1beta1.Query/EstimateSwapExactAmountOut", &poolmanagerqueryproto.EstimateSwapExactAmountOutRequest{}) + setWhitelistedQuery("/osmosis.poolmanager.v1beta1.Query/EstimateSinglePoolSwapExactAmountIn", &poolmanagerqueryproto.EstimateSwapExactAmountInResponse{}) + setWhitelistedQuery("/osmosis.poolmanager.v1beta1.Query/EstimateSinglePoolSwapExactAmountOut", &poolmanagerqueryproto.EstimateSwapExactAmountOutRequest{}) + setWhitelistedQuery("/osmosis.poolmanager.v1beta1.Query/Pool", &poolmanagerqueryproto.PoolResponse{}) // txfees setWhitelistedQuery("/osmosis.txfees.v1beta1.Query/FeeTokens", &txfeestypes.QueryFeeTokensResponse{}) @@ -150,7 +153,9 @@ func init() { setWhitelistedQuery("/osmosis.downtimedetector.v1beta1.Query/RecoveredSinceDowntimeOfLength", &downtimequerytypes.RecoveredSinceDowntimeOfLengthResponse{}) // concentrated-liquidity - setWhitelistedQuery("/osmosis.concentratedliquidity.v1beta1.Query/PositionById", &concentratedliquidityqueryproto.QueryPositionByIdResponse{}) + setWhitelistedQuery("/osmosis.concentratedliquidity.v1beta1.Query/PositionById", &concentratedliquidityquery.QueryPositionByIdResponse{}) + setWhitelistedQuery("/osmosis.concentratedliquidity.v1beta1.Query/Params", &concentratedliquidityquery.QueryParamsResponse{}) + setWhitelistedQuery("/osmosis.concentratedliquidity.v1beta1.Query/ClaimableFees", &concentratedliquidityquery.QueryClaimableFeesResponse{}) } // GetWhitelistedQuery returns the whitelisted query at the provided path. diff --git a/x/concentrated-liquidity/client/cli/tx.go b/x/concentrated-liquidity/client/cli/tx.go index 58bf9e47be2..51e22dcf2ce 100644 --- a/x/concentrated-liquidity/client/cli/tx.go +++ b/x/concentrated-liquidity/client/cli/tx.go @@ -35,9 +35,9 @@ func NewCreateConcentratedPoolCmd() (*osmocli.TxCliDesc, *clmodel.MsgCreateConce func NewCreatePositionCmd() (*osmocli.TxCliDesc, *types.MsgCreatePosition) { return &osmocli.TxCliDesc{ - Use: "create-position [lower-tick] [upper-tick] [token-0] [token-1] [token-0-min-amount] [token-1-min-amount] [freeze-duration]", + Use: "create-position [lower-tick] [upper-tick] [token-0] [token-1] [token-0-min-amount] [token-1-min-amount]", Short: "create or add to existing concentrated liquidity position", - Example: "create-position [-69082] 69082 1000000000uosmo 10000000uion 0 0 24h --pool-id 1 --from val --chain-id osmosis-1", + Example: "create-position [-69082] 69082 1000000000uosmo 10000000uion 0 0 --pool-id 1 --from val --chain-id osmosis-1", CustomFlagOverrides: poolIdFlagOverride, Flags: osmocli.FlagDesc{RequiredFlags: []*flag.FlagSet{FlagSetJustPoolId()}}, }, &types.MsgCreatePosition{} diff --git a/x/concentrated-liquidity/export_test.go b/x/concentrated-liquidity/export_test.go index 9c8ee20d6bf..95c666ea146 100644 --- a/x/concentrated-liquidity/export_test.go +++ b/x/concentrated-liquidity/export_test.go @@ -244,7 +244,7 @@ func PrepareAccumAndClaimRewards(accum accum.AccumulatorObject, positionKey stri return prepareAccumAndClaimRewards(accum, positionKey, growthOutside) } -func (k Keeper) ClaimAllIncentivesForPosition(ctx sdk.Context, positionId uint64) (sdk.Coins, error) { +func (k Keeper) ClaimAllIncentivesForPosition(ctx sdk.Context, positionId uint64) (sdk.Coins, sdk.Coins, error) { return k.claimAllIncentivesForPosition(ctx, positionId) } diff --git a/x/concentrated-liquidity/incentives.go b/x/concentrated-liquidity/incentives.go index ae1901115f7..70dfcb2eab2 100644 --- a/x/concentrated-liquidity/incentives.go +++ b/x/concentrated-liquidity/incentives.go @@ -510,34 +510,39 @@ func prepareAccumAndClaimRewards(accum accum.AccumulatorObject, positionKey stri } // claimAllIncentivesForPosition claims and returns all the incentives for a given position. -// It takes in a `forfeitIncentives` boolean to indicate whether the accrued incentives should be forfeited, in which case it -// redeposits the accrued rewards back into the accumulator as additional rewards for other participants. -func (k Keeper) claimAllIncentivesForPosition(ctx sdk.Context, positionId uint64) (sdk.Coins, error) { +// It claims all the incentives that the position is eligible for and forfeits the rest by redepositing them back +// into the accumulator (effectively redistributing them to the other LPs). +// +// Returns the amount of successfully claimed incentives and the amount of forfeited incentives. +// Returns error if the position/uptime accumulators don't exist, or if there is an issue that arises while claiming. +func (k Keeper) claimAllIncentivesForPosition(ctx sdk.Context, positionId uint64) (sdk.Coins, sdk.Coins, error) { // Retrieve the position with the given ID. position, err := k.GetPosition(ctx, positionId) if err != nil { - return sdk.Coins{}, err + return sdk.Coins{}, sdk.Coins{}, err } + // TODO: add validation to ensure this is never negative positionAge := ctx.BlockTime().Sub(position.JoinTime) // Retrieve the uptime accumulators for the position's pool. uptimeAccumulators, err := k.getUptimeAccumulators(ctx, position.PoolId) if err != nil { - return sdk.Coins{}, err + return sdk.Coins{}, sdk.Coins{}, err } // Compute uptime growth outside of the range between lower tick and upper tick uptimeGrowthOutside, err := k.GetUptimeGrowthOutsideRange(ctx, position.PoolId, position.LowerTick, position.UpperTick) if err != nil { - return sdk.Coins{}, err + return sdk.Coins{}, sdk.Coins{}, err } // Create a variable to hold the name of the position. positionName := string(types.KeyPositionId(positionId)) - // Create a variable to hold the total collected incentives for the position. + // Create variables to hold the total collected and forfeited incentives for the position. collectedIncentivesForPosition := sdk.Coins{} + forfeitedIncentivesForPosition := sdk.Coins{} supportedUptimes := types.SupportedUptimes @@ -546,27 +551,30 @@ func (k Keeper) claimAllIncentivesForPosition(ctx sdk.Context, positionId uint64 // Check if the accumulator contains the position. hasPosition, err := uptimeAccum.HasPosition(positionName) if err != nil { - return sdk.Coins{}, err + return sdk.Coins{}, sdk.Coins{}, err } // If the accumulator contains the position, claim the position's incentives. if hasPosition { collectedIncentivesForUptime, err := prepareAccumAndClaimRewards(uptimeAccum, positionName, uptimeGrowthOutside[uptimeIndex]) if err != nil { - return sdk.Coins{}, err + return sdk.Coins{}, sdk.Coins{}, err } // If the claimed incentives are forfeited, deposit them back into the accumulator to be distributed // to other qualifying positions. if positionAge < supportedUptimes[uptimeIndex] { uptimeAccum.AddToAccumulator(sdk.NewDecCoinsFromCoins(collectedIncentivesForUptime...)) + + forfeitedIncentivesForPosition = forfeitedIncentivesForPosition.Add(collectedIncentivesForUptime...) + continue } collectedIncentivesForPosition = collectedIncentivesForPosition.Add(collectedIncentivesForUptime...) } } - return collectedIncentivesForPosition, nil + return collectedIncentivesForPosition, forfeitedIncentivesForPosition, nil } // collectIncentives collects incentives for all uptime accumulators for the specified position id. @@ -583,7 +591,8 @@ func (k Keeper) collectIncentives(ctx sdk.Context, owner sdk.AccAddress, positio } // Claim all incentives for the position. - collectedIncentivesForPosition, err := k.claimAllIncentivesForPosition(ctx, position.PositionId) + // TODO: consider returning forfeited rewards as well + collectedIncentivesForPosition, _, err := k.claimAllIncentivesForPosition(ctx, position.PositionId) if err != nil { return sdk.Coins{}, err } diff --git a/x/concentrated-liquidity/incentives_test.go b/x/concentrated-liquidity/incentives_test.go index fa8cc228ddb..8fde41358a9 100644 --- a/x/concentrated-liquidity/incentives_test.go +++ b/x/concentrated-liquidity/incentives_test.go @@ -169,11 +169,11 @@ func expectedIncentivesFromRate(denom string, rate sdk.Dec, timeElapsed time.Dur // towards result. Takes in a multiplier parameter for further versatility in testing. // // Returns value as truncated sdk.Coins as the primary use of this helper is testing higher level incentives functions such as claiming. -func expectedIncentivesFromUptimeGrowth(uptimeGrowths []sdk.DecCoins, positionShares sdk.Dec, freezeDuration time.Duration, multiplier sdk.Int) sdk.Coins { +func expectedIncentivesFromUptimeGrowth(uptimeGrowths []sdk.DecCoins, positionShares sdk.Dec, timeInPool time.Duration, multiplier sdk.Int) sdk.Coins { // Sum up rewards from all inputs totalRewards := sdk.DecCoins(nil) for uptimeIndex, uptimeGrowth := range uptimeGrowths { - if freezeDuration >= types.SupportedUptimes[uptimeIndex] { + if timeInPool >= types.SupportedUptimes[uptimeIndex] { totalRewards = totalRewards.Add(uptimeGrowth...) } } @@ -717,7 +717,13 @@ func (s *KeeperTestSuite) TestCalcAccruedIncentivesForAccum() { } } +// Testing strategy: +// 1. Create a position +// 2. Let a fixed amount of time pass, enough to qualify it for some (but not all) uptimes +// 3. Let a variable amount of time pass determined by the test case +// 4. Ensure uptime accumulators and incentive records behave as expected func (s *KeeperTestSuite) TestUpdateUptimeAccumulatorsToNow() { + defaultTestUptime := types.SupportedUptimes[2] type updateAccumToNow struct { poolId uint64 accumUptime time.Duration @@ -738,7 +744,7 @@ func (s *KeeperTestSuite) TestUpdateUptimeAccumulatorsToNow() { expectedIncentiveRecords: []types.IncentiveRecord{ // We deduct incentives from the record for the period it emitted incentives - chargeIncentive(incentiveRecordOne, time.Hour), + chargeIncentive(incentiveRecordOne, defaultTestUptime+time.Hour), }, expectedPass: true, }, @@ -749,8 +755,8 @@ func (s *KeeperTestSuite) TestUpdateUptimeAccumulatorsToNow() { expectedIncentiveRecords: []types.IncentiveRecord{ // We deduct incentives from both records since there are positions for each - chargeIncentive(incentiveRecordOne, time.Hour), - chargeIncentive(incentiveRecordTwo, time.Hour), + chargeIncentive(incentiveRecordOne, defaultTestUptime+time.Hour), + chargeIncentive(incentiveRecordTwo, defaultTestUptime+time.Hour), }, expectedPass: true, }, @@ -762,9 +768,9 @@ func (s *KeeperTestSuite) TestUpdateUptimeAccumulatorsToNow() { expectedIncentiveRecords: []types.IncentiveRecord{ // We deduct incentives from each record since there are positions for all three // Note that records are in ascending order by uptime index - chargeIncentive(incentiveRecordOne, time.Hour), - chargeIncentive(incentiveRecordTwo, time.Hour), - chargeIncentive(incentiveRecordThree, time.Hour), + chargeIncentive(incentiveRecordOne, defaultTestUptime+time.Hour), + chargeIncentive(incentiveRecordTwo, defaultTestUptime+time.Hour), + chargeIncentive(incentiveRecordThree, defaultTestUptime+time.Hour), }, expectedPass: true, }, @@ -776,10 +782,12 @@ func (s *KeeperTestSuite) TestUpdateUptimeAccumulatorsToNow() { expectedIncentiveRecords: []types.IncentiveRecord{ // We only deduct from the first three incentive records since the last doesn't emit anything // Note that records are in ascending order by uptime index - chargeIncentive(incentiveRecordOne, time.Hour), - chargeIncentive(incentiveRecordTwo, time.Hour), - chargeIncentive(incentiveRecordThree, time.Hour), - incentiveRecordFour, + chargeIncentive(incentiveRecordOne, defaultTestUptime+time.Hour), + chargeIncentive(incentiveRecordTwo, defaultTestUptime+time.Hour), + chargeIncentive(incentiveRecordThree, defaultTestUptime+time.Hour), + // We charge even for uptimes the position has technically not qualified for since its liquidity is on + // the accumulator. + chargeIncentive(incentiveRecordFour, defaultTestUptime+time.Hour), }, expectedPass: true, }, @@ -805,36 +813,16 @@ func (s *KeeperTestSuite) TestUpdateUptimeAccumulatorsToNow() { // Add qualifying and non-qualifying liquidity to the pool s.FundAcc(testAddressOne, sdk.NewCoins(sdk.NewCoin(clPool.GetToken0(), testQualifyingDepositsOne), sdk.NewCoin(clPool.GetToken1(), testQualifyingDepositsOne))) - s.FundAcc(testAddressTwo, sdk.NewCoins(sdk.NewCoin(clPool.GetToken0(), testQualifyingDepositsTwo), sdk.NewCoin(clPool.GetToken1(), testQualifyingDepositsTwo))) - s.FundAcc(testAddressThree, sdk.NewCoins(sdk.NewCoin(clPool.GetToken0(), testQualifyingDepositsThree), sdk.NewCoin(clPool.GetToken1(), testQualifyingDepositsThree))) - - _, _, _, qualifyingLiquidityUptimeThree, _, err := clKeeper.CreatePosition(s.Ctx, tc.poolId, testAddressThree, testQualifyingDepositsThree, testQualifyingDepositsThree, sdk.ZeroInt(), sdk.ZeroInt(), clPool.GetCurrentTick().Int64()-1, clPool.GetCurrentTick().Int64()+1) - s.Require().NoError(err) - - s.Ctx = s.Ctx.WithBlockTime(s.Ctx.BlockTime().Add(cltypes.SupportedUptimes[2] - cltypes.SupportedUptimes[1] - cltypes.SupportedUptimes[0])) - - _, _, _, qualifyingLiquidityUptimeTwo, _, err := clKeeper.CreatePosition(s.Ctx, tc.poolId, testAddressTwo, testQualifyingDepositsTwo, testQualifyingDepositsTwo, sdk.ZeroInt(), sdk.ZeroInt(), clPool.GetCurrentTick().Int64()-1, clPool.GetCurrentTick().Int64()+1) - s.Require().NoError(err) - - s.Ctx = s.Ctx.WithBlockTime(s.Ctx.BlockTime().Add(cltypes.SupportedUptimes[1] - cltypes.SupportedUptimes[0])) - - _, _, _, qualifyingLiquidityUptimeOne, _, err := clKeeper.CreatePosition(s.Ctx, tc.poolId, testAddressOne, testQualifyingDepositsOne, testQualifyingDepositsOne, sdk.ZeroInt(), sdk.ZeroInt(), clPool.GetCurrentTick().Int64()-1, clPool.GetCurrentTick().Int64()+1) + _, _, _, qualifyingLiquidity, _, err := clKeeper.CreatePosition(s.Ctx, tc.poolId, testAddressOne, testQualifyingDepositsOne, testQualifyingDepositsOne, sdk.ZeroInt(), sdk.ZeroInt(), clPool.GetCurrentTick().Int64()-1, clPool.GetCurrentTick().Int64()+1) s.Require().NoError(err) - s.Ctx = s.Ctx.WithBlockTime(s.Ctx.BlockTime().Add(cltypes.SupportedUptimes[0])) + // Let enough time elapse to qualify the position for the first three supported uptimes + s.Ctx = s.Ctx.WithBlockTime(s.Ctx.BlockTime().Add(defaultTestUptime)) - // Note that the third position (1D freeze) qualifies for all three uptimes, the second position qualifies for the first two, - // and the first position only qualifies for the first. None of the positions qualify for any later uptimes (e.g. 1W) - qualifyingLiquidities := []sdk.Dec{ - qualifyingLiquidityUptimeOne.Add(qualifyingLiquidityUptimeTwo).Add(qualifyingLiquidityUptimeThree), - qualifyingLiquidityUptimeTwo.Add(qualifyingLiquidityUptimeThree), - qualifyingLiquidityUptimeThree, - } - - // // Let `timeElapsed` time pass - // s.Ctx = s.Ctx.WithBlockTime(defaultStartTime.Add(tc.timeElapsed)) + // Let `timeElapsed` time pass to test incentive distribution + s.Ctx = s.Ctx.WithBlockTime(s.Ctx.BlockTime().Add(tc.timeElapsed)) - // system under test + // System under test err = clKeeper.UpdateUptimeAccumulatorsToNow(s.Ctx, tc.poolId) if tc.expectedPass { @@ -848,14 +836,19 @@ func (s *KeeperTestSuite) TestUpdateUptimeAccumulatorsToNow() { newUptimeAccumValues, err := clKeeper.GetUptimeAccumulatorValues(s.Ctx, tc.poolId) s.Require().NoError(err) - // Calculate expected uptime deltas using qualifying liquidity deltas (eh can only test one incentive?) + // Calculate expected uptime deltas using qualifying liquidity deltas expectedUptimeDeltas := []sdk.DecCoins{} for uptimeIndex := range newUptimeAccumValues { - if uptimeIndex < len(tc.poolIncentiveRecords) && uptimeIndex < len(qualifyingLiquidities) { - expectedUptimeDeltas = append(expectedUptimeDeltas, sdk.NewDecCoins(expectedIncentivesFromRate(tc.poolIncentiveRecords[uptimeIndex].IncentiveDenom, tc.poolIncentiveRecords[uptimeIndex].EmissionRate, time.Hour, qualifyingLiquidities[uptimeIndex]))) - } else { - expectedUptimeDeltas = append(expectedUptimeDeltas, cl.EmptyCoins) + // Calculate expected incentives for the current uptime by emitting incentives from + // all incentive records for the it + curUptimeAccruedIncentives := cl.EmptyCoins + for _, poolRecord := range tc.poolIncentiveRecords { + if poolRecord.MinUptime == types.SupportedUptimes[uptimeIndex] { + // We set the expected accrued incentives based on the total time that has elapsed since position creation + curUptimeAccruedIncentives = curUptimeAccruedIncentives.Add(sdk.NewDecCoins(expectedIncentivesFromRate(poolRecord.IncentiveDenom, poolRecord.EmissionRate, defaultTestUptime+tc.timeElapsed, qualifyingLiquidity))...) + } } + expectedUptimeDeltas = append(expectedUptimeDeltas, curUptimeAccruedIncentives) } // Ensure that each accumulator value changes by the correct amount @@ -3194,9 +3187,14 @@ func (s *KeeperTestSuite) TestClaimAllIncentives() { initSenderBalances := s.App.BankKeeper.GetAllBalances(s.Ctx, defaultSender) initPoolBalances := s.App.BankKeeper.GetAllBalances(s.Ctx, clPool.GetAddress()) + if !tc.forfeitIncentives { + // Let enough time elapse for the position to accrue rewards for all uptimes + s.Ctx = s.Ctx.WithBlockTime(s.Ctx.BlockTime().Add(types.SupportedUptimes[len(types.SupportedUptimes)-1])) + } + // --- System under test --- - amountClaimed, err := clKeeper.ClaimAllIncentivesForPosition(s.Ctx, tc.positionIdClaim) + amountClaimed, amountForfeited, err := clKeeper.ClaimAllIncentivesForPosition(s.Ctx, tc.positionIdClaim) // --- Assertions --- @@ -3215,13 +3213,6 @@ func (s *KeeperTestSuite) TestClaimAllIncentives() { } s.Require().NoError(err) - // We expect claimed rewards to be equal to growth inside - expectedCoins := sdk.Coins(nil) - for _, growthInside := range tc.growthInside { - expectedCoins = expectedCoins.Add(sdk.NormalizeCoins(growthInside)...) - } - s.Require().Equal(expectedCoins, amountClaimed) - // Ensure that forfeited incentives were properly added to their respective accumulators if tc.forfeitIncentives { newUptimeAccumValues, err := clKeeper.GetUptimeAccumulatorValues(s.Ctx, validPoolId) @@ -3237,7 +3228,14 @@ func (s *KeeperTestSuite) TestClaimAllIncentives() { normalizedUptimeAccumDelta = normalizedUptimeAccumDelta.Add(sdk.NormalizeCoins(uptimeAccumDelta)...) } - s.Require().Equal(normalizedUptimeAccumDelta, amountClaimed) + s.Require().Equal(normalizedUptimeAccumDelta, amountClaimed.Add(amountForfeited...)) + } else { + // We expect claimed rewards to be equal to growth inside + expectedCoins := sdk.Coins(nil) + for _, growthInside := range tc.growthInside { + expectedCoins = expectedCoins.Add(sdk.NormalizeCoins(growthInside)...) + } + s.Require().Equal(expectedCoins, amountClaimed.Add(amountForfeited...)) } // Ensure balances have not been mutated diff --git a/x/concentrated-liquidity/lp.go b/x/concentrated-liquidity/lp.go index 4c6d22b17b7..5cdde503f50 100644 --- a/x/concentrated-liquidity/lp.go +++ b/x/concentrated-liquidity/lp.go @@ -129,7 +129,7 @@ func (k Keeper) withdrawPosition(ctx sdk.Context, owner sdk.AccAddress, position return sdk.Int{}, sdk.Int{}, err } - _, err = k.claimAllIncentivesForPosition(ctx, positionId) + _, err = k.collectIncentives(ctx, owner, positionId) if err != nil { return sdk.Int{}, sdk.Int{}, err } diff --git a/x/concentrated-liquidity/lp_test.go b/x/concentrated-liquidity/lp_test.go index 168c29631e1..b122ccfcf83 100644 --- a/x/concentrated-liquidity/lp_test.go +++ b/x/concentrated-liquidity/lp_test.go @@ -302,9 +302,8 @@ func (s *KeeperTestSuite) TestCreatePosition() { } func (s *KeeperTestSuite) TestWithdrawPosition() { - frozenBaseCase := *baseCase - frozenBaseCase.freezeDuration = DefaultFreezeDuration defaultJoinTime := defaultBlockTime + defaultTimeElapsed := time.Hour * 24 uptimeHelper := getExpectedUptimes() defaultUptimeGrowth := uptimeHelper.hundredTokensMultiDenom @@ -339,18 +338,6 @@ func (s *KeeperTestSuite) TestWithdrawPosition() { amount1Expected: baseCase.amount1Expected.QuoRaw(2), // 2500 usdc }, }, - "position still unfreezing": { - // setup parameters for creation a pool and position. - setupConfig: &frozenBaseCase, - - // system under test parameters - // for withdrawing a position. - sutConfigOverwrite: &lpTest{ - amount0Expected: baseCase.amount0Expected, // 0.998976 eth - amount1Expected: baseCase.amount1Expected, // 5000 usdc - joinTime: defaultJoinTime, - }, - }, "error: no position created": { // setup parameters for creation a pool and position. setupConfig: baseCase, @@ -396,11 +383,6 @@ func (s *KeeperTestSuite) TestWithdrawPosition() { // If specific configs are provided in the test case, overwrite the config with those values. mergeConfigs(&config, &sutConfigOverwrite) - // createPositionFreezeDuration := config.freezeDuration - - // if tc.createPositionFreezeOverwrite { - // createPositionFreezeDuration = 0 - // } // If a setupConfig is provided, use it to create a pool and position. pool := s.PrepareConcentratedPool() @@ -410,7 +392,7 @@ func (s *KeeperTestSuite) TestWithdrawPosition() { _, _, _, liquidityCreated, _, err := concentratedLiquidityKeeper.CreatePosition(ctx, pool.GetId(), owner, config.amount0Desired, config.amount1Desired, sdk.ZeroInt(), sdk.ZeroInt(), DefaultLowerTick, DefaultUpperTick) s.Require().NoError(err) - ctx = ctx.WithBlockTime(ctx.BlockTime().Add(time.Hour * 24)) + ctx = ctx.WithBlockTime(ctx.BlockTime().Add(defaultTimeElapsed)) // Set global fee growth to 1 ETH and charge the fee to the pool. globalFeeGrowth := sdk.NewDecCoin(ETH, sdk.NewInt(1)) @@ -431,23 +413,18 @@ func (s *KeeperTestSuite) TestWithdrawPosition() { if expectedRemainingLiquidity.IsZero() { expectedFeesClaimed = expectedFeesClaimed.Add(sdk.NewCoin(ETH, liquidityCreated.TruncateInt())) s.FundAcc(pool.GetAddress(), expectedFeesClaimed) - - if !ctx.BlockTime().Before(config.joinTime.Add(config.freezeDuration)) { - expectedIncentivesClaimed = expectedIncentivesFromUptimeGrowth(defaultUptimeGrowth, liquidityCreated, config.freezeDuration, sdk.OneInt()) - s.FundAcc(pool.GetAddress(), expectedIncentivesClaimed) - } } + // Set expected incentives and fund pool with appropriate amount + expectedIncentivesClaimed = expectedIncentivesFromUptimeGrowth(defaultUptimeGrowth, liquidityCreated, defaultTimeElapsed, sdk.OneInt()) + s.FundAcc(pool.GetAddress(), expectedIncentivesClaimed) + // Note the pool and owner balances before collecting fees. poolBalanceBeforeCollect := s.App.BankKeeper.GetAllBalances(ctx, pool.GetAddress()) ownerBalancerBeforeCollect := s.App.BankKeeper.GetAllBalances(ctx, owner) expectedBalanceDelta := expectedFeesClaimed.Add(expectedIncentivesClaimed...).Add(sdk.NewCoin(ETH, config.amount0Expected.Abs())).Add(sdk.NewCoin(USDC, config.amount1Expected.Abs())) - // if tc.setupConfig != &frozenBaseCase { - // ctx = ctx.WithBlockTime(defaultJoinTime.Add(time.Hour * 24)) - // } - // System under test. amtDenom0, amtDenom1, err := concentratedLiquidityKeeper.WithdrawPosition(ctx, owner, config.positionId, config.liquidityAmount) if config.expectedError != nil { diff --git a/x/poolmanager/client/cli/cli_test.go b/x/poolmanager/client/cli/cli_test.go index cff4bd45458..c4e93a3f0d2 100644 --- a/x/poolmanager/client/cli/cli_test.go +++ b/x/poolmanager/client/cli/cli_test.go @@ -199,9 +199,8 @@ func TestGetCmdEstimateSwapExactAmountIn(t *testing.T) { desc, _ := cli.GetCmdEstimateSwapExactAmountIn() tcs := map[string]osmocli.QueryCliTestCase[*queryproto.EstimateSwapExactAmountInRequest]{ "basic test": { - Cmd: "1 osm11vmx8jtggpd9u7qr0t8vxclycz85u925sazglr7 10stake --swap-route-pool-ids=2 --swap-route-denoms=node0token", + Cmd: "1 10stake --swap-route-pool-ids=2 --swap-route-denoms=node0token", ExpectedQuery: &queryproto.EstimateSwapExactAmountInRequest{ - Sender: "osm11vmx8jtggpd9u7qr0t8vxclycz85u925sazglr7", PoolId: 1, TokenIn: "10stake", Routes: []types.SwapAmountInRoute{{PoolId: 2, TokenOutDenom: "node0token"}}, @@ -210,13 +209,13 @@ func TestGetCmdEstimateSwapExactAmountIn(t *testing.T) { } osmocli.RunQueryTestCases(t, desc, tcs) } + func TestGetCmdEstimateSwapExactAmountOut(t *testing.T) { desc, _ := cli.GetCmdEstimateSwapExactAmountOut() tcs := map[string]osmocli.QueryCliTestCase[*queryproto.EstimateSwapExactAmountOutRequest]{ "basic test": { - Cmd: "1 osm11vmx8jtggpd9u7qr0t8vxclycz85u925sazglr7 10stake --swap-route-pool-ids=2 --swap-route-denoms=node0token", + Cmd: "1 10stake --swap-route-pool-ids=2 --swap-route-denoms=node0token", ExpectedQuery: &queryproto.EstimateSwapExactAmountOutRequest{ - Sender: "osm11vmx8jtggpd9u7qr0t8vxclycz85u925sazglr7", PoolId: 1, TokenOut: "10stake", Routes: []types.SwapAmountOutRoute{{PoolId: 2, TokenInDenom: "node0token"}}, @@ -225,6 +224,37 @@ func TestGetCmdEstimateSwapExactAmountOut(t *testing.T) { } osmocli.RunQueryTestCases(t, desc, tcs) } + +func TestGetCmdEstimateSinglePoolSwapExactAmountIn(t *testing.T) { + desc, _ := cli.GetCmdEstimateSinglePoolSwapExactAmountIn() + tcs := map[string]osmocli.QueryCliTestCase[*queryproto.EstimateSinglePoolSwapExactAmountInRequest]{ + "basic test": { + Cmd: "1 10stake node0token", + ExpectedQuery: &queryproto.EstimateSinglePoolSwapExactAmountInRequest{ + PoolId: 1, + TokenIn: "10stake", + TokenOutDenom: "node0token", + }, + }, + } + osmocli.RunQueryTestCases(t, desc, tcs) +} + +func TestGetCmdEstimateSinglePoolSwapExactAmountOut(t *testing.T) { + desc, _ := cli.GetCmdEstimateSinglePoolSwapExactAmountOut() + tcs := map[string]osmocli.QueryCliTestCase[*queryproto.EstimateSinglePoolSwapExactAmountOutRequest]{ + "basic test": { + Cmd: "1 node0token 10stake", + ExpectedQuery: &queryproto.EstimateSinglePoolSwapExactAmountOutRequest{ + PoolId: 1, + TokenInDenom: "node0token", + TokenOut: "10stake", + }, + }, + } + osmocli.RunQueryTestCases(t, desc, tcs) +} + func (s *IntegrationTestSuite) TestNewCreatePoolCmd() { val := s.network.Validators[0] diff --git a/x/poolmanager/client/cli/query.go b/x/poolmanager/client/cli/query.go index 73706444699..387f6406b0f 100644 --- a/x/poolmanager/client/cli/query.go +++ b/x/poolmanager/client/cli/query.go @@ -23,6 +23,8 @@ func GetQueryCmd() *cobra.Command { osmocli.AddQueryCmd(cmd, queryproto.NewQueryClient, GetCmdNumPools) osmocli.AddQueryCmd(cmd, queryproto.NewQueryClient, GetCmdEstimateSwapExactAmountIn) osmocli.AddQueryCmd(cmd, queryproto.NewQueryClient, GetCmdEstimateSwapExactAmountOut) + osmocli.AddQueryCmd(cmd, queryproto.NewQueryClient, GetCmdEstimateSinglePoolSwapExactAmountIn) + osmocli.AddQueryCmd(cmd, queryproto.NewQueryClient, GetCmdEstimateSinglePoolSwapExactAmountOut) osmocli.AddQueryCmd(cmd, queryproto.NewQueryClient, GetCmdSpotPrice) return cmd @@ -31,10 +33,10 @@ func GetQueryCmd() *cobra.Command { // GetCmdEstimateSwapExactAmountIn returns estimation of output coin when amount of x token input. func GetCmdEstimateSwapExactAmountIn() (*osmocli.QueryDescriptor, *queryproto.EstimateSwapExactAmountInRequest) { return &osmocli.QueryDescriptor{ - Use: "estimate-swap-exact-amount-in ", + Use: "estimate-swap-exact-amount-in ", Short: "Query estimate-swap-exact-amount-in", Long: `Query estimate-swap-exact-amount-in.{{.ExampleHeader}} -{{.CommandPrefix}} estimate-swap-exact-amount-in 1 osm11vmx8jtggpd9u7qr0t8vxclycz85u925sazglr7 1000stake --swap-route-pool-ids=2 --swap-route-pool-ids=3`, +{{.CommandPrefix}} estimate-swap-exact-amount-in 1 1000stake --swap-route-pool-ids=2 --swap-route-pool-ids=3`, ParseQuery: EstimateSwapExactAmountInParseArgs, Flags: osmocli.FlagDesc{RequiredFlags: []*flag.FlagSet{FlagSetMultihopSwapRoutes()}}, QueryFnName: "EstimateSwapExactAmountIn", @@ -45,10 +47,10 @@ func GetCmdEstimateSwapExactAmountIn() (*osmocli.QueryDescriptor, *queryproto.Es // GetCmdEstimateSwapExactAmountOut returns estimation of input coin to get exact amount of x token output. func GetCmdEstimateSwapExactAmountOut() (*osmocli.QueryDescriptor, *queryproto.EstimateSwapExactAmountOutRequest) { return &osmocli.QueryDescriptor{ - Use: "estimate-swap-exact-amount-out ", + Use: "estimate-swap-exact-amount-out ", Short: "Query estimate-swap-exact-amount-out", Long: `Query estimate-swap-exact-amount-out.{{.ExampleHeader}} -{{.CommandPrefix}} estimate-swap-exact-amount-out 1 osm11vmx8jtggpd9u7qr0t8vxclycz85u925sazglr7 1000stake --swap-route-pool-ids=2 --swap-route-pool-ids=3`, +{{.CommandPrefix}} estimate-swap-exact-amount-out 1 1000stake --swap-route-pool-ids=2 --swap-route-pool-ids=3`, ParseQuery: EstimateSwapExactAmountOutParseArgs, Flags: osmocli.FlagDesc{RequiredFlags: []*flag.FlagSet{FlagSetMultihopSwapRoutes()}}, QueryFnName: "EstimateSwapExactAmountOut", @@ -95,9 +97,8 @@ func EstimateSwapExactAmountInParseArgs(args []string, fs *flag.FlagSet) (proto. } return &queryproto.EstimateSwapExactAmountInRequest{ - Sender: args[1], // TODO: where sender is used? PoolId: uint64(poolID), // TODO: is this poolId used? - TokenIn: args[2], + TokenIn: args[1], Routes: routes, }, nil } @@ -114,9 +115,30 @@ func EstimateSwapExactAmountOutParseArgs(args []string, fs *flag.FlagSet) (proto } return &queryproto.EstimateSwapExactAmountOutRequest{ - Sender: args[1], // TODO: where sender is used? PoolId: uint64(poolID), // TODO: is this poolId used? Routes: routes, - TokenOut: args[2], + TokenOut: args[1], }, nil } + +// GetCmdEstimateSinglePoolSwapExactAmountIn returns estimation of output coin when amount of x token input. +func GetCmdEstimateSinglePoolSwapExactAmountIn() (*osmocli.QueryDescriptor, *queryproto.EstimateSinglePoolSwapExactAmountInRequest) { + return &osmocli.QueryDescriptor{ + Use: "estimate-single-pool-swap-exact-amount-in ", + Short: "Query estimate-single-pool-swap-exact-amount-in", + Long: `Query estimate-single-pool-swap-exact-amount-in.{{.ExampleHeader}} +{{.CommandPrefix}} estimate-single-pool-swap-exact-amount-in 1 1000stake uosmo`, + QueryFnName: "EstimateSinglePoolSwapExactAmountIn", + }, &queryproto.EstimateSinglePoolSwapExactAmountInRequest{} +} + +// GetCmdEstimateSinglePoolSwapExactAmountOut returns estimation of input coin to get exact amount of x token output. +func GetCmdEstimateSinglePoolSwapExactAmountOut() (*osmocli.QueryDescriptor, *queryproto.EstimateSinglePoolSwapExactAmountOutRequest) { + return &osmocli.QueryDescriptor{ + Use: "estimate-single-pool-swap-exact-amount-out ", + Short: "Query estimate-single-pool-swap-exact-amount-out", + Long: `Query estimate-single-pool-swap-exact-amount-out.{{.ExampleHeader}} +{{.CommandPrefix}} estimate-single-pool-swap-exact-amount-out 1 uosmo 1000stake`, + QueryFnName: "EstimateSinglePoolSwapExactAmountOut", + }, &queryproto.EstimateSinglePoolSwapExactAmountOutRequest{} +} diff --git a/x/poolmanager/client/cli/query_test.go b/x/poolmanager/client/cli/query_test.go index 3410d9c4202..c1fe4960891 100644 --- a/x/poolmanager/client/cli/query_test.go +++ b/x/poolmanager/client/cli/query_test.go @@ -2,6 +2,7 @@ package cli_test import ( gocontext "context" + "github.com/osmosis-labs/osmosis/v15/x/poolmanager/types" "testing" "github.com/stretchr/testify/suite" @@ -39,13 +40,21 @@ func (s *QueryTestSuite) TestQueriesNeverAlterState() { { "Query estimate swap in", "/osmosis.poolmanager.v1beta1.Query/EstimateSwapExactAmountIn", - &poolmanagerqueryproto.EstimateSwapExactAmountInRequest{}, + &poolmanagerqueryproto.EstimateSwapExactAmountInRequest{ + PoolId: 1, + TokenIn: "10bar", + Routes: types.SwapAmountInRoutes{{PoolId: 1, TokenOutDenom: "baz"}}, + }, &poolmanagerqueryproto.EstimateSwapExactAmountInResponse{}, }, { "Query estimate swap out", "/osmosis.poolmanager.v1beta1.Query/EstimateSwapExactAmountOut", - &poolmanagerqueryproto.EstimateSwapExactAmountOutRequest{}, + &poolmanagerqueryproto.EstimateSwapExactAmountOutRequest{ + PoolId: 1, + TokenOut: "6baz", + Routes: types.SwapAmountOutRoutes{{PoolId: 1, TokenInDenom: "bar"}}, + }, &poolmanagerqueryproto.EstimateSwapExactAmountOutResponse{}, }, } @@ -61,10 +70,49 @@ func (s *QueryTestSuite) TestQueriesNeverAlterState() { } } -func TestQueryTestSuite(t *testing.T) { +func (s *QueryTestSuite) TestSimplifiedQueries() { + swapIn := &poolmanagerqueryproto.EstimateSwapExactAmountInRequest{ + PoolId: 1, + TokenIn: "10bar", + Routes: types.SwapAmountInRoutes{{PoolId: 1, TokenOutDenom: "baz"}}, + } + swapOut := &poolmanagerqueryproto.EstimateSwapExactAmountOutRequest{ + PoolId: 1, + TokenOut: "6baz", + Routes: types.SwapAmountOutRoutes{{PoolId: 1, TokenInDenom: "bar"}}, + } + simplifiedSwapIn := &poolmanagerqueryproto.EstimateSinglePoolSwapExactAmountInRequest{ + PoolId: 1, + TokenIn: "10bar", + TokenOutDenom: "baz", + } + simplifiedSwapOut := &poolmanagerqueryproto.EstimateSinglePoolSwapExactAmountOutRequest{ + PoolId: 1, + TokenOut: "6baz", + TokenInDenom: "bar", + } + s.SetupSuite() + output1 := &poolmanagerqueryproto.EstimateSwapExactAmountInResponse{} + output2 := &poolmanagerqueryproto.EstimateSwapExactAmountInResponse{} + err := s.QueryHelper.Invoke(gocontext.Background(), + "/osmosis.poolmanager.v1beta1.Query/EstimateSwapExactAmountIn", swapIn, output1) + s.Require().NoError(err) + err = s.QueryHelper.Invoke(gocontext.Background(), + "/osmosis.poolmanager.v1beta1.Query/EstimateSinglePoolSwapExactAmountIn", simplifiedSwapIn, output2) + s.Require().NoError(err) + s.Require().Equal(output1, output2) - // TODO: re-enable this once poolmanager is fully merged. - t.SkipNow() + output3 := &poolmanagerqueryproto.EstimateSwapExactAmountOutResponse{} + output4 := &poolmanagerqueryproto.EstimateSwapExactAmountOutResponse{} + err = s.QueryHelper.Invoke(gocontext.Background(), + "/osmosis.poolmanager.v1beta1.Query/EstimateSwapExactAmountOut", swapOut, output3) + s.Require().NoError(err) + err = s.QueryHelper.Invoke(gocontext.Background(), + "/osmosis.poolmanager.v1beta1.Query/EstimateSinglePoolSwapExactAmountOut", simplifiedSwapOut, output4) + s.Require().NoError(err) + s.Require().Equal(output3, output4) +} +func TestQueryTestSuite(t *testing.T) { suite.Run(t, new(QueryTestSuite)) } diff --git a/x/poolmanager/client/grpc/grpc_query.go b/x/poolmanager/client/grpc/grpc_query.go index 7af5536db54..7aab48c525f 100644 --- a/x/poolmanager/client/grpc/grpc_query.go +++ b/x/poolmanager/client/grpc/grpc_query.go @@ -80,6 +80,26 @@ func (q Querier) EstimateSwapExactAmountIn(grpcCtx context.Context, return q.Q.EstimateSwapExactAmountIn(ctx, *req) } +func (q Querier) EstimateSinglePoolSwapExactAmountOut(grpcCtx context.Context, + req *queryproto.EstimateSinglePoolSwapExactAmountOutRequest, +) (*queryproto.EstimateSwapExactAmountOutResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + ctx := sdk.UnwrapSDKContext(grpcCtx) + return q.Q.EstimateSinglePoolSwapExactAmountOut(ctx, *req) +} + +func (q Querier) EstimateSinglePoolSwapExactAmountIn(grpcCtx context.Context, + req *queryproto.EstimateSinglePoolSwapExactAmountInRequest, +) (*queryproto.EstimateSwapExactAmountInResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + ctx := sdk.UnwrapSDKContext(grpcCtx) + return q.Q.EstimateSinglePoolSwapExactAmountIn(ctx, *req) +} + func (q Querier) AllPools(grpcCtx context.Context, req *queryproto.AllPoolsRequest, ) (*queryproto.AllPoolsResponse, error) { diff --git a/x/poolmanager/client/query_proto_wrap.go b/x/poolmanager/client/query_proto_wrap.go index 1ea3a0b2568..ddb9eaa6162 100644 --- a/x/poolmanager/client/query_proto_wrap.go +++ b/x/poolmanager/client/query_proto_wrap.go @@ -52,10 +52,6 @@ func (q Querier) EstimateSwapExactAmountIn(ctx sdk.Context, req queryproto.Estim // EstimateSwapExactAmountOut estimates token output amount for a swap. func (q Querier) EstimateSwapExactAmountOut(ctx sdk.Context, req queryproto.EstimateSwapExactAmountOutRequest) (*queryproto.EstimateSwapExactAmountOutResponse, error) { - if req.Sender == "" { - return nil, status.Error(codes.InvalidArgument, "address cannot be empty") - } - if req.TokenOut == "" { return nil, status.Error(codes.InvalidArgument, "invalid token") } @@ -79,6 +75,24 @@ func (q Querier) EstimateSwapExactAmountOut(ctx sdk.Context, req queryproto.Esti }, nil } +func (q Querier) EstimateSinglePoolSwapExactAmountOut(ctx sdk.Context, req queryproto.EstimateSinglePoolSwapExactAmountOutRequest) (*queryproto.EstimateSwapExactAmountOutResponse, error) { + routeReq := &queryproto.EstimateSwapExactAmountOutRequest{ + PoolId: req.PoolId, + TokenOut: req.TokenOut, + Routes: types.SwapAmountOutRoutes{{PoolId: req.PoolId, TokenInDenom: req.TokenInDenom}}, + } + return q.EstimateSwapExactAmountOut(ctx, *routeReq) +} + +func (q Querier) EstimateSinglePoolSwapExactAmountIn(ctx sdk.Context, req queryproto.EstimateSinglePoolSwapExactAmountInRequest) (*queryproto.EstimateSwapExactAmountInResponse, error) { + routeReq := &queryproto.EstimateSwapExactAmountInRequest{ + PoolId: req.PoolId, + TokenIn: req.TokenIn, + Routes: types.SwapAmountInRoutes{{PoolId: req.PoolId, TokenOutDenom: req.TokenOutDenom}}, + } + return q.EstimateSwapExactAmountIn(ctx, *routeReq) +} + // NumPools returns total number of pools. func (q Querier) NumPools(ctx sdk.Context, _ queryproto.NumPoolsRequest) (*queryproto.NumPoolsResponse, error) { return &queryproto.NumPoolsResponse{ diff --git a/x/poolmanager/client/queryproto/query.pb.go b/x/poolmanager/client/queryproto/query.pb.go index 996f0bd7dc5..10abab81dd1 100644 --- a/x/poolmanager/client/queryproto/query.pb.go +++ b/x/poolmanager/client/queryproto/query.pb.go @@ -119,8 +119,6 @@ func (m *ParamsResponse) GetParams() types.Params { // =============================== EstimateSwapExactAmountIn type EstimateSwapExactAmountInRequest struct { - // TODO: CHANGE THIS TO RESERVED IN A PATCH RELEASE - Sender string `protobuf:"bytes,1,opt,name=sender,proto3" json:"sender,omitempty" yaml:"sender"` PoolId uint64 `protobuf:"varint,2,opt,name=pool_id,json=poolId,proto3" json:"pool_id,omitempty" yaml:"pool_id"` TokenIn string `protobuf:"bytes,3,opt,name=token_in,json=tokenIn,proto3" json:"token_in,omitempty" yaml:"token_in"` Routes []types.SwapAmountInRoute `protobuf:"bytes,4,rep,name=routes,proto3" json:"routes" yaml:"routes"` @@ -159,13 +157,6 @@ func (m *EstimateSwapExactAmountInRequest) XXX_DiscardUnknown() { var xxx_messageInfo_EstimateSwapExactAmountInRequest proto.InternalMessageInfo -func (m *EstimateSwapExactAmountInRequest) GetSender() string { - if m != nil { - return m.Sender - } - return "" -} - func (m *EstimateSwapExactAmountInRequest) GetPoolId() uint64 { if m != nil { return m.PoolId @@ -187,6 +178,70 @@ func (m *EstimateSwapExactAmountInRequest) GetRoutes() []types.SwapAmountInRoute return nil } +type EstimateSinglePoolSwapExactAmountInRequest struct { + PoolId uint64 `protobuf:"varint,1,opt,name=pool_id,json=poolId,proto3" json:"pool_id,omitempty" yaml:"pool_id"` + TokenIn string `protobuf:"bytes,2,opt,name=token_in,json=tokenIn,proto3" json:"token_in,omitempty" yaml:"token_in"` + TokenOutDenom string `protobuf:"bytes,3,opt,name=token_out_denom,json=tokenOutDenom,proto3" json:"token_out_denom,omitempty" yaml:"token_out_denom"` +} + +func (m *EstimateSinglePoolSwapExactAmountInRequest) Reset() { + *m = EstimateSinglePoolSwapExactAmountInRequest{} +} +func (m *EstimateSinglePoolSwapExactAmountInRequest) String() string { + return proto.CompactTextString(m) +} +func (*EstimateSinglePoolSwapExactAmountInRequest) ProtoMessage() {} +func (*EstimateSinglePoolSwapExactAmountInRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_6256a4106f701b7d, []int{3} +} +func (m *EstimateSinglePoolSwapExactAmountInRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *EstimateSinglePoolSwapExactAmountInRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_EstimateSinglePoolSwapExactAmountInRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *EstimateSinglePoolSwapExactAmountInRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_EstimateSinglePoolSwapExactAmountInRequest.Merge(m, src) +} +func (m *EstimateSinglePoolSwapExactAmountInRequest) XXX_Size() int { + return m.Size() +} +func (m *EstimateSinglePoolSwapExactAmountInRequest) XXX_DiscardUnknown() { + xxx_messageInfo_EstimateSinglePoolSwapExactAmountInRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_EstimateSinglePoolSwapExactAmountInRequest proto.InternalMessageInfo + +func (m *EstimateSinglePoolSwapExactAmountInRequest) GetPoolId() uint64 { + if m != nil { + return m.PoolId + } + return 0 +} + +func (m *EstimateSinglePoolSwapExactAmountInRequest) GetTokenIn() string { + if m != nil { + return m.TokenIn + } + return "" +} + +func (m *EstimateSinglePoolSwapExactAmountInRequest) GetTokenOutDenom() string { + if m != nil { + return m.TokenOutDenom + } + return "" +} + type EstimateSwapExactAmountInResponse struct { TokenOutAmount github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,1,opt,name=token_out_amount,json=tokenOutAmount,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"token_out_amount" yaml:"token_out_amount"` } @@ -195,7 +250,7 @@ func (m *EstimateSwapExactAmountInResponse) Reset() { *m = EstimateSwapE func (m *EstimateSwapExactAmountInResponse) String() string { return proto.CompactTextString(m) } func (*EstimateSwapExactAmountInResponse) ProtoMessage() {} func (*EstimateSwapExactAmountInResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_6256a4106f701b7d, []int{3} + return fileDescriptor_6256a4106f701b7d, []int{4} } func (m *EstimateSwapExactAmountInResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -226,8 +281,6 @@ var xxx_messageInfo_EstimateSwapExactAmountInResponse proto.InternalMessageInfo // =============================== EstimateSwapExactAmountOut type EstimateSwapExactAmountOutRequest struct { - // TODO: CHANGE THIS TO RESERVED IN A PATCH RELEASE - Sender string `protobuf:"bytes,1,opt,name=sender,proto3" json:"sender,omitempty" yaml:"sender"` PoolId uint64 `protobuf:"varint,2,opt,name=pool_id,json=poolId,proto3" json:"pool_id,omitempty" yaml:"pool_id"` Routes []types.SwapAmountOutRoute `protobuf:"bytes,3,rep,name=routes,proto3" json:"routes" yaml:"routes"` TokenOut string `protobuf:"bytes,4,opt,name=token_out,json=tokenOut,proto3" json:"token_out,omitempty" yaml:"token_out"` @@ -237,7 +290,7 @@ func (m *EstimateSwapExactAmountOutRequest) Reset() { *m = EstimateSwapE func (m *EstimateSwapExactAmountOutRequest) String() string { return proto.CompactTextString(m) } func (*EstimateSwapExactAmountOutRequest) ProtoMessage() {} func (*EstimateSwapExactAmountOutRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_6256a4106f701b7d, []int{4} + return fileDescriptor_6256a4106f701b7d, []int{5} } func (m *EstimateSwapExactAmountOutRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -266,13 +319,6 @@ func (m *EstimateSwapExactAmountOutRequest) XXX_DiscardUnknown() { var xxx_messageInfo_EstimateSwapExactAmountOutRequest proto.InternalMessageInfo -func (m *EstimateSwapExactAmountOutRequest) GetSender() string { - if m != nil { - return m.Sender - } - return "" -} - func (m *EstimateSwapExactAmountOutRequest) GetPoolId() uint64 { if m != nil { return m.PoolId @@ -294,6 +340,70 @@ func (m *EstimateSwapExactAmountOutRequest) GetTokenOut() string { return "" } +type EstimateSinglePoolSwapExactAmountOutRequest struct { + PoolId uint64 `protobuf:"varint,1,opt,name=pool_id,json=poolId,proto3" json:"pool_id,omitempty" yaml:"pool_id"` + TokenInDenom string `protobuf:"bytes,2,opt,name=token_in_denom,json=tokenInDenom,proto3" json:"token_in_denom,omitempty" yaml:"token_in_denom"` + TokenOut string `protobuf:"bytes,3,opt,name=token_out,json=tokenOut,proto3" json:"token_out,omitempty" yaml:"token_out"` +} + +func (m *EstimateSinglePoolSwapExactAmountOutRequest) Reset() { + *m = EstimateSinglePoolSwapExactAmountOutRequest{} +} +func (m *EstimateSinglePoolSwapExactAmountOutRequest) String() string { + return proto.CompactTextString(m) +} +func (*EstimateSinglePoolSwapExactAmountOutRequest) ProtoMessage() {} +func (*EstimateSinglePoolSwapExactAmountOutRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_6256a4106f701b7d, []int{6} +} +func (m *EstimateSinglePoolSwapExactAmountOutRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *EstimateSinglePoolSwapExactAmountOutRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_EstimateSinglePoolSwapExactAmountOutRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *EstimateSinglePoolSwapExactAmountOutRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_EstimateSinglePoolSwapExactAmountOutRequest.Merge(m, src) +} +func (m *EstimateSinglePoolSwapExactAmountOutRequest) XXX_Size() int { + return m.Size() +} +func (m *EstimateSinglePoolSwapExactAmountOutRequest) XXX_DiscardUnknown() { + xxx_messageInfo_EstimateSinglePoolSwapExactAmountOutRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_EstimateSinglePoolSwapExactAmountOutRequest proto.InternalMessageInfo + +func (m *EstimateSinglePoolSwapExactAmountOutRequest) GetPoolId() uint64 { + if m != nil { + return m.PoolId + } + return 0 +} + +func (m *EstimateSinglePoolSwapExactAmountOutRequest) GetTokenInDenom() string { + if m != nil { + return m.TokenInDenom + } + return "" +} + +func (m *EstimateSinglePoolSwapExactAmountOutRequest) GetTokenOut() string { + if m != nil { + return m.TokenOut + } + return "" +} + type EstimateSwapExactAmountOutResponse struct { TokenInAmount github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,1,opt,name=token_in_amount,json=tokenInAmount,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"token_in_amount" yaml:"token_in_amount"` } @@ -302,7 +412,7 @@ func (m *EstimateSwapExactAmountOutResponse) Reset() { *m = EstimateSwap func (m *EstimateSwapExactAmountOutResponse) String() string { return proto.CompactTextString(m) } func (*EstimateSwapExactAmountOutResponse) ProtoMessage() {} func (*EstimateSwapExactAmountOutResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_6256a4106f701b7d, []int{5} + return fileDescriptor_6256a4106f701b7d, []int{7} } func (m *EstimateSwapExactAmountOutResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -339,7 +449,7 @@ func (m *NumPoolsRequest) Reset() { *m = NumPoolsRequest{} } func (m *NumPoolsRequest) String() string { return proto.CompactTextString(m) } func (*NumPoolsRequest) ProtoMessage() {} func (*NumPoolsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_6256a4106f701b7d, []int{6} + return fileDescriptor_6256a4106f701b7d, []int{8} } func (m *NumPoolsRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -376,7 +486,7 @@ func (m *NumPoolsResponse) Reset() { *m = NumPoolsResponse{} } func (m *NumPoolsResponse) String() string { return proto.CompactTextString(m) } func (*NumPoolsResponse) ProtoMessage() {} func (*NumPoolsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_6256a4106f701b7d, []int{7} + return fileDescriptor_6256a4106f701b7d, []int{9} } func (m *NumPoolsResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -421,7 +531,7 @@ func (m *PoolRequest) Reset() { *m = PoolRequest{} } func (m *PoolRequest) String() string { return proto.CompactTextString(m) } func (*PoolRequest) ProtoMessage() {} func (*PoolRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_6256a4106f701b7d, []int{8} + return fileDescriptor_6256a4106f701b7d, []int{10} } func (m *PoolRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -465,7 +575,7 @@ func (m *PoolResponse) Reset() { *m = PoolResponse{} } func (m *PoolResponse) String() string { return proto.CompactTextString(m) } func (*PoolResponse) ProtoMessage() {} func (*PoolResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_6256a4106f701b7d, []int{9} + return fileDescriptor_6256a4106f701b7d, []int{11} } func (m *PoolResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -510,7 +620,7 @@ func (m *AllPoolsRequest) Reset() { *m = AllPoolsRequest{} } func (m *AllPoolsRequest) String() string { return proto.CompactTextString(m) } func (*AllPoolsRequest) ProtoMessage() {} func (*AllPoolsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_6256a4106f701b7d, []int{10} + return fileDescriptor_6256a4106f701b7d, []int{12} } func (m *AllPoolsRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -554,7 +664,7 @@ func (m *AllPoolsResponse) Reset() { *m = AllPoolsResponse{} } func (m *AllPoolsResponse) String() string { return proto.CompactTextString(m) } func (*AllPoolsResponse) ProtoMessage() {} func (*AllPoolsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_6256a4106f701b7d, []int{11} + return fileDescriptor_6256a4106f701b7d, []int{13} } func (m *AllPoolsResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -602,7 +712,7 @@ func (m *SpotPriceRequest) Reset() { *m = SpotPriceRequest{} } func (m *SpotPriceRequest) String() string { return proto.CompactTextString(m) } func (*SpotPriceRequest) ProtoMessage() {} func (*SpotPriceRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_6256a4106f701b7d, []int{12} + return fileDescriptor_6256a4106f701b7d, []int{14} } func (m *SpotPriceRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -663,7 +773,7 @@ func (m *SpotPriceResponse) Reset() { *m = SpotPriceResponse{} } func (m *SpotPriceResponse) String() string { return proto.CompactTextString(m) } func (*SpotPriceResponse) ProtoMessage() {} func (*SpotPriceResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_6256a4106f701b7d, []int{13} + return fileDescriptor_6256a4106f701b7d, []int{15} } func (m *SpotPriceResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -703,8 +813,10 @@ func init() { proto.RegisterType((*ParamsRequest)(nil), "osmosis.poolmanager.v1beta1.ParamsRequest") proto.RegisterType((*ParamsResponse)(nil), "osmosis.poolmanager.v1beta1.ParamsResponse") proto.RegisterType((*EstimateSwapExactAmountInRequest)(nil), "osmosis.poolmanager.v1beta1.EstimateSwapExactAmountInRequest") + proto.RegisterType((*EstimateSinglePoolSwapExactAmountInRequest)(nil), "osmosis.poolmanager.v1beta1.EstimateSinglePoolSwapExactAmountInRequest") proto.RegisterType((*EstimateSwapExactAmountInResponse)(nil), "osmosis.poolmanager.v1beta1.EstimateSwapExactAmountInResponse") proto.RegisterType((*EstimateSwapExactAmountOutRequest)(nil), "osmosis.poolmanager.v1beta1.EstimateSwapExactAmountOutRequest") + proto.RegisterType((*EstimateSinglePoolSwapExactAmountOutRequest)(nil), "osmosis.poolmanager.v1beta1.EstimateSinglePoolSwapExactAmountOutRequest") proto.RegisterType((*EstimateSwapExactAmountOutResponse)(nil), "osmosis.poolmanager.v1beta1.EstimateSwapExactAmountOutResponse") proto.RegisterType((*NumPoolsRequest)(nil), "osmosis.poolmanager.v1beta1.NumPoolsRequest") proto.RegisterType((*NumPoolsResponse)(nil), "osmosis.poolmanager.v1beta1.NumPoolsResponse") @@ -721,76 +833,84 @@ func init() { } var fileDescriptor_6256a4106f701b7d = []byte{ - // 1096 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0x4f, 0x6f, 0x1b, 0xc5, - 0x1b, 0xce, 0x26, 0x8e, 0x1b, 0x4f, 0x7e, 0x89, 0x9d, 0xf9, 0xa5, 0xe0, 0xba, 0x95, 0x1d, 0xa6, - 0x50, 0x92, 0x26, 0xde, 0x55, 0x92, 0xf6, 0x52, 0xa9, 0x29, 0x71, 0x09, 0xaa, 0x2f, 0x34, 0x6c, - 0x6e, 0x48, 0x65, 0x35, 0x71, 0x06, 0xb3, 0xea, 0xee, 0xcc, 0xc6, 0x33, 0xdb, 0xc6, 0x42, 0x5c, - 0xb8, 0x71, 0xa9, 0x40, 0x48, 0xc0, 0x8d, 0x2f, 0xc1, 0x87, 0xa8, 0x38, 0xa0, 0x48, 0x5c, 0x10, - 0x07, 0x0b, 0x12, 0xf8, 0x02, 0xfe, 0x04, 0x68, 0xfe, 0xec, 0xda, 0x31, 0x64, 0xe3, 0x06, 0x71, - 0xf2, 0x7a, 0xde, 0xe7, 0x7d, 0xe7, 0x79, 0x9f, 0x77, 0xf6, 0x99, 0x05, 0x6f, 0x33, 0x1e, 0x32, - 0xee, 0x73, 0x27, 0x62, 0x2c, 0x08, 0x31, 0xc5, 0x6d, 0xd2, 0x71, 0x9e, 0xad, 0xef, 0x13, 0x81, - 0xd7, 0x9d, 0xc3, 0x98, 0x74, 0xba, 0x76, 0xd4, 0x61, 0x82, 0xc1, 0xeb, 0x06, 0x68, 0x0f, 0x01, - 0x6d, 0x03, 0xac, 0x2c, 0xb6, 0x59, 0x9b, 0x29, 0x9c, 0x23, 0x9f, 0x74, 0x4a, 0x65, 0x25, 0xab, - 0x76, 0x9b, 0x50, 0xa2, 0xca, 0x29, 0xe8, 0x9b, 0x59, 0x50, 0x71, 0x64, 0x50, 0x6b, 0x59, 0x28, - 0xfe, 0x1c, 0x47, 0x5e, 0x87, 0xc5, 0x82, 0x18, 0x74, 0xb5, 0xa5, 0xe0, 0xce, 0x3e, 0xe6, 0x24, - 0x45, 0xb5, 0x98, 0x4f, 0x4d, 0xfc, 0xf6, 0x70, 0x5c, 0xb5, 0x9a, 0xa2, 0x22, 0xdc, 0xf6, 0x29, - 0x16, 0x3e, 0x4b, 0xb0, 0x37, 0xda, 0x8c, 0xb5, 0x03, 0xe2, 0xe0, 0xc8, 0x77, 0x30, 0xa5, 0x4c, - 0xa8, 0x60, 0xc2, 0xfe, 0x9a, 0x89, 0xaa, 0x7f, 0xfb, 0xf1, 0xc7, 0x0e, 0xa6, 0xdd, 0x24, 0xa4, - 0x37, 0xf1, 0xb4, 0x38, 0xfa, 0x8f, 0x09, 0xd5, 0x46, 0xb3, 0x84, 0x1f, 0x12, 0x2e, 0x70, 0x18, - 0x69, 0x00, 0x2a, 0x82, 0xb9, 0x5d, 0xdc, 0xc1, 0x21, 0x77, 0xc9, 0x61, 0x4c, 0xb8, 0x40, 0x7b, - 0x60, 0x3e, 0x59, 0xe0, 0x11, 0xa3, 0x9c, 0xc0, 0x6d, 0x90, 0x8f, 0xd4, 0x4a, 0xd9, 0x5a, 0xb2, - 0x96, 0x67, 0x37, 0x6e, 0xda, 0x19, 0x63, 0xb2, 0x75, 0x72, 0x23, 0xf7, 0xb2, 0x57, 0x9b, 0x70, - 0x4d, 0x22, 0xfa, 0x62, 0x12, 0x2c, 0xed, 0x70, 0xe1, 0x87, 0x58, 0x90, 0xbd, 0xe7, 0x38, 0xda, - 0x39, 0xc2, 0x2d, 0xb1, 0x1d, 0xb2, 0x98, 0x8a, 0x26, 0x35, 0x3b, 0xc3, 0x15, 0x90, 0xe7, 0x84, - 0x1e, 0x90, 0x8e, 0xda, 0xa7, 0xd0, 0x58, 0xe8, 0xf7, 0x6a, 0x73, 0x5d, 0x1c, 0x06, 0xf7, 0x90, - 0x5e, 0x47, 0xae, 0x01, 0xc0, 0x55, 0x70, 0x45, 0xee, 0xed, 0xf9, 0x07, 0xe5, 0xc9, 0x25, 0x6b, - 0x39, 0xd7, 0x80, 0xfd, 0x5e, 0x6d, 0x5e, 0x63, 0x4d, 0x00, 0xb9, 0x79, 0xf9, 0xd4, 0x3c, 0x80, - 0x36, 0x98, 0x11, 0xec, 0x29, 0xa1, 0x9e, 0x4f, 0xcb, 0x53, 0xaa, 0xf2, 0xff, 0xfb, 0xbd, 0x5a, - 0x51, 0xa3, 0x93, 0x08, 0x72, 0xaf, 0xa8, 0xc7, 0x26, 0x85, 0x4f, 0x40, 0x5e, 0x8d, 0x98, 0x97, - 0x73, 0x4b, 0x53, 0xcb, 0xb3, 0x1b, 0x76, 0x66, 0xbf, 0xb2, 0x9d, 0xb4, 0x13, 0x99, 0xd6, 0xb8, - 0x2a, 0x5b, 0x1f, 0x70, 0xd7, 0xb5, 0x90, 0x6b, 0x8a, 0xa2, 0xef, 0x2c, 0xf0, 0x46, 0x86, 0x16, - 0x46, 0x74, 0x0e, 0x4a, 0x9a, 0x1a, 0x8b, 0x85, 0x87, 0x55, 0xd4, 0xc8, 0xd2, 0x94, 0xe5, 0x7f, - 0xed, 0xd5, 0x6e, 0xb5, 0x7d, 0xf1, 0x49, 0xbc, 0x6f, 0xb7, 0x58, 0x68, 0x66, 0x6e, 0x7e, 0xea, - 0xfc, 0xe0, 0xa9, 0x23, 0xba, 0x11, 0xe1, 0x76, 0x93, 0x8a, 0x7e, 0xaf, 0xf6, 0xfa, 0x70, 0xab, - 0x83, 0x7a, 0xc8, 0x9d, 0x57, 0x4b, 0x8f, 0x63, 0xb3, 0x3d, 0x7a, 0x31, 0x79, 0x2e, 0xb5, 0xc7, - 0xb1, 0xf8, 0xaf, 0xe7, 0xf4, 0x51, 0xaa, 0xfb, 0x94, 0xd2, 0xdd, 0x19, 0x53, 0x77, 0x49, 0x6d, - 0x0c, 0xe1, 0xe1, 0x3a, 0x28, 0xa4, 0x12, 0x94, 0x73, 0x8a, 0xfa, 0x62, 0xbf, 0x57, 0x2b, 0x8d, - 0xa8, 0x83, 0xdc, 0x99, 0x44, 0x16, 0xf4, 0x8d, 0x05, 0x50, 0x96, 0x20, 0x66, 0x58, 0x11, 0x28, - 0x26, 0xe7, 0xe8, 0xec, 0xac, 0x1e, 0xbd, 0xf2, 0xac, 0x5e, 0x3b, 0x7b, 0x2c, 0xd3, 0x51, 0xcd, - 0x99, 0xd3, 0x69, 0x26, 0xb5, 0x00, 0x8a, 0xef, 0xc7, 0xe1, 0x2e, 0x63, 0x41, 0xfa, 0xe2, 0xee, - 0x80, 0xd2, 0x60, 0xc9, 0x10, 0x5b, 0x07, 0x05, 0x1a, 0x87, 0x9e, 0xd4, 0x4f, 0xbf, 0xbd, 0xb9, - 0xe1, 0x96, 0xd3, 0x10, 0x72, 0x67, 0xa8, 0x49, 0x45, 0xf7, 0xc0, 0xac, 0x7c, 0x48, 0x86, 0x3d, - 0x34, 0x41, 0xeb, 0xa2, 0x09, 0xa2, 0x87, 0xe0, 0x7f, 0x3a, 0xd7, 0x6c, 0xbf, 0x09, 0x72, 0x32, - 0x62, 0x7c, 0x63, 0xd1, 0xd6, 0x66, 0x64, 0x27, 0x66, 0x64, 0x6f, 0xd3, 0x6e, 0xa3, 0xf0, 0xe3, - 0x0f, 0xf5, 0x69, 0x99, 0xd5, 0x74, 0x15, 0x18, 0x6d, 0x81, 0xe2, 0x76, 0x10, 0x0c, 0xb7, 0xf6, - 0x6a, 0x24, 0x9a, 0xa0, 0x34, 0xc8, 0x37, 0x44, 0xee, 0x82, 0xe9, 0x44, 0x83, 0xa9, 0x71, 0x98, - 0x68, 0x34, 0x3a, 0xb6, 0x40, 0x69, 0x2f, 0x62, 0x62, 0xb7, 0xe3, 0xb7, 0xc8, 0x65, 0xc8, 0xc0, - 0x1d, 0x50, 0x92, 0xd6, 0xef, 0x61, 0xce, 0x89, 0xf0, 0x0e, 0x08, 0x65, 0xa1, 0x7a, 0x13, 0x0a, - 0x8d, 0xeb, 0x83, 0x17, 0x73, 0x14, 0x81, 0xdc, 0x79, 0xb9, 0xb4, 0x2d, 0x57, 0xde, 0x95, 0x0b, - 0xf0, 0x11, 0x58, 0x38, 0x8c, 0x99, 0x38, 0x5b, 0x47, 0x7b, 0xd9, 0x8d, 0x7e, 0xaf, 0x56, 0xd6, - 0x75, 0xfe, 0x06, 0x41, 0x6e, 0x51, 0xad, 0x0d, 0x2a, 0xa1, 0x26, 0x58, 0x18, 0xea, 0xc8, 0xc8, - 0x73, 0x07, 0x00, 0x1e, 0x31, 0xe1, 0x45, 0x72, 0xd5, 0x1c, 0xdd, 0xab, 0xfd, 0x5e, 0x6d, 0xc1, - 0xbc, 0xd5, 0x69, 0x0c, 0xb9, 0x05, 0x9e, 0x64, 0x6f, 0xfc, 0x54, 0x00, 0xd3, 0x1f, 0xc8, 0x2b, - 0x0d, 0xbe, 0xb0, 0x40, 0x5e, 0xfb, 0x3e, 0xbc, 0x3d, 0xc6, 0xe5, 0x60, 0x94, 0xac, 0xac, 0x8e, - 0x85, 0xd5, 0x1c, 0xd1, 0xea, 0xe7, 0x3f, 0xff, 0xf1, 0xf5, 0xe4, 0x5b, 0xf0, 0xa6, 0x93, 0x75, - 0x41, 0x1b, 0x16, 0xbf, 0x5b, 0xe0, 0xda, 0xb9, 0x1e, 0x0b, 0xef, 0x67, 0xee, 0x7b, 0xd1, 0x3d, - 0x55, 0xd9, 0xba, 0x6c, 0xba, 0xe9, 0x64, 0x47, 0x75, 0xf2, 0x00, 0xde, 0x4f, 0x3b, 0x69, 0xe3, - 0x30, 0x4c, 0x5b, 0xf8, 0xd4, 0x1c, 0xa2, 0xcf, 0x1c, 0x62, 0x4a, 0xe9, 0xcf, 0x0e, 0x22, 0x8b, - 0x19, 0x3f, 0xf0, 0x7c, 0x0a, 0xff, 0xb4, 0x40, 0xe5, 0x7c, 0x6f, 0x82, 0x97, 0x62, 0x39, 0x70, - 0xf9, 0xca, 0x83, 0x4b, 0xe7, 0x9b, 0x36, 0xdf, 0x53, 0x6d, 0xbe, 0x03, 0xb7, 0xfe, 0x45, 0x9b, - 0x2c, 0x16, 0xf0, 0x5b, 0x0b, 0xcc, 0x24, 0xc6, 0x06, 0xd7, 0x32, 0x59, 0x8d, 0x58, 0x62, 0xa5, - 0x3e, 0x26, 0xda, 0x30, 0xb6, 0x15, 0xe3, 0x65, 0x78, 0x2b, 0xf3, 0x88, 0xa5, 0xae, 0x09, 0xbf, - 0xb2, 0x40, 0x4e, 0x56, 0x80, 0xcb, 0xd9, 0x07, 0x79, 0x60, 0xa7, 0x95, 0x95, 0x31, 0x90, 0x86, - 0xcd, 0x1d, 0xc5, 0xc6, 0x86, 0x6b, 0x99, 0x6c, 0x14, 0x93, 0x81, 0x98, 0x4a, 0xad, 0xc4, 0xfe, - 0x2e, 0x50, 0x6b, 0xc4, 0x65, 0x2f, 0x50, 0x6b, 0xd4, 0x53, 0xc7, 0x54, 0x0b, 0x07, 0x41, 0x5d, - 0xab, 0xf5, 0xbd, 0x05, 0x0a, 0xa9, 0xf5, 0xc0, 0xec, 0xcd, 0x46, 0x4d, 0xb7, 0x62, 0x8f, 0x0b, - 0x37, 0xe4, 0x36, 0x15, 0xb9, 0x3a, 0x5c, 0xfd, 0x47, 0x72, 0x23, 0xa2, 0x39, 0xca, 0xdb, 0x78, - 0xe3, 0xc9, 0xcb, 0x93, 0xaa, 0x75, 0x7c, 0x52, 0xb5, 0x7e, 0x3b, 0xa9, 0x5a, 0x5f, 0x9e, 0x56, - 0x27, 0x8e, 0x4f, 0xab, 0x13, 0xbf, 0x9c, 0x56, 0x27, 0x3e, 0x7c, 0x38, 0x74, 0x7f, 0x9b, 0x82, - 0xf5, 0x00, 0xef, 0xf3, 0xb4, 0xfa, 0xb3, 0xf5, 0xbb, 0xce, 0xd1, 0x99, 0x3d, 0x5a, 0x81, 0x4f, - 0xa8, 0xd0, 0xdf, 0xfc, 0xfa, 0x9a, 0xc9, 0xab, 0x9f, 0xcd, 0xbf, 0x02, 0x00, 0x00, 0xff, 0xff, - 0x31, 0xe5, 0x33, 0xa9, 0x0f, 0x0d, 0x00, 0x00, + // 1231 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x57, 0xcf, 0x6f, 0x1b, 0x45, + 0x14, 0xce, 0x3a, 0x8e, 0x1b, 0x4f, 0x9a, 0xd8, 0x19, 0x92, 0xe2, 0xb8, 0x95, 0x37, 0x4c, 0x4a, + 0x71, 0x93, 0x78, 0x57, 0x49, 0xda, 0x4b, 0x25, 0x5a, 0xc5, 0x6d, 0x68, 0x8c, 0x80, 0x86, 0x8d, + 0x10, 0x08, 0x29, 0x58, 0x1b, 0x67, 0x30, 0xab, 0x7a, 0x77, 0x36, 0x9e, 0xd9, 0x36, 0x11, 0xe2, + 0xc2, 0x89, 0x13, 0x2a, 0x42, 0x02, 0x6e, 0xfc, 0x13, 0xfc, 0x11, 0x15, 0x12, 0x28, 0x12, 0x17, + 0xc4, 0xc1, 0x42, 0x09, 0x07, 0x0e, 0xbd, 0xe0, 0x23, 0x27, 0xb4, 0x33, 0xb3, 0xeb, 0x1f, 0x24, + 0xeb, 0x8d, 0xd3, 0x53, 0xd6, 0xf3, 0xbe, 0xf7, 0xe6, 0xfb, 0xde, 0x7c, 0x9a, 0x37, 0x01, 0x6f, + 0x10, 0x6a, 0x13, 0x6a, 0x51, 0xdd, 0x25, 0xa4, 0x61, 0x9b, 0x8e, 0x59, 0xc7, 0x4d, 0xfd, 0xc9, + 0xca, 0x2e, 0x66, 0xe6, 0x8a, 0xbe, 0xef, 0xe1, 0xe6, 0xa1, 0xe6, 0x36, 0x09, 0x23, 0xf0, 0xaa, + 0x04, 0x6a, 0x5d, 0x40, 0x4d, 0x02, 0xf3, 0x33, 0x75, 0x52, 0x27, 0x1c, 0xa7, 0xfb, 0x5f, 0x22, + 0x25, 0x7f, 0x33, 0xaa, 0x76, 0x1d, 0x3b, 0x98, 0x97, 0xe3, 0xd0, 0xeb, 0x51, 0x50, 0x76, 0x20, + 0x51, 0xcb, 0x51, 0x28, 0xfa, 0xd4, 0x74, 0xab, 0x4d, 0xe2, 0x31, 0x2c, 0xd1, 0x85, 0x1a, 0x87, + 0xeb, 0xbb, 0x26, 0xc5, 0x21, 0xaa, 0x46, 0x2c, 0x47, 0xc6, 0x17, 0xbb, 0xe3, 0x5c, 0x6a, 0x88, + 0x72, 0xcd, 0xba, 0xe5, 0x98, 0xcc, 0x22, 0x01, 0xf6, 0x5a, 0x9d, 0x90, 0x7a, 0x03, 0xeb, 0xa6, + 0x6b, 0xe9, 0xa6, 0xe3, 0x10, 0xc6, 0x83, 0x01, 0xfb, 0x39, 0x19, 0xe5, 0xbf, 0x76, 0xbd, 0x4f, + 0x75, 0xd3, 0x39, 0x0c, 0x42, 0x62, 0x93, 0xaa, 0x68, 0x8e, 0xf8, 0x21, 0x43, 0x6a, 0x7f, 0x16, + 0xb3, 0x6c, 0x4c, 0x99, 0x69, 0xbb, 0x02, 0x80, 0x32, 0x60, 0x72, 0xcb, 0x6c, 0x9a, 0x36, 0x35, + 0xf0, 0xbe, 0x87, 0x29, 0x43, 0xdb, 0x60, 0x2a, 0x58, 0xa0, 0x2e, 0x71, 0x28, 0x86, 0xeb, 0x20, + 0xe5, 0xf2, 0x95, 0x9c, 0x32, 0xaf, 0x14, 0x27, 0x56, 0x17, 0xb4, 0x88, 0x63, 0xd2, 0x44, 0x72, + 0x39, 0xf9, 0xbc, 0xa5, 0x8e, 0x18, 0x32, 0x11, 0xbd, 0x50, 0xc0, 0xfc, 0x06, 0x65, 0x96, 0x6d, + 0x32, 0xbc, 0xfd, 0xd4, 0x74, 0x37, 0x0e, 0xcc, 0x1a, 0x5b, 0xb7, 0x89, 0xe7, 0xb0, 0x8a, 0x23, + 0x77, 0x86, 0x4b, 0xe0, 0x92, 0x5f, 0xb0, 0x6a, 0xed, 0xe5, 0x12, 0xf3, 0x4a, 0x31, 0x59, 0x86, + 0xed, 0x96, 0x3a, 0x75, 0x68, 0xda, 0x8d, 0x3b, 0x48, 0x06, 0x90, 0x91, 0xf2, 0xbf, 0x2a, 0x7b, + 0x50, 0x03, 0xe3, 0x8c, 0x3c, 0xc6, 0x4e, 0xd5, 0x72, 0x72, 0xa3, 0xf3, 0x4a, 0x31, 0x5d, 0x7e, + 0xa5, 0xdd, 0x52, 0x33, 0x02, 0x1d, 0x44, 0x90, 0x71, 0x89, 0x7f, 0x56, 0x1c, 0xb8, 0x03, 0x52, + 0xfc, 0xdc, 0x68, 0x2e, 0x39, 0x3f, 0x5a, 0x9c, 0x58, 0xd5, 0x22, 0x45, 0xf8, 0x1c, 0x43, 0x7a, + 0x7e, 0x5a, 0x79, 0xd6, 0xd7, 0xd3, 0x6e, 0xa9, 0x93, 0x62, 0x07, 0x51, 0x0b, 0x19, 0xb2, 0xe8, + 0xdb, 0xc9, 0x71, 0x25, 0x9b, 0x30, 0x52, 0x14, 0x3b, 0x7b, 0xb8, 0x89, 0x7e, 0x51, 0xc0, 0x62, + 0x28, 0xd7, 0x72, 0xea, 0x0d, 0xbc, 0x45, 0x48, 0x23, 0x8e, 0x70, 0xe5, 0x5c, 0xc2, 0x13, 0x31, + 0x84, 0x97, 0x41, 0x46, 0xac, 0x12, 0x8f, 0x55, 0xf7, 0xb0, 0x43, 0x6c, 0xd9, 0xaf, 0x7c, 0xbb, + 0xa5, 0x5e, 0xe9, 0x4e, 0x0b, 0x01, 0xc8, 0x98, 0xe4, 0x2b, 0x8f, 0x3c, 0xf6, 0x80, 0xff, 0xfe, + 0x41, 0x01, 0xaf, 0x45, 0x1c, 0x9f, 0xf4, 0x09, 0x05, 0xd9, 0x4e, 0x21, 0x93, 0x47, 0xb9, 0x9e, + 0x74, 0xb9, 0xe2, 0x37, 0xef, 0x8f, 0x96, 0x7a, 0xa3, 0x6e, 0xb1, 0xcf, 0xbc, 0x5d, 0xad, 0x46, + 0x6c, 0x69, 0x53, 0xf9, 0xa7, 0x44, 0xf7, 0x1e, 0xeb, 0xec, 0xd0, 0xc5, 0x54, 0xab, 0x38, 0xac, + 0xdd, 0x52, 0x5f, 0xed, 0x27, 0x26, 0xea, 0x21, 0x63, 0x2a, 0x60, 0x26, 0xb6, 0x47, 0xff, 0x9c, + 0x4d, 0xed, 0x91, 0xc7, 0x86, 0xb2, 0xd6, 0x27, 0xa1, 0x55, 0x46, 0xb9, 0x55, 0xf4, 0x98, 0x56, + 0xf1, 0xf7, 0x8b, 0xe1, 0x15, 0xb8, 0x02, 0xd2, 0xa1, 0xae, 0x5c, 0x92, 0x37, 0x68, 0xa6, 0xdd, + 0x52, 0xb3, 0x7d, 0x92, 0x91, 0x31, 0x1e, 0x68, 0xed, 0xb3, 0xd7, 0xaf, 0x0a, 0x58, 0x1a, 0x68, + 0xaf, 0xd3, 0xd5, 0x0f, 0xf6, 0xd7, 0x3d, 0x30, 0x15, 0xb8, 0x48, 0xda, 0x45, 0xb8, 0x6c, 0xae, + 0xdd, 0x52, 0x67, 0x7b, 0x5d, 0x16, 0xb8, 0xe5, 0xb2, 0xf4, 0x1a, 0x37, 0x4b, 0xaf, 0xbc, 0xd1, + 0x38, 0xf2, 0xd0, 0x77, 0x0a, 0x40, 0x51, 0x87, 0x28, 0x0d, 0xe6, 0x06, 0x56, 0xb6, 0x9c, 0x5e, + 0x7f, 0x6d, 0x9e, 0xdb, 0x5f, 0x57, 0xfa, 0x94, 0x04, 0xf6, 0x9a, 0x94, 0x52, 0xa4, 0xbb, 0xa6, + 0x41, 0xe6, 0x3d, 0xcf, 0xf6, 0xbb, 0x1b, 0xde, 0x8f, 0x1b, 0x20, 0xdb, 0x59, 0x92, 0xc4, 0x56, + 0x40, 0xda, 0xf1, 0xec, 0xaa, 0xdf, 0x41, 0x2a, 0x5b, 0xdc, 0x25, 0x39, 0x0c, 0x21, 0x63, 0xdc, + 0x91, 0xa9, 0xe8, 0x0e, 0x98, 0xf0, 0x3f, 0x86, 0x39, 0x22, 0x74, 0x1f, 0x5c, 0x16, 0xb9, 0x72, + 0xfb, 0x35, 0x90, 0xf4, 0x23, 0xf2, 0x7a, 0x9e, 0xd1, 0xc4, 0x9d, 0xaf, 0x05, 0x77, 0xbe, 0xb6, + 0xee, 0x1c, 0x96, 0xd3, 0x3f, 0xff, 0x54, 0x1a, 0xf3, 0xb3, 0x2a, 0x06, 0x07, 0xa3, 0xbb, 0x20, + 0xb3, 0xde, 0x68, 0x74, 0x4b, 0x3b, 0x1f, 0x89, 0x0a, 0xc8, 0x76, 0xf2, 0x25, 0x91, 0xdb, 0x60, + 0x2c, 0xe8, 0xc1, 0x68, 0x1c, 0x26, 0x02, 0x8d, 0x8e, 0x14, 0x90, 0xdd, 0x76, 0x09, 0xdb, 0x6a, + 0x5a, 0x35, 0x3c, 0x94, 0x69, 0x37, 0x40, 0xd6, 0x9f, 0xb0, 0x55, 0x93, 0x52, 0xcc, 0x7a, 0x6c, + 0x7b, 0xb5, 0x73, 0x99, 0xf4, 0x23, 0x90, 0x31, 0xe5, 0x2f, 0xad, 0xfb, 0x2b, 0xc2, 0xba, 0x9b, + 0x60, 0x7a, 0xdf, 0x23, 0xac, 0xb7, 0x8e, 0xb0, 0xf0, 0xb5, 0x76, 0x4b, 0xcd, 0x89, 0x3a, 0xff, + 0x83, 0x20, 0x23, 0xc3, 0xd7, 0x3a, 0x95, 0x50, 0x05, 0x4c, 0x77, 0x29, 0x92, 0xed, 0xb9, 0x05, + 0x00, 0x75, 0x09, 0xab, 0xba, 0xfe, 0xaa, 0xb4, 0xee, 0x6c, 0xbb, 0xa5, 0x4e, 0x8b, 0xba, 0x9d, + 0x18, 0x32, 0xd2, 0x34, 0xc8, 0x5e, 0xfd, 0x77, 0x12, 0x8c, 0xbd, 0xef, 0xbf, 0x1c, 0xe0, 0xd7, + 0x0a, 0x48, 0x89, 0xf1, 0x0a, 0x17, 0x63, 0xcc, 0x60, 0xd9, 0xc9, 0xfc, 0x52, 0x2c, 0xac, 0xe0, + 0x88, 0x96, 0xbe, 0xfc, 0xed, 0xaf, 0x6f, 0x13, 0xaf, 0xc3, 0x05, 0x3d, 0xea, 0x1d, 0x24, 0x59, + 0xfc, 0xad, 0x80, 0xb9, 0x33, 0xe7, 0x02, 0x7c, 0x33, 0x72, 0xdf, 0x41, 0xcf, 0x81, 0xfc, 0xdd, + 0x61, 0xd3, 0xa5, 0x92, 0x77, 0xb8, 0x92, 0xb7, 0xe0, 0x83, 0x48, 0x25, 0x9f, 0x4b, 0x2f, 0x7d, + 0xa1, 0x63, 0x59, 0x51, 0x3c, 0xf2, 0xb0, 0x5f, 0x53, 0x5e, 0x0b, 0x55, 0xcb, 0x81, 0x5f, 0x25, + 0xc0, 0x42, 0x8c, 0x91, 0x0e, 0x1f, 0xc6, 0x63, 0x3d, 0xf0, 0x51, 0x70, 0x61, 0xf9, 0x1f, 0x71, + 0xf9, 0x06, 0xdc, 0x3a, 0xb7, 0x7c, 0xce, 0x8d, 0xdf, 0x58, 0xd5, 0x53, 0x5b, 0xf1, 0x42, 0x01, + 0xf9, 0xb3, 0x6f, 0x6b, 0x38, 0x14, 0xf1, 0xce, 0xb4, 0xca, 0xdf, 0x1b, 0x3a, 0x5f, 0x2a, 0x7f, + 0x97, 0x2b, 0x7f, 0x08, 0x37, 0x2e, 0x7e, 0xf0, 0xc4, 0x63, 0xf0, 0x59, 0x02, 0x5c, 0x8f, 0x33, + 0x6d, 0xe1, 0xe6, 0xc5, 0x8e, 0xfe, 0x65, 0xb6, 0x60, 0x87, 0xb7, 0xe0, 0x43, 0xf8, 0xc1, 0x39, + 0x5b, 0xe0, 0x0b, 0x1e, 0x60, 0x00, 0xbf, 0x25, 0xdf, 0x2b, 0x60, 0x3c, 0x18, 0x82, 0x70, 0x39, + 0x92, 0x6c, 0xdf, 0xf8, 0xcc, 0x97, 0x62, 0xa2, 0xa5, 0x10, 0x8d, 0x0b, 0x29, 0xc2, 0x1b, 0x91, + 0x42, 0xc2, 0x09, 0x0b, 0xbf, 0x51, 0x40, 0xd2, 0xaf, 0x00, 0x8b, 0xd1, 0x97, 0x5e, 0x67, 0xf4, + 0xe6, 0x6f, 0xc6, 0x40, 0x4a, 0x36, 0xb7, 0x38, 0x1b, 0x0d, 0x2e, 0x47, 0xb2, 0xe1, 0x4c, 0x3a, + 0xcd, 0xe5, 0xdd, 0x0a, 0x46, 0xe5, 0x80, 0x6e, 0xf5, 0x4d, 0xe4, 0x01, 0xdd, 0xea, 0x9f, 0xbf, + 0x31, 0xbb, 0x65, 0x36, 0x1a, 0x25, 0xd1, 0xad, 0x1f, 0x15, 0x90, 0x0e, 0xc7, 0x14, 0x8c, 0xde, + 0xac, 0x7f, 0x40, 0xe7, 0xb5, 0xb8, 0x70, 0x49, 0x6e, 0x8d, 0x93, 0x2b, 0xc1, 0xa5, 0x53, 0xc9, + 0xf5, 0x35, 0x4d, 0xe7, 0x73, 0x90, 0x96, 0x77, 0x9e, 0x1f, 0x17, 0x94, 0xa3, 0xe3, 0x82, 0xf2, + 0xe7, 0x71, 0x41, 0x79, 0x76, 0x52, 0x18, 0x39, 0x3a, 0x29, 0x8c, 0xfc, 0x7e, 0x52, 0x18, 0xf9, + 0xf8, 0x7e, 0xd7, 0x5b, 0x4f, 0x16, 0x2c, 0x35, 0xcc, 0x5d, 0x1a, 0x56, 0x7f, 0xb2, 0x72, 0x5b, + 0x3f, 0xe8, 0xd9, 0xa3, 0xd6, 0xb0, 0xb0, 0xc3, 0xc4, 0xbf, 0xe1, 0xe2, 0x49, 0x92, 0xe2, 0x7f, + 0xd6, 0xfe, 0x0b, 0x00, 0x00, 0xff, 0xff, 0xb7, 0x5a, 0xe7, 0x8d, 0xa2, 0x10, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -808,8 +928,10 @@ type QueryClient interface { Params(ctx context.Context, in *ParamsRequest, opts ...grpc.CallOption) (*ParamsResponse, error) // Estimates swap amount out given in. EstimateSwapExactAmountIn(ctx context.Context, in *EstimateSwapExactAmountInRequest, opts ...grpc.CallOption) (*EstimateSwapExactAmountInResponse, error) + EstimateSinglePoolSwapExactAmountIn(ctx context.Context, in *EstimateSinglePoolSwapExactAmountInRequest, opts ...grpc.CallOption) (*EstimateSwapExactAmountInResponse, error) // Estimates swap amount in given out. EstimateSwapExactAmountOut(ctx context.Context, in *EstimateSwapExactAmountOutRequest, opts ...grpc.CallOption) (*EstimateSwapExactAmountOutResponse, error) + EstimateSinglePoolSwapExactAmountOut(ctx context.Context, in *EstimateSinglePoolSwapExactAmountOutRequest, opts ...grpc.CallOption) (*EstimateSwapExactAmountOutResponse, error) // Returns the total number of pools existing in Osmosis. NumPools(ctx context.Context, in *NumPoolsRequest, opts ...grpc.CallOption) (*NumPoolsResponse, error) // Pool returns the Pool specified by the pool id @@ -847,6 +969,15 @@ func (c *queryClient) EstimateSwapExactAmountIn(ctx context.Context, in *Estimat return out, nil } +func (c *queryClient) EstimateSinglePoolSwapExactAmountIn(ctx context.Context, in *EstimateSinglePoolSwapExactAmountInRequest, opts ...grpc.CallOption) (*EstimateSwapExactAmountInResponse, error) { + out := new(EstimateSwapExactAmountInResponse) + err := c.cc.Invoke(ctx, "/osmosis.poolmanager.v1beta1.Query/EstimateSinglePoolSwapExactAmountIn", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *queryClient) EstimateSwapExactAmountOut(ctx context.Context, in *EstimateSwapExactAmountOutRequest, opts ...grpc.CallOption) (*EstimateSwapExactAmountOutResponse, error) { out := new(EstimateSwapExactAmountOutResponse) err := c.cc.Invoke(ctx, "/osmosis.poolmanager.v1beta1.Query/EstimateSwapExactAmountOut", in, out, opts...) @@ -856,6 +987,15 @@ func (c *queryClient) EstimateSwapExactAmountOut(ctx context.Context, in *Estima return out, nil } +func (c *queryClient) EstimateSinglePoolSwapExactAmountOut(ctx context.Context, in *EstimateSinglePoolSwapExactAmountOutRequest, opts ...grpc.CallOption) (*EstimateSwapExactAmountOutResponse, error) { + out := new(EstimateSwapExactAmountOutResponse) + err := c.cc.Invoke(ctx, "/osmosis.poolmanager.v1beta1.Query/EstimateSinglePoolSwapExactAmountOut", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *queryClient) NumPools(ctx context.Context, in *NumPoolsRequest, opts ...grpc.CallOption) (*NumPoolsResponse, error) { out := new(NumPoolsResponse) err := c.cc.Invoke(ctx, "/osmosis.poolmanager.v1beta1.Query/NumPools", in, out, opts...) @@ -897,8 +1037,10 @@ type QueryServer interface { Params(context.Context, *ParamsRequest) (*ParamsResponse, error) // Estimates swap amount out given in. EstimateSwapExactAmountIn(context.Context, *EstimateSwapExactAmountInRequest) (*EstimateSwapExactAmountInResponse, error) + EstimateSinglePoolSwapExactAmountIn(context.Context, *EstimateSinglePoolSwapExactAmountInRequest) (*EstimateSwapExactAmountInResponse, error) // Estimates swap amount in given out. EstimateSwapExactAmountOut(context.Context, *EstimateSwapExactAmountOutRequest) (*EstimateSwapExactAmountOutResponse, error) + EstimateSinglePoolSwapExactAmountOut(context.Context, *EstimateSinglePoolSwapExactAmountOutRequest) (*EstimateSwapExactAmountOutResponse, error) // Returns the total number of pools existing in Osmosis. NumPools(context.Context, *NumPoolsRequest) (*NumPoolsResponse, error) // Pool returns the Pool specified by the pool id @@ -920,9 +1062,15 @@ func (*UnimplementedQueryServer) Params(ctx context.Context, req *ParamsRequest) func (*UnimplementedQueryServer) EstimateSwapExactAmountIn(ctx context.Context, req *EstimateSwapExactAmountInRequest) (*EstimateSwapExactAmountInResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method EstimateSwapExactAmountIn not implemented") } +func (*UnimplementedQueryServer) EstimateSinglePoolSwapExactAmountIn(ctx context.Context, req *EstimateSinglePoolSwapExactAmountInRequest) (*EstimateSwapExactAmountInResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method EstimateSinglePoolSwapExactAmountIn not implemented") +} func (*UnimplementedQueryServer) EstimateSwapExactAmountOut(ctx context.Context, req *EstimateSwapExactAmountOutRequest) (*EstimateSwapExactAmountOutResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method EstimateSwapExactAmountOut not implemented") } +func (*UnimplementedQueryServer) EstimateSinglePoolSwapExactAmountOut(ctx context.Context, req *EstimateSinglePoolSwapExactAmountOutRequest) (*EstimateSwapExactAmountOutResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method EstimateSinglePoolSwapExactAmountOut not implemented") +} func (*UnimplementedQueryServer) NumPools(ctx context.Context, req *NumPoolsRequest) (*NumPoolsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method NumPools not implemented") } @@ -976,6 +1124,24 @@ func _Query_EstimateSwapExactAmountIn_Handler(srv interface{}, ctx context.Conte return interceptor(ctx, in, info, handler) } +func _Query_EstimateSinglePoolSwapExactAmountIn_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(EstimateSinglePoolSwapExactAmountInRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).EstimateSinglePoolSwapExactAmountIn(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/osmosis.poolmanager.v1beta1.Query/EstimateSinglePoolSwapExactAmountIn", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).EstimateSinglePoolSwapExactAmountIn(ctx, req.(*EstimateSinglePoolSwapExactAmountInRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _Query_EstimateSwapExactAmountOut_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(EstimateSwapExactAmountOutRequest) if err := dec(in); err != nil { @@ -994,6 +1160,24 @@ func _Query_EstimateSwapExactAmountOut_Handler(srv interface{}, ctx context.Cont return interceptor(ctx, in, info, handler) } +func _Query_EstimateSinglePoolSwapExactAmountOut_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(EstimateSinglePoolSwapExactAmountOutRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).EstimateSinglePoolSwapExactAmountOut(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/osmosis.poolmanager.v1beta1.Query/EstimateSinglePoolSwapExactAmountOut", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).EstimateSinglePoolSwapExactAmountOut(ctx, req.(*EstimateSinglePoolSwapExactAmountOutRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _Query_NumPools_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(NumPoolsRequest) if err := dec(in); err != nil { @@ -1078,10 +1262,18 @@ var _Query_serviceDesc = grpc.ServiceDesc{ MethodName: "EstimateSwapExactAmountIn", Handler: _Query_EstimateSwapExactAmountIn_Handler, }, + { + MethodName: "EstimateSinglePoolSwapExactAmountIn", + Handler: _Query_EstimateSinglePoolSwapExactAmountIn_Handler, + }, { MethodName: "EstimateSwapExactAmountOut", Handler: _Query_EstimateSwapExactAmountOut_Handler, }, + { + MethodName: "EstimateSinglePoolSwapExactAmountOut", + Handler: _Query_EstimateSinglePoolSwapExactAmountOut_Handler, + }, { MethodName: "NumPools", Handler: _Query_NumPools_Handler, @@ -1205,12 +1397,47 @@ func (m *EstimateSwapExactAmountInRequest) MarshalToSizedBuffer(dAtA []byte) (in i-- dAtA[i] = 0x10 } - if len(m.Sender) > 0 { - i -= len(m.Sender) - copy(dAtA[i:], m.Sender) - i = encodeVarintQuery(dAtA, i, uint64(len(m.Sender))) + return len(dAtA) - i, nil +} + +func (m *EstimateSinglePoolSwapExactAmountInRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *EstimateSinglePoolSwapExactAmountInRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *EstimateSinglePoolSwapExactAmountInRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.TokenOutDenom) > 0 { + i -= len(m.TokenOutDenom) + copy(dAtA[i:], m.TokenOutDenom) + i = encodeVarintQuery(dAtA, i, uint64(len(m.TokenOutDenom))) + i-- + dAtA[i] = 0x1a + } + if len(m.TokenIn) > 0 { + i -= len(m.TokenIn) + copy(dAtA[i:], m.TokenIn) + i = encodeVarintQuery(dAtA, i, uint64(len(m.TokenIn))) + i-- + dAtA[i] = 0x12 + } + if m.PoolId != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.PoolId)) i-- - dAtA[i] = 0xa + dAtA[i] = 0x8 } return len(dAtA) - i, nil } @@ -1294,12 +1521,47 @@ func (m *EstimateSwapExactAmountOutRequest) MarshalToSizedBuffer(dAtA []byte) (i i-- dAtA[i] = 0x10 } - if len(m.Sender) > 0 { - i -= len(m.Sender) - copy(dAtA[i:], m.Sender) - i = encodeVarintQuery(dAtA, i, uint64(len(m.Sender))) + return len(dAtA) - i, nil +} + +func (m *EstimateSinglePoolSwapExactAmountOutRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *EstimateSinglePoolSwapExactAmountOutRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *EstimateSinglePoolSwapExactAmountOutRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.TokenOut) > 0 { + i -= len(m.TokenOut) + copy(dAtA[i:], m.TokenOut) + i = encodeVarintQuery(dAtA, i, uint64(len(m.TokenOut))) i-- - dAtA[i] = 0xa + dAtA[i] = 0x1a + } + if len(m.TokenInDenom) > 0 { + i -= len(m.TokenInDenom) + copy(dAtA[i:], m.TokenInDenom) + i = encodeVarintQuery(dAtA, i, uint64(len(m.TokenInDenom))) + i-- + dAtA[i] = 0x12 + } + if m.PoolId != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.PoolId)) + i-- + dAtA[i] = 0x8 } return len(dAtA) - i, nil } @@ -1625,10 +1887,6 @@ func (m *EstimateSwapExactAmountInRequest) Size() (n int) { } var l int _ = l - l = len(m.Sender) - if l > 0 { - n += 1 + l + sovQuery(uint64(l)) - } if m.PoolId != 0 { n += 1 + sovQuery(uint64(m.PoolId)) } @@ -1645,6 +1903,26 @@ func (m *EstimateSwapExactAmountInRequest) Size() (n int) { return n } +func (m *EstimateSinglePoolSwapExactAmountInRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.PoolId != 0 { + n += 1 + sovQuery(uint64(m.PoolId)) + } + l = len(m.TokenIn) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = len(m.TokenOutDenom) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + func (m *EstimateSwapExactAmountInResponse) Size() (n int) { if m == nil { return 0 @@ -1662,10 +1940,6 @@ func (m *EstimateSwapExactAmountOutRequest) Size() (n int) { } var l int _ = l - l = len(m.Sender) - if l > 0 { - n += 1 + l + sovQuery(uint64(l)) - } if m.PoolId != 0 { n += 1 + sovQuery(uint64(m.PoolId)) } @@ -1682,17 +1956,37 @@ func (m *EstimateSwapExactAmountOutRequest) Size() (n int) { return n } -func (m *EstimateSwapExactAmountOutResponse) Size() (n int) { +func (m *EstimateSinglePoolSwapExactAmountOutRequest) Size() (n int) { if m == nil { return 0 } var l int _ = l - l = m.TokenInAmount.Size() - n += 1 + l + sovQuery(uint64(l)) - return n -} - + if m.PoolId != 0 { + n += 1 + sovQuery(uint64(m.PoolId)) + } + l = len(m.TokenInDenom) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = len(m.TokenOut) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *EstimateSwapExactAmountOutResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.TokenInAmount.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + func (m *NumPoolsRequest) Size() (n int) { if m == nil { return 0 @@ -1967,9 +2261,28 @@ func (m *EstimateSwapExactAmountInRequest) Unmarshal(dAtA []byte) error { return fmt.Errorf("proto: EstimateSwapExactAmountInRequest: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { - case 1: + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field PoolId", wireType) + } + m.PoolId = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.PoolId |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Sender", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field TokenIn", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -1997,9 +2310,93 @@ func (m *EstimateSwapExactAmountInRequest) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Sender = string(dAtA[iNdEx:postIndex]) + m.TokenIn = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 2: + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Routes", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Routes = append(m.Routes, types.SwapAmountInRoute{}) + if err := m.Routes[len(m.Routes)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *EstimateSinglePoolSwapExactAmountInRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: EstimateSinglePoolSwapExactAmountInRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EstimateSinglePoolSwapExactAmountInRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field PoolId", wireType) } @@ -2018,7 +2415,7 @@ func (m *EstimateSwapExactAmountInRequest) Unmarshal(dAtA []byte) error { break } } - case 3: + case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field TokenIn", wireType) } @@ -2050,11 +2447,11 @@ func (m *EstimateSwapExactAmountInRequest) Unmarshal(dAtA []byte) error { } m.TokenIn = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 4: + case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Routes", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field TokenOutDenom", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowQuery @@ -2064,25 +2461,23 @@ func (m *EstimateSwapExactAmountInRequest) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLengthQuery } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthQuery } if postIndex > l { return io.ErrUnexpectedEOF } - m.Routes = append(m.Routes, types.SwapAmountInRoute{}) - if err := m.Routes[len(m.Routes)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } + m.TokenOutDenom = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex @@ -2218,9 +2613,62 @@ func (m *EstimateSwapExactAmountOutRequest) Unmarshal(dAtA []byte) error { return fmt.Errorf("proto: EstimateSwapExactAmountOutRequest: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { - case 1: + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field PoolId", wireType) + } + m.PoolId = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.PoolId |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Routes", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Routes = append(m.Routes, types.SwapAmountOutRoute{}) + if err := m.Routes[len(m.Routes)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Sender", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field TokenOut", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -2248,9 +2696,59 @@ func (m *EstimateSwapExactAmountOutRequest) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Sender = string(dAtA[iNdEx:postIndex]) + m.TokenOut = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 2: + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *EstimateSinglePoolSwapExactAmountOutRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: EstimateSinglePoolSwapExactAmountOutRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EstimateSinglePoolSwapExactAmountOutRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field PoolId", wireType) } @@ -2269,11 +2767,11 @@ func (m *EstimateSwapExactAmountOutRequest) Unmarshal(dAtA []byte) error { break } } - case 3: + case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Routes", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field TokenInDenom", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowQuery @@ -2283,27 +2781,25 @@ func (m *EstimateSwapExactAmountOutRequest) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLengthQuery } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthQuery } if postIndex > l { return io.ErrUnexpectedEOF } - m.Routes = append(m.Routes, types.SwapAmountOutRoute{}) - if err := m.Routes[len(m.Routes)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } + m.TokenInDenom = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 4: + case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field TokenOut", wireType) } diff --git a/x/poolmanager/client/queryproto/query.pb.gw.go b/x/poolmanager/client/queryproto/query.pb.gw.go index 9e78b4e101c..12f69e4355f 100644 --- a/x/poolmanager/client/queryproto/query.pb.gw.go +++ b/x/poolmanager/client/queryproto/query.pb.gw.go @@ -123,6 +123,78 @@ func local_request_Query_EstimateSwapExactAmountIn_0(ctx context.Context, marsha } +var ( + filter_Query_EstimateSinglePoolSwapExactAmountIn_0 = &utilities.DoubleArray{Encoding: map[string]int{"pool_id": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} +) + +func request_Query_EstimateSinglePoolSwapExactAmountIn_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq EstimateSinglePoolSwapExactAmountInRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["pool_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pool_id") + } + + protoReq.PoolId, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pool_id", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_EstimateSinglePoolSwapExactAmountIn_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.EstimateSinglePoolSwapExactAmountIn(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_EstimateSinglePoolSwapExactAmountIn_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq EstimateSinglePoolSwapExactAmountInRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["pool_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pool_id") + } + + protoReq.PoolId, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pool_id", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_EstimateSinglePoolSwapExactAmountIn_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.EstimateSinglePoolSwapExactAmountIn(ctx, &protoReq) + return msg, metadata, err + +} + var ( filter_Query_EstimateSwapExactAmountOut_0 = &utilities.DoubleArray{Encoding: map[string]int{"pool_id": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} ) @@ -195,6 +267,78 @@ func local_request_Query_EstimateSwapExactAmountOut_0(ctx context.Context, marsh } +var ( + filter_Query_EstimateSinglePoolSwapExactAmountOut_0 = &utilities.DoubleArray{Encoding: map[string]int{"pool_id": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} +) + +func request_Query_EstimateSinglePoolSwapExactAmountOut_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq EstimateSinglePoolSwapExactAmountOutRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["pool_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pool_id") + } + + protoReq.PoolId, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pool_id", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_EstimateSinglePoolSwapExactAmountOut_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.EstimateSinglePoolSwapExactAmountOut(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_EstimateSinglePoolSwapExactAmountOut_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq EstimateSinglePoolSwapExactAmountOutRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["pool_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pool_id") + } + + protoReq.PoolId, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pool_id", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_EstimateSinglePoolSwapExactAmountOut_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.EstimateSinglePoolSwapExactAmountOut(ctx, &protoReq) + return msg, metadata, err + +} + func request_Query_NumPools_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq NumPoolsRequest var metadata runtime.ServerMetadata @@ -427,6 +571,29 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) + mux.Handle("GET", pattern_Query_EstimateSinglePoolSwapExactAmountIn_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_EstimateSinglePoolSwapExactAmountIn_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_EstimateSinglePoolSwapExactAmountIn_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("GET", pattern_Query_EstimateSwapExactAmountOut_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -450,6 +617,29 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) + mux.Handle("GET", pattern_Query_EstimateSinglePoolSwapExactAmountOut_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_EstimateSinglePoolSwapExactAmountOut_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_EstimateSinglePoolSwapExactAmountOut_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("GET", pattern_Query_NumPools_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -623,6 +813,26 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) + mux.Handle("GET", pattern_Query_EstimateSinglePoolSwapExactAmountIn_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_EstimateSinglePoolSwapExactAmountIn_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_EstimateSinglePoolSwapExactAmountIn_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("GET", pattern_Query_EstimateSwapExactAmountOut_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -643,6 +853,26 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) + mux.Handle("GET", pattern_Query_EstimateSinglePoolSwapExactAmountOut_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_EstimateSinglePoolSwapExactAmountOut_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_EstimateSinglePoolSwapExactAmountOut_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("GET", pattern_Query_NumPools_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -729,9 +959,13 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie var ( pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"osmosis", "poolmanager", "v1beta1", "Params"}, "", runtime.AssumeColonVerbOpt(false))) - pattern_Query_EstimateSwapExactAmountIn_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4, 2, 5}, []string{"osmosis", "gamm", "v1beta1", "pool_id", "estimate", "swap_exact_amount_in"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_EstimateSwapExactAmountIn_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4, 2, 5}, []string{"osmosis", "poolmanager", "v1beta1", "pool_id", "estimate", "swap_exact_amount_in"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_EstimateSinglePoolSwapExactAmountIn_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4, 2, 5}, []string{"osmosis", "poolmanager", "v1beta1", "pool_id", "estimate", "single_pool_swap_exact_amount_in"}, "", runtime.AssumeColonVerbOpt(false))) - pattern_Query_EstimateSwapExactAmountOut_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4, 2, 5}, []string{"osmosis", "gamm", "v1beta1", "pool_id", "estimate", "swap_exact_amount_out"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_EstimateSwapExactAmountOut_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4, 2, 5}, []string{"osmosis", "poolmanager", "v1beta1", "pool_id", "estimate", "swap_exact_amount_out"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_EstimateSinglePoolSwapExactAmountOut_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4, 2, 5}, []string{"osmosis", "poolmanager", "v1beta1", "pool_id", "estimate_out", "single_pool_swap_exact_amount_out"}, "", runtime.AssumeColonVerbOpt(false))) pattern_Query_NumPools_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"osmosis", "poolmanager", "v1beta1", "num_pools"}, "", runtime.AssumeColonVerbOpt(false))) @@ -747,8 +981,12 @@ var ( forward_Query_EstimateSwapExactAmountIn_0 = runtime.ForwardResponseMessage + forward_Query_EstimateSinglePoolSwapExactAmountIn_0 = runtime.ForwardResponseMessage + forward_Query_EstimateSwapExactAmountOut_0 = runtime.ForwardResponseMessage + forward_Query_EstimateSinglePoolSwapExactAmountOut_0 = runtime.ForwardResponseMessage + forward_Query_NumPools_0 = runtime.ForwardResponseMessage forward_Query_Pool_0 = runtime.ForwardResponseMessage diff --git a/x/poolmanager/types/swap_route.pb.go b/x/poolmanager/types/swap_route.pb.go index 22cb811f5c1..44fd3f26206 100644 --- a/x/poolmanager/types/swap_route.pb.go +++ b/x/poolmanager/types/swap_route.pb.go @@ -77,7 +77,7 @@ func (m *SwapAmountInRoute) GetTokenOutDenom() string { type SwapAmountOutRoute struct { PoolId uint64 `protobuf:"varint,1,opt,name=pool_id,json=poolId,proto3" json:"pool_id,omitempty" yaml:"pool_id"` - TokenInDenom string `protobuf:"bytes,2,opt,name=token_in_denom,json=tokenInDenom,proto3" json:"token_in_denom,omitempty" yaml:"token_out_denom"` + TokenInDenom string `protobuf:"bytes,2,opt,name=token_in_denom,json=tokenInDenom,proto3" json:"token_in_denom,omitempty" yaml:"token_in_denom"` } func (m *SwapAmountOutRoute) Reset() { *m = SwapAmountOutRoute{} } @@ -137,7 +137,7 @@ func init() { } var fileDescriptor_cddd97a9a05492a8 = []byte{ - // 292 bytes of a gzipped FileDescriptorProto + // 304 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0xc9, 0x2f, 0xce, 0xcd, 0x2f, 0xce, 0x2c, 0xd6, 0x2f, 0xc8, 0xcf, 0xcf, 0xc9, 0x4d, 0xcc, 0x4b, 0x4c, 0x4f, 0x2d, 0xd2, 0x2f, 0x33, 0x4c, 0x4a, 0x2d, 0x49, 0x34, 0xd4, 0x2f, 0x2e, 0x4f, 0x2c, 0x88, 0x2f, 0xca, 0x2f, @@ -149,14 +149,14 @@ var fileDescriptor_cddd97a9a05492a8 = []byte{ 0x42, 0x29, 0x88, 0x0d, 0xc4, 0xf2, 0x4c, 0x11, 0x72, 0xe2, 0xe2, 0x2f, 0xc9, 0xcf, 0x4e, 0xcd, 0x8b, 0xcf, 0x2f, 0x2d, 0x89, 0x4f, 0x49, 0xcd, 0xcb, 0xcf, 0x95, 0x60, 0x52, 0x60, 0xd4, 0xe0, 0x74, 0x92, 0xfa, 0x74, 0x4f, 0x5e, 0x0c, 0xa2, 0x09, 0x4d, 0x81, 0x52, 0x10, 0x2f, 0x58, 0xc4, - 0xbf, 0xb4, 0xc4, 0x05, 0xcc, 0x6f, 0x66, 0xe4, 0x12, 0x42, 0x38, 0xc3, 0xbf, 0xb4, 0x84, 0x0c, - 0x77, 0x38, 0x70, 0xf1, 0x41, 0xac, 0xc9, 0xcc, 0x23, 0xda, 0x19, 0x3c, 0x60, 0x11, 0xcf, 0x3c, - 0xb0, 0x2b, 0x9c, 0x02, 0x4f, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, - 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, 0xf1, 0x58, 0x8e, 0x21, 0xca, 0x3c, - 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x57, 0x1f, 0x1a, 0xc8, 0xba, 0x39, 0x89, - 0x49, 0xc5, 0x30, 0x8e, 0x7e, 0x99, 0xa1, 0xa9, 0x7e, 0x05, 0x4a, 0x2c, 0x95, 0x54, 0x16, 0xa4, - 0x16, 0x27, 0xb1, 0x81, 0x83, 0xd9, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x2c, 0x01, 0x52, 0x52, - 0xc9, 0x01, 0x00, 0x00, + 0xbf, 0xb4, 0xc4, 0x05, 0xcc, 0x6f, 0x62, 0xe4, 0x12, 0x42, 0x38, 0xc3, 0xbf, 0xb4, 0x84, 0x0c, + 0x77, 0xd8, 0x73, 0xf1, 0x41, 0xac, 0xc9, 0xcc, 0x43, 0x71, 0x86, 0xe4, 0xa7, 0x7b, 0xf2, 0xa2, + 0xc8, 0xce, 0x80, 0xc9, 0x2b, 0x05, 0xf1, 0x80, 0x05, 0x3c, 0xf3, 0xc0, 0x8e, 0x70, 0x0a, 0x3c, + 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4, 0x18, 0x27, 0x3c, 0x96, 0x63, + 0xb8, 0xf0, 0x58, 0x8e, 0xe1, 0xc6, 0x63, 0x39, 0x86, 0x28, 0xf3, 0xf4, 0xcc, 0x92, 0x8c, 0xd2, + 0x24, 0xbd, 0xe4, 0xfc, 0x5c, 0x7d, 0x68, 0x18, 0xeb, 0xe6, 0x24, 0x26, 0x15, 0xc3, 0x38, 0xfa, + 0x65, 0x86, 0xa6, 0xfa, 0x15, 0x28, 0x91, 0x54, 0x52, 0x59, 0x90, 0x5a, 0x9c, 0xc4, 0x06, 0x0e, + 0x65, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x7f, 0x11, 0x78, 0xbd, 0xc8, 0x01, 0x00, 0x00, } func (m *SwapAmountInRoute) Marshal() (dAtA []byte, err error) { diff --git a/x/protorev/client/cli/query.go b/x/protorev/client/cli/query.go index d54604d31e0..e549e29cc2d 100644 --- a/x/protorev/client/cli/query.go +++ b/x/protorev/client/cli/query.go @@ -28,6 +28,7 @@ func NewCmdQuery() *cobra.Command { osmocli.AddQueryCmd(cmd, types.NewQueryClient, NewQueryMaxPoolPointsPerBlockCmd) osmocli.AddQueryCmd(cmd, types.NewQueryClient, NewQueryBaseDenomsCmd) osmocli.AddQueryCmd(cmd, types.NewQueryClient, NewQueryEnabledCmd) + osmocli.AddQueryCmd(cmd, types.NewQueryClient, NewQueryPoolWeightsCmd) return cmd } @@ -86,7 +87,7 @@ func NewQueryAllRouteStatisticsCmd() (*osmocli.QueryDescriptor, *types.QueryGetP // NewQueryTokenPairArbRoutesCmd returns the command to query the token pair arb routes func NewQueryTokenPairArbRoutesCmd() (*osmocli.QueryDescriptor, *types.QueryGetProtoRevTokenPairArbRoutesRequest) { return &osmocli.QueryDescriptor{ - Use: "token-pair-arb-routes", + Use: "hot-routes", Short: "Query the ProtoRev hot routes currently being used", }, &types.QueryGetProtoRevTokenPairArbRoutesRequest{} } @@ -139,6 +140,14 @@ func NewQueryEnabledCmd() (*osmocli.QueryDescriptor, *types.QueryGetProtoRevEnab }, &types.QueryGetProtoRevEnabledRequest{} } +// NewQueryPoolWeightsCmd returns the command to query the pool weights of protorev +func NewQueryPoolWeightsCmd() (*osmocli.QueryDescriptor, *types.QueryGetProtoRevPoolWeightsRequest) { + return &osmocli.QueryDescriptor{ + Use: "pool-weights", + Short: "Query the pool weights used to determine how computationally expensive a route is", + }, &types.QueryGetProtoRevPoolWeightsRequest{} +} + // convert a string array "[1,2,3]" to []uint64 func parseRoute(arg string, _ *pflag.FlagSet) (any, osmocli.FieldReadLocation, error) { var route []uint64 diff --git a/x/protorev/client/cli/tx.go b/x/protorev/client/cli/tx.go index a774d99f290..f0f49c2222e 100644 --- a/x/protorev/client/cli/tx.go +++ b/x/protorev/client/cli/tx.go @@ -5,11 +5,13 @@ import ( "strconv" "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/tx" "github.com/cosmos/cosmos-sdk/version" "github.com/cosmos/cosmos-sdk/x/gov/client/cli" "github.com/spf13/cobra" + "github.com/spf13/pflag" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" @@ -22,17 +24,174 @@ import ( // NewCmdTx returns the cli transaction commands for this module func NewCmdTx() *cobra.Command { txCmd := osmocli.TxIndexCmd(types.ModuleName) + osmocli.AddTxCmd(txCmd, CmdSetDeveloperAccount) + osmocli.AddTxCmd(txCmd, CmdSetMaxPoolPointsPerTx) + osmocli.AddTxCmd(txCmd, CmdSetMaxPoolPointsPerBlock) txCmd.AddCommand( + CmdSetDeveloperHotRoutes().BuildCommandCustomFn(), + CmdSetPoolWeights().BuildCommandCustomFn(), + CmdSetBaseDenoms().BuildCommandCustomFn(), CmdSetProtoRevAdminAccountProposal(), CmdSetProtoRevEnabledProposal(), ) return txCmd } +// CmdSetDeveloperHotRoutes implements the command to set the protorev hot routes +func CmdSetDeveloperHotRoutes() *osmocli.TxCliDesc { + desc := osmocli.TxCliDesc{ + Use: "set-hot-routes [path/to/routes.json]", + Short: "set the protorev hot routes", + Long: `Must provide a json file with all of the hot routes that will be set. + Sample json file: + [ + { + "token_in": "uosmo", + "token_out": "ibc/123...", + "arb_routes" : [ + { + "trades": [ + { + "pool": 1, + "token_in": "uosmo", + "token_out": "uatom" + }, + { + "pool": 2, + "token_in": "uatom", + "token_out": "ibc/123..." + }, + { + "pool": 0, + "token_in": "ibc/123...", + "token_out": "uosmo" + } + ], + "step_size": 1000000 + } + ] + } + ] + `, + Example: fmt.Sprintf(`$ %s tx protorev set-hot-routes routes.json --from mykey`, version.AppName), + NumArgs: 1, + ParseAndBuildMsg: BuildSetHotRoutesMsg, + } + + return &desc +} + +// CmdSetDeveloperAccount implements the command to set the protorev developer account +func CmdSetDeveloperAccount() (*osmocli.TxCliDesc, *types.MsgSetDeveloperAccount) { + return &osmocli.TxCliDesc{ + Use: "set-developer-account [sdk.AccAddress]", + Short: "set the protorev developer account", + NumArgs: 1, + ParseAndBuildMsg: func(clientCtx client.Context, args []string, flags *pflag.FlagSet) (sdk.Msg, error) { + developer, err := sdk.AccAddressFromBech32(args[0]) + if err != nil { + return nil, err + } + + return &types.MsgSetDeveloperAccount{ + DeveloperAccount: developer.String(), + Admin: clientCtx.GetFromAddress().String(), + }, nil + }, + }, &types.MsgSetDeveloperAccount{} +} + +// CmdSetMaxPoolPointsPerTx implements the command to set the max pool points per tx +func CmdSetMaxPoolPointsPerTx() (*osmocli.TxCliDesc, *types.MsgSetMaxPoolPointsPerTx) { + return &osmocli.TxCliDesc{ + Use: "set-max-pool-points-per-tx [uint64]", + Short: "set the max pool points that can be consumed per tx", + NumArgs: 1, + ParseAndBuildMsg: func(clientCtx client.Context, args []string, flags *pflag.FlagSet) (sdk.Msg, error) { + maxPoolPointsPerTx, err := strconv.ParseUint(args[0], 10, 64) + if err != nil { + return nil, err + } + + return &types.MsgSetMaxPoolPointsPerTx{ + MaxPoolPointsPerTx: maxPoolPointsPerTx, + Admin: clientCtx.GetFromAddress().String(), + }, nil + }, + }, &types.MsgSetMaxPoolPointsPerTx{} +} + +// CmdSetMaxPoolPointsPerBlock implements the command to set the max pool points per block +func CmdSetMaxPoolPointsPerBlock() (*osmocli.TxCliDesc, *types.MsgSetMaxPoolPointsPerBlock) { + return &osmocli.TxCliDesc{ + Use: "set-max-pool-points-per-block [uint64]", + Short: "set the max pool points that can be consumed per block", + NumArgs: 1, + ParseAndBuildMsg: func(clientCtx client.Context, args []string, flags *pflag.FlagSet) (sdk.Msg, error) { + maxPoolPointsPerBlock, err := strconv.ParseUint(args[0], 10, 64) + if err != nil { + return nil, err + } + + return &types.MsgSetMaxPoolPointsPerBlock{ + MaxPoolPointsPerBlock: maxPoolPointsPerBlock, + Admin: clientCtx.GetFromAddress().String(), + }, nil + }, + }, &types.MsgSetMaxPoolPointsPerBlock{} +} + +// CmdSetPoolWeights implements the command to set the pool weights used to estimate execution costs +func CmdSetPoolWeights() *osmocli.TxCliDesc { + desc := osmocli.TxCliDesc{ + Use: "set-pool-weights [path/to/routes.json]", + Short: "set the protorev pool weights", + Long: `Must provide a json file with all the pool weights that will be set. + Sample json file: + { + "stable_weight" : 1, + "balancer_weight" : 1, + "concentrated_weight" : 1 + } + `, + Example: fmt.Sprintf(`$ %s tx protorev set-pool-weights weights.json --from mykey`, version.AppName), + NumArgs: 1, + ParseAndBuildMsg: BuildSetPoolWeightsMsg, + } + + return &desc +} + +// CmdSetBaseDenoms implements the command to set the base denoms used in the highest liquidity method +func CmdSetBaseDenoms() *osmocli.TxCliDesc { + desc := osmocli.TxCliDesc{ + Use: "set-base-denoms [path/to/denoms.json]", + Short: "set the protorev base denoms", + Long: `Must provide a json file with all the base denoms that will be set. + Sample json file: + [ + { + "step_size" : 10000, + "denom" : "uosmo" + }, + { + "step_size" : 10000, + "denom" : "atom" + } + ] + `, + Example: fmt.Sprintf(`$ %s tx protorev set-base-denoms denoms.json --from mykey`, version.AppName), + NumArgs: 1, + ParseAndBuildMsg: BuildSetBaseDenomsMsg, + } + + return &desc +} + // CmdSetProtoRevAdminAccountProposal implements the command to submit a SetProtoRevAdminAccountProposal func CmdSetProtoRevAdminAccountProposal() *cobra.Command { cmd := &cobra.Command{ - Use: "set-protorev-admin-account-proposal [sdk.AccAddress]", + Use: "set-admin-account-proposal [sdk.AccAddress]", Args: cobra.ExactArgs(1), Short: "submit a set protorev admin account proposal to set the admin account for x/protorev", Example: fmt.Sprintf(`$ %s tx protorev set-protorev-admin-account osmo123... --from mykey`, version.AppName), @@ -58,7 +217,7 @@ func CmdSetProtoRevAdminAccountProposal() *cobra.Command { // CmdSetProtoRevEnabledProposal implements the command to submit a SetProtoRevEnabledProposal func CmdSetProtoRevEnabledProposal() *cobra.Command { cmd := &cobra.Command{ - Use: "set-protorev-enabled-proposal [boolean]", + Use: "set-enabled-proposal [boolean]", Args: cobra.ExactArgs(1), Short: "submit a set protorev enabled proposal to enable or disable the protocol", Example: fmt.Sprintf(`$ %s tx protorev set-protorev-enabled true --from mykey`, version.AppName), diff --git a/x/protorev/client/cli/utils.go b/x/protorev/client/cli/utils.go new file mode 100644 index 00000000000..e5900bf4be8 --- /dev/null +++ b/x/protorev/client/cli/utils.go @@ -0,0 +1,245 @@ +package cli + +import ( + "bytes" + "encoding/json" + "fmt" + "os" + + "github.com/cosmos/cosmos-sdk/client" + sdk "github.com/cosmos/cosmos-sdk/types" + flag "github.com/spf13/pflag" + + "github.com/osmosis-labs/osmosis/v15/x/protorev/types" +) + +// ------------ types/functions to handle a SetHotRoutes CLI TX ------------ // +type Trade struct { + Pool uint64 `json:"pool"` + TokenIn string `json:"token_in"` + TokenOut string `json:"token_out"` +} + +type ArbRoutes struct { + Trades []Trade `json:"trades"` + StepSize uint64 `json:"step_size"` +} + +type hotRoutesInput struct { + TokenIn string `json:"token_in"` + TokenOut string `json:"token_out"` + ArbRoutes []ArbRoutes `json:"arb_routes"` +} + +type createArbRoutesInput []hotRoutesInput + +type XCreateHotRoutesInputs hotRoutesInput + +type XCreateHotRoutesExceptions struct { + XCreateHotRoutesInputs + Other *string // Other won't raise an error +} + +// UnmarshalJSON should error if there are fields unexpected. +func (release *createArbRoutesInput) UnmarshalJSON(data []byte) error { + var createHotRoutesE []XCreateHotRoutesExceptions + dec := json.NewDecoder(bytes.NewReader(data)) + dec.DisallowUnknownFields() // Force + + if err := dec.Decode(&createHotRoutesE); err != nil { + return err + } + + routes := make([]hotRoutesInput, 0) + for _, route := range createHotRoutesE { + routes = append(routes, hotRoutesInput(route.XCreateHotRoutesInputs)) + } + + *release = createArbRoutesInput(routes) + return nil +} + +// extractTokenPairArbRoutes builds all of the TokenPairArbRoutes that were extracted from the json file +func (release *createArbRoutesInput) extractTokenPairArbRoutes() []types.TokenPairArbRoutes { + if release == nil { + return nil + } + + tokenPairArbRoutes := make([]types.TokenPairArbRoutes, 0) + + // Iterate through each hot route and construct the token pair arb routes + for _, hotRoute := range *release { + current := types.TokenPairArbRoutes{} + current.TokenIn = hotRoute.TokenIn + current.TokenOut = hotRoute.TokenOut + + for _, arbRoute := range hotRoute.ArbRoutes { + currentArbRoute := types.Route{} + currentArbRoute.StepSize = sdk.NewIntFromUint64(arbRoute.StepSize) + + for _, trade := range arbRoute.Trades { + currentTrade := types.Trade{} + currentTrade.Pool = trade.Pool + currentTrade.TokenIn = trade.TokenIn + currentTrade.TokenOut = trade.TokenOut + currentArbRoute.Trades = append(currentArbRoute.Trades, currentTrade) + } + + current.ArbRoutes = append(current.ArbRoutes, currentArbRoute) + } + + tokenPairArbRoutes = append(tokenPairArbRoutes, current) + } + + return tokenPairArbRoutes +} + +// BuildSetHotRoutesMsg builds a MsgSetHotRoutes from the provided json file +func BuildSetHotRoutesMsg(clientCtx client.Context, args []string, fs *flag.FlagSet) (sdk.Msg, error) { + if len(args) == 0 { + return nil, fmt.Errorf("must provide a json file") + } + + // Read the json file + input := &createArbRoutesInput{} + path := args[0] + contents, err := os.ReadFile(path) + if err != nil { + return nil, err + } + + // Unmarshal the json file + if err := input.UnmarshalJSON(contents); err != nil { + return nil, err + } + + // Build the msg + tokenPairArbRoutes := input.extractTokenPairArbRoutes() + admin := clientCtx.GetFromAddress().String() + return &types.MsgSetHotRoutes{ + Admin: admin, + HotRoutes: tokenPairArbRoutes, + }, nil +} + +// ------------ types/functions to handle a SetPoolWeights CLI TX ------------ // +type createPoolWeightsInput types.PoolWeights + +type XCreatePoolWeightsInputs createPoolWeightsInput + +type XCreatePoolWeightsExceptions struct { + XCreatePoolWeightsInputs + Other *string // Other won't raise an error +} + +// UnmarshalJSON should error if there are fields unexpected. +func (release *createPoolWeightsInput) UnmarshalJSON(data []byte) error { + var createPoolWeightsE XCreatePoolWeightsExceptions + dec := json.NewDecoder(bytes.NewReader(data)) + dec.DisallowUnknownFields() // Force + + if err := dec.Decode(&createPoolWeightsE); err != nil { + return err + } + + *release = createPoolWeightsInput(createPoolWeightsE.XCreatePoolWeightsInputs) + return nil +} + +// BuildSetPoolWeightsMsg builds a MsgSetPoolWeights from the provided json file +func BuildSetPoolWeightsMsg(clientCtx client.Context, args []string, fs *flag.FlagSet) (sdk.Msg, error) { + if len(args) == 0 { + return nil, fmt.Errorf("must provide a json file") + } + + // Read the json file + input := &createPoolWeightsInput{} + path := args[0] + contents, err := os.ReadFile(path) + if err != nil { + return nil, err + } + + // Unmarshal the json file + if err := input.UnmarshalJSON(contents); err != nil { + return nil, err + } + + // Build the msg + admin := clientCtx.GetFromAddress().String() + return &types.MsgSetPoolWeights{ + Admin: admin, + PoolWeights: types.PoolWeights(*input), + }, nil +} + +// ------------ types/functions to handle a SetBaseDenoms CLI TX ------------ // +type baseDenomInput struct { + Denom string `json:"denom"` + StepSize uint64 `json:"step_size"` +} + +type createBaseDenomsInput []baseDenomInput + +type XCreateBaseDenomsInputs baseDenomInput + +type XCreateBaseDenomsException struct { + XCreateBaseDenomsInputs + Other *string // Other won't raise an error +} + +// UnmarshalJSON should error if there are fields unexpected. +func (release *createBaseDenomsInput) UnmarshalJSON(data []byte) error { + var createBaseDenomsE []XCreateBaseDenomsException + dec := json.NewDecoder(bytes.NewReader(data)) + dec.DisallowUnknownFields() // Force + + if err := dec.Decode(&createBaseDenomsE); err != nil { + return err + } + + baseDenoms := make([]baseDenomInput, 0) + for _, denom := range createBaseDenomsE { + baseDenoms = append(baseDenoms, baseDenomInput(denom.XCreateBaseDenomsInputs)) + } + + *release = createBaseDenomsInput(baseDenoms) + + return nil +} + +// BuildSetBaseDenomsMsg builds a MsgSetBaseDenoms from the provided json file +func BuildSetBaseDenomsMsg(clientCtx client.Context, args []string, fs *flag.FlagSet) (sdk.Msg, error) { + if len(args) == 0 { + return nil, fmt.Errorf("must provide a json file") + } + + // Read the json file + input := &createBaseDenomsInput{} + path := args[0] + contents, err := os.ReadFile(path) + if err != nil { + return nil, err + } + + // Unmarshal the json file + if err := input.UnmarshalJSON(contents); err != nil { + return nil, err + } + + // Build the base denoms + baseDenoms := make([]types.BaseDenom, 0) + for _, baseDenom := range *input { + baseDenoms = append(baseDenoms, types.BaseDenom{ + Denom: baseDenom.Denom, + StepSize: sdk.NewIntFromUint64(baseDenom.StepSize), + }) + } + + // Build the msg + admin := clientCtx.GetFromAddress().String() + return &types.MsgSetBaseDenoms{ + Admin: admin, + BaseDenoms: baseDenoms, + }, nil +} diff --git a/x/protorev/keeper/msg_server.go b/x/protorev/keeper/msg_server.go index 193123624ba..386a7f803db 100644 --- a/x/protorev/keeper/msg_server.go +++ b/x/protorev/keeper/msg_server.go @@ -71,6 +71,15 @@ func (m MsgServer) SetMaxPoolPointsPerTx(c context.Context, msg *types.MsgSetMax return nil, err } + maxPointsPerBlock, err := m.k.GetMaxPointsPerBlock(ctx) + if err != nil { + return nil, err + } + + if msg.MaxPoolPointsPerTx > maxPointsPerBlock { + return nil, fmt.Errorf("max pool points per tx cannot be greater than max pool points per block") + } + // Set the max pool points per tx if err := m.k.SetMaxPointsPerTx(ctx, msg.MaxPoolPointsPerTx); err != nil { return nil, err @@ -88,6 +97,15 @@ func (m MsgServer) SetMaxPoolPointsPerBlock(c context.Context, msg *types.MsgSet return nil, err } + maxPointsPerTx, err := m.k.GetMaxPointsPerTx(ctx) + if err != nil { + return nil, err + } + + if msg.MaxPoolPointsPerBlock < maxPointsPerTx { + return nil, fmt.Errorf("max pool points per block cannot be less than max pool points per tx") + } + // Set the max pool points per block if err := m.k.SetMaxPointsPerBlock(ctx, msg.MaxPoolPointsPerBlock); err != nil { return nil, err diff --git a/x/protorev/keeper/msg_server_test.go b/x/protorev/keeper/msg_server_test.go index b463c4c43a1..9d36a56f5f5 100644 --- a/x/protorev/keeper/msg_server_test.go +++ b/x/protorev/keeper/msg_server_test.go @@ -410,9 +410,16 @@ func (suite *KeeperTestSuite) TestMsgSetMaxPoolPointsPerBlock() { { "Valid message (correct admin)", suite.adminAccount.String(), - 1, + 50, + true, true, + }, + { + "Invalid message (correct admin but less points than max pool points per tx)", + suite.adminAccount.String(), + 17, true, + false, }, { "Valid message (correct admin, valid max pool points per block)", diff --git a/x/protorev/protorev.md b/x/protorev/protorev.md index c09a1703800..f7501498b2d 100644 --- a/x/protorev/protorev.md +++ b/x/protorev/protorev.md @@ -627,20 +627,27 @@ osmosisd query protorev params | query protorev | all-profits | Queries all ProtoRev profits | | query protorev | statistics-by-route [route] where route is the list of pool ids i.e. [1,2,3] | Queries ProtoRev statistics by route | | query protorev | all-statistics | Queries all ProtoRev statistics | -| query protorev | token-pair-arb-routes | Queries the ProtoRev token pair arb routes | +| query protorev | hot-routes | Queries the ProtoRev token pair arb routes | | query protorev | admin-account | Queries the ProtoRev admin account | | query protorev | developer-account | Queries the ProtoRev developer account | | query protorev | max-pool-points-per-tx | Queries the ProtoRev max pool points per transaction | | query protorev | max-pool-points-per-block | Queries the ProtoRev max pool points per block | | query protorev | base-denoms | Queries the ProtoRev base denoms used to create cyclic arbitrage routes | | query protorev | enabled | Queries whether the ProtoRev module is currently enabled | +| query protorev | pool-weights | Queries the pool weights used to determine how computationally expensive a route is | ### Proposals | Command | Subcommand | Description | | --- | --- | --- | -| tx protorev | set-protorev-admin-account-proposal [sdk.AccAddress] | Submit a proposal to set the admin account for ProtoRev | -| tx protorev | set-protorev-enabled-proposal [boolean] | Submit a proposal to disable/enable the ProtoRev module | +| tx protorev | set-pool-weights [path/to/file.json] | Submit a tx to set the pool weights for ProtoRev | +| tx protorev | set-hot-routes [path/to/file.json] | Submit a tx to set the hot routes for ProtoRev | +| tx protorev | set-base-denoms [path/to/file.json] | Submit a tx to set the base denoms for ProtoRev | +| tx protorev | set-max-pool-points-per-block [uint64] | Submit a tx to set the max pool points per block for ProtoRev | +| tx protorev | set-max-pool-points-per-tx [uint64] | Submit a tx to set the max pool points per transaction for ProtoRev | +| tx protorev | set-developer-account [sdk.AccAddress] | Submit a tx to set the developer account for ProtoRev | +| tx protorev | set-admin-account-proposal [sdk.AccAddress] | Submit a proposal to set the admin account for ProtoRev | +| tx protorev | set-enabled-proposal [boolean] | Submit a proposal to disable/enable the ProtoRev module | ## gRPC & REST