Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Query wasm contract state #NTRN-476 #96

Merged
merged 8 commits into from
Jun 30, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions contracts/neutron_interchain_queries/schema/execute_msg.json
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,40 @@
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"register_cw20_balance_query"
],
"properties": {
"register_cw20_balance_query": {
"type": "object",
"required": [
"account_address",
"connection_id",
"cw20_contract_address",
"update_period"
],
"properties": {
"account_address": {
"type": "string"
},
"connection_id": {
"type": "string"
},
"cw20_contract_address": {
"type": "string"
},
"update_period": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
Expand Down
22 changes: 22 additions & 0 deletions contracts/neutron_interchain_queries/schema/query_msg.json
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,28 @@
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"cw20_balance"
],
"properties": {
"cw20_balance": {
"type": "object",
"required": [
"query_id"
],
"properties": {
"query_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
Expand Down
73 changes: 64 additions & 9 deletions contracts/neutron_interchain_queries/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,39 @@ use cosmos_sdk_proto::cosmos::bank::v1beta1::MsgSend;
use cosmos_sdk_proto::cosmos::tx::v1beta1::{TxBody, TxRaw};
use cosmwasm_std::{
entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult,
Uint128,
};
use cw2::set_contract_version;
use prost::Message as ProstMessage;

use crate::msg::{ExecuteMsg, GetRecipientTxsResponse, InstantiateMsg, MigrateMsg, QueryMsg};
use crate::msg::{
Cw20BalanceResponse, ExecuteMsg, GetRecipientTxsResponse, InstantiateMsg, MigrateMsg, QueryMsg,
};
use crate::state::{Transfer, RECIPIENT_TXS, TRANSFERS};
use neutron_sdk::bindings::msg::NeutronMsg;
use neutron_sdk::bindings::query::{NeutronQuery, QueryRegisteredQueryResponse};
use neutron_sdk::bindings::types::{Height, KVKey};
use neutron_sdk::interchain_queries::get_registered_query;
use neutron_sdk::interchain_queries::v045::queries::{
query_balance, query_bank_total, query_delegations, query_distribution_fee_pool,
query_government_proposals, query_staking_validators,
};
use neutron_sdk::interchain_queries::v045::{
new_register_balance_query_msg, new_register_bank_total_supply_query_msg,
new_register_delegator_delegations_query_msg, new_register_distribution_fee_pool_query_msg,
new_register_gov_proposal_query_msg, new_register_staking_validators_query_msg,
new_register_transfers_query_msg,
use neutron_sdk::interchain_queries::{
check_query_type, get_registered_query, query_kv_result,
v045::{
new_register_balance_query_msg, new_register_bank_total_supply_query_msg,
new_register_delegator_delegations_query_msg, new_register_distribution_fee_pool_query_msg,
new_register_gov_proposal_query_msg, new_register_staking_validators_query_msg,
new_register_transfers_query_msg,
register_queries::new_register_wasm_contract_store_query_msg,
types::{COSMOS_SDK_TRANSFER_MSG_URL, RECIPIENT_FIELD},
},
};
use neutron_sdk::sudo::msg::SudoMsg;
use neutron_sdk::{NeutronError, NeutronResult};

use neutron_sdk::interchain_queries::types::{
TransactionFilterItem, TransactionFilterOp, TransactionFilterValue,
QueryType, TransactionFilterItem, TransactionFilterOp, TransactionFilterValue,
};
use neutron_sdk::interchain_queries::v045::types::{COSMOS_SDK_TRANSFER_MSG_URL, RECIPIENT_FIELD};
use serde_json_wasm;

/// defines the incoming transfers limit to make a case of failed callback possible.
Expand Down Expand Up @@ -95,6 +101,17 @@ pub fn execute(
update_period,
min_height,
} => register_transfers_query(connection_id, recipient, update_period, min_height),
ExecuteMsg::RegisterCw20BalanceQuery {
connection_id,
update_period,
cw20_contract_address,
account_address,
} => register_cw20_balance_query(
connection_id,
update_period,
cw20_contract_address,
account_address,
),
ExecuteMsg::UpdateInterchainQuery {
query_id,
new_keys,
Expand Down Expand Up @@ -183,6 +200,28 @@ pub fn register_transfers_query(
Ok(Response::new().add_message(msg))
}

pub fn register_cw20_balance_query(
connection_id: String,
update_period: u64,
cw20_contract_address: String,
account_address: String,
) -> NeutronResult<Response<NeutronMsg>> {
// cw_storage_plus uses this prefix for maps
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There must be an instruction how did you get this prefix, it's not obvious

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bold of you to assume I remember how I did that.

Okay, I will write some extensive docs on how to examine contract storage paths.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added some docs, please check them out

let mut storage_key = vec![0u8, 7u8];

storage_key.extend_from_slice("balance".as_bytes());
storage_key.extend_from_slice(account_address.as_bytes());
Comment on lines +210 to +213
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This stuff definitely should be in the SDK helpers not here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, it shouldn't be there, because these values are contract-dependent. I wish I could make a cw20 balance helper, but it is impossible to do.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Mike means that it's not only new_register_wasm_contract_store_query_msg should be in the helpers package as the most generic helper for interacting with contract's storage, but a more specific helper to register cw20 balance query as well (e.g. new_register_cw20_balance_query_msg analogically to new_register_delegator_delegations_query_msg). the helper can have contract and holder addresses as parameters and assemble the needed key inside with these vec![0u8, 7u8] and balance reused from constants defined here: https://github.com/neutron-org/neutron-sdk/blob/main/packages/neutron-sdk/src/interchain_queries/v045/types.rs

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but now I wonder if we need such a helper — to query a cw20 balance

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I like it how it is now, a general new_register_wasm_contract_store_query_msg is what we need here whereas cw20 balance stuff is a too specific case to be in the sdk helpers package

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could implement some kind of storage key factory like this:

let key: Vec<u8> =
  StorageKeyFactory::new()
  .push_bytes([0, 7])
  .push_string("balance")
  .push_string(account_address)
  .into_bytes();

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am very hesitant creating even some kind of "general helper which works for canonical cw20-base contract" because users will definitely try to use it and it won't work in some cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could implement some kind of storage key factory like this:

To be honest it doesn't make anything easier to use, I think I should abandon this idea.


let msg = new_register_wasm_contract_store_query_msg(
connection_id,
cw20_contract_address,
&storage_key,
update_period,
)?;

Ok(Response::new().add_message(msg))
}

pub fn update_interchain_query(
query_id: u64,
new_keys: Option<Vec<KVKey>>,
Expand Down Expand Up @@ -227,6 +266,9 @@ pub fn query(deps: Deps<NeutronQuery>, env: Env, msg: QueryMsg) -> NeutronResult
QueryMsg::GetDelegations { query_id } => {
Ok(to_binary(&query_delegations(deps, env, query_id)?)?)
}
QueryMsg::Cw20Balance { query_id } => {
Ok(to_binary(&query_cw20_balance(deps, env, query_id)?)?)
}
QueryMsg::GetRegisteredQuery { query_id } => {
Ok(to_binary(&get_registered_query(deps, query_id)?)?)
}
Expand All @@ -241,6 +283,19 @@ fn query_recipient_txs(deps: Deps<NeutronQuery>, recipient: String) -> NeutronRe
Ok(to_binary(&GetRecipientTxsResponse { transfers: txs })?)
}

pub fn query_cw20_balance(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we create a new_register_cw20_balance_query_msg in the helpers package, we should move this one to the helpers package as well

deps: Deps<NeutronQuery>,
_env: Env,
registered_query_id: u64,
) -> NeutronResult<Cw20BalanceResponse> {
let registered_query = get_registered_query(deps, registered_query_id)?;

check_query_type(registered_query.registered_query.query_type, QueryType::KV)?;

let balance: Uint128 = query_kv_result(deps, registered_query_id)?;
Ok(Cw20BalanceResponse { balance })
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult<Response> {
deps.api.debug("WASMDEBUG: migrate");
Expand Down
14 changes: 14 additions & 0 deletions contracts/neutron_interchain_queries/src/msg.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::state::Transfer;
use cosmwasm_std::Uint128;
use neutron_sdk::bindings::types::KVKey;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -46,6 +47,12 @@ pub enum ExecuteMsg {
connection_id: String,
update_period: u64,
},
RegisterCw20BalanceQuery {
connection_id: String,
update_period: u64,
cw20_contract_address: String,
account_address: String,
},
UpdateInterchainQuery {
query_id: u64,
new_keys: Option<Vec<KVKey>>,
Expand All @@ -66,10 +73,17 @@ pub enum QueryMsg {
StakingValidators { query_id: u64 },
GovernmentProposals { query_id: u64 },
GetDelegations { query_id: u64 },
Cw20Balance { query_id: u64 },
GetRegisteredQuery { query_id: u64 },
GetRecipientTxs { recipient: String },
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct Cw20BalanceResponse {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we create a new_register_cw20_balance_query_msg in the helpers package, we should move this one to the helpers package as well

pub balance: Uint128,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct GetRecipientTxsResponse {
Expand Down
9 changes: 6 additions & 3 deletions packages/neutron-sdk/src/interchain_queries/queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,16 @@ pub fn query_kv_result<T: KVReconstruct>(
deps: Deps<NeutronQuery>,
query_id: u64,
) -> NeutronResult<T> {
let registered_query_result = get_interchain_query_result(deps, query_id)?;
let registered_query_result = get_raw_interchain_query_result(deps, query_id)?;

KVReconstruct::reconstruct(&registered_query_result.result.kv_results)
}

/// Queries interchain query result (raw KV storage values or transactions) from Interchain Queries Module
fn get_interchain_query_result(
/// Queries raw interchain query result (raw KV storage values or transactions) from Interchain Queries Module.
/// Usually it is better to implement [KVReconstruct] for your own type and then use [query_kv_result],
/// but in cases when Rust forbids to implement foreign trait [KVReconstruct] for some foreign type,
/// it is possible to use [get_raw_interchain_query_result] and reconstruct query result manually.
pub fn get_raw_interchain_query_result(
deps: Deps<NeutronQuery>,
interchain_query_id: u64,
) -> NeutronResult<QueryRegisteredQueryResultResponse> {
Expand Down
18 changes: 17 additions & 1 deletion packages/neutron-sdk/src/interchain_queries/v045/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::errors::error::NeutronResult;
use crate::interchain_queries::helpers::length_prefix;
use crate::interchain_queries::v045::types::{
BALANCES_PREFIX, DELEGATION_KEY, FEE_POOL_KEY, PARAMS_STORE_DELIMITER, PROPOSALS_KEY_PREFIX,
SUPPLY_PREFIX, VALIDATORS_KEY,
SUPPLY_PREFIX, VALIDATORS_KEY, WASM_CONTRACT_STORE_PREFIX,
};
use cosmos_sdk_proto::cosmos::staking::v1beta1::Commission as ValidatorCommission;
use cosmwasm_std::{Binary, Decimal, Uint128};
Expand Down Expand Up @@ -93,6 +93,22 @@ pub fn create_validator_key<AddrBytes: AsRef<[u8]>>(
Ok(key)
}

/// Creates Wasm key for contract state.
/// This function is similar to
/// <https://github.com/CosmWasm/wasmd/blob/e6d451bf9dd96a555b10e72aa3c0f6b820d34684/x/wasm/types/keys.go#L59>,
/// but it also concatenates resulting contract store prefix with contract's storage key,
/// resulting in a complete storage key.
pub fn create_wasm_contract_store_key<AddrBytes: AsRef<[u8]>, Key: AsRef<[u8]>>(
contract_address: AddrBytes,
key: Key,
) -> NeutronResult<Vec<u8>> {
let mut prefix: Vec<u8> = vec![WASM_CONTRACT_STORE_PREFIX];
prefix.extend_from_slice(contract_address.as_ref());
prefix.extend_from_slice(key.as_ref());

Ok(prefix)
}

/// Creates Cosmos-SDK distribution key for fee pool
/// <https://github.com/cosmos/cosmos-sdk/blob/35ae2c4c72d4aeb33447d5a7af23ca47f786606e/x/distribution/types/keys.go#L46>
pub fn create_fee_pool_key() -> NeutronResult<Vec<u8>> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::interchain_queries::types::{
};
use crate::interchain_queries::v045::types::{
BANK_STORE_KEY, DISTRIBUTION_STORE_KEY, GOV_STORE_KEY, HEIGHT_FIELD, KEY_BOND_DENOM,
PARAMS_STORE_KEY, RECIPIENT_FIELD, STAKING_STORE_KEY,
PARAMS_STORE_KEY, RECIPIENT_FIELD, STAKING_STORE_KEY, WASM_STORE_KEY,
};
use crate::{
bindings::{msg::NeutronMsg, types::KVKey},
Expand All @@ -12,7 +12,7 @@ use crate::{
interchain_queries::v045::helpers::{
create_account_denom_balance_key, create_delegation_key, create_fee_pool_key,
create_gov_proposal_key, create_params_store_key, create_total_denom_key,
create_validator_key,
create_validator_key, create_wasm_contract_store_key,
},
};
use cosmwasm_std::Binary;
Expand Down Expand Up @@ -185,6 +185,40 @@ pub fn new_register_delegator_delegations_query_msg(
NeutronMsg::register_interchain_query(QueryPayload::KV(keys), connection_id, update_period)
}

/// Creates a message to register an Interchain Query to get wasm contract store on remote chain
/// from **wasm** module
///
/// * **connection_id** is an IBC connection identifier between Neutron and remote chain;
/// * **contract_address** is an address of a contract on a remote chain;
/// * **key** is a wasm contract store key;
/// * **update_period** is used to say how often the query must be updated.
///
/// Obtaining a **key** might not be a trivial task. One could list all contract's storage keys
/// using `$CHAIN_BIN query wasm contract-state all $CONTRACT_ADDRESS --output json | jq`.
/// The listed keys will be in format of plain hexadecimal string which is hard to understand
/// just by looking it. One could pipe this string into `| xxd -r -p | hexdump -C` and examine
/// its contents.
pub fn new_register_wasm_contract_store_query_msg(
connection_id: String,
contract_address: String,
key: impl AsRef<[u8]>,
update_period: u64,
) -> NeutronResult<NeutronMsg> {
let converted_addr_bytes = decode_and_convert(contract_address.as_str())?;
let wasm_key = create_wasm_contract_store_key(converted_addr_bytes, key.as_ref())?;

let kv_key = KVKey {
path: WASM_STORE_KEY.to_string(),
key: Binary(wasm_key),
};

NeutronMsg::register_interchain_query(
QueryPayload::KV(vec![kv_key]),
connection_id,
update_period,
)
}

/// Creates a message to register an Interchain Query to get transfer events to a recipient on a remote chain.
///
/// * **connection_id** is an IBC connection identifier between Neutron and remote chain;
Expand Down
19 changes: 18 additions & 1 deletion packages/neutron-sdk/src/interchain_queries/v045/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use cosmos_sdk_proto::cosmos::{
gov::v1beta1::Proposal as CosmosProposal,
staking::v1beta1::{Delegation, Validator as CosmosValidator},
};
use cosmwasm_std::{from_binary, Addr, Coin, Decimal, Uint128};
use cosmwasm_std::{from_binary, Addr, Coin, Decimal, StdError, Uint128};
use prost::Message as ProstMessage;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -50,6 +50,10 @@ pub const FEE_POOL_KEY: u8 = 0x00;
/// <https://github.com/cosmos/cosmos-sdk/blob/35ae2c4c72d4aeb33447d5a7af23ca47f786606e/x/gov/types/keys.go#L41>
pub const PROPOSALS_KEY_PREFIX: u8 = 0x00;

/// Key for Wasm Contract Store in the **wasm** module's storage
/// <https://github.com/CosmWasm/wasmd/blob/e6d451bf9dd96a555b10e72aa3c0f6b820d34684/x/wasm/types/keys.go#L28>
pub const WASM_CONTRACT_STORE_PREFIX: u8 = 0x03;

/// Name of the standard **bank** Cosmos-SDK module
pub const BANK_STORE_KEY: &str = "bank";

Expand All @@ -72,9 +76,22 @@ pub const PARAMS_STORE_KEY: &str = "params";
/// Default delimiter of **params** Cosmos-SDK module
pub const PARAMS_STORE_DELIMITER: &str = "/";

/// Name of the **wasm** Cosmos module
pub const WASM_STORE_KEY: &str = "wasm";

pub const RECIPIENT_FIELD: &str = "transfer.recipient";
pub const HEIGHT_FIELD: &str = "tx.height";

impl KVReconstruct for Uint128 {
fn reconstruct(storage_values: &[StorageValue]) -> NeutronResult<Uint128> {
let value = storage_values
.first()
.ok_or_else(|| StdError::generic_err("empty query result"))?;
let balance: Uint128 = from_binary(&value.value)?;
Ok(balance)
}
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
/// A structure that can be reconstructed from **StorageValues**'s for the **Balance Interchain Query**.
/// Contains coins that are held by some account on remote chain.
Expand Down
2 changes: 1 addition & 1 deletion packages/neutron-sdk/src/interchain_txs/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use cosmos_sdk_proto::cosmos::base::abci::v1beta1::{MsgData, TxMsgData};
use cosmwasm_std::{Binary, StdError, StdResult};
use prost::{DecodeError, Message};

/// Decodes acknowledgement into Vec<MsgData> structure
/// Decodes acknowledgement into `Vec<MsgData>` structure
pub fn decode_acknowledgement_response(data: Binary) -> StdResult<Vec<MsgData>> {
let msg_data: Result<TxMsgData, DecodeError> = TxMsgData::decode(data.as_slice());
match msg_data {
Expand Down
1 change: 1 addition & 0 deletions scripts/test_icq_wasm_juno_testnet/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
storage/
Loading