Skip to content

Commit

Permalink
Merge pull request #728 from CosmWasm/ibc-enhancements
Browse files Browse the repository at this point in the history
IBC Enhancements
  • Loading branch information
mergify[bot] authored Jan 20, 2021
2 parents 31a5d0d + bd47be5 commit e606c75
Show file tree
Hide file tree
Showing 13 changed files with 359 additions and 117 deletions.
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,31 @@ and this project adheres to
- cosmwasm-vm: The new `Cache::analyze` provides a static analyzis of the Wasm
bytecode. This is used to tell the caller if the contract exposes IBC entry
points. ([#736])
- cosmwasm-vm: Added new `stargate` feature flag to enable new stargate and ibc
features ([#692], [#716])
- cosmwasm-vm: (requires `stargate`) call into 6 new ibc entry points if exposed
by contract ([#692], [#716])
- cosmwasm-std: Added new `stargate` feature flag to enable new stargate and ibc
features ([#692], [#706])
- cosmwasm-std: (requires `stargate`) Added new `CosmosMsg::Stargate` message
type to dispatch protobuf-encoded message (contract must know proto schema)
([#706])
- cosmwasm-std: (requires `stargate`) Added new `QueryRequest::Stargate` message
type to dispatch protobuf-encoded queries (contract must know proto schema for
request and response) ([#706])
- cosmwasm-std: (requires `stargate`) Added new `CosmosMsg::Ibc(IbcMsg)` message
type to use ibctransfer app or send raw ics packets (if contract has ibc entry
points) ([#692], [#710])
- contracts: added new `ibc-reflect` contract that receives channels and assigns
each an account to redispatch. Similar idea to ICS27/Interchain Accounts (but
different implementation) ([#692], [#711], [#714])

[#692]: https://github.com/CosmWasm/cosmwasm/issues/692
[#706]: https://github.com/CosmWasm/cosmwasm/pull/706
[#710]: https://github.com/CosmWasm/cosmwasm/pull/710
[#711]: https://github.com/CosmWasm/cosmwasm/pull/711
[#714]: https://github.com/CosmWasm/cosmwasm/pull/714
[#716]: https://github.com/CosmWasm/cosmwasm/pull/716

### Changed

Expand All @@ -29,6 +54,8 @@ and this project adheres to
([#697])
- cosmwasm-vm: Bump required marker export `cosmwasm_vm_version_4` to
`interface_version_5`.
- contracts: `reflect` contract requires `stargate` feature and supports
redispatching `Stargate` and `IbcMsg::Transfer` messages ([#692])

[#696]: https://github.com/CosmWasm/cosmwasm/issues/696
[#697]: https://github.com/CosmWasm/cosmwasm/issues/697
Expand Down
30 changes: 15 additions & 15 deletions contracts/ibc-reflect/schema/packet_msg.json
Original file line number Diff line number Diff line change
Expand Up @@ -219,19 +219,19 @@
"description": "exisiting channel to send the tokens over",
"type": "string"
},
"timeout_height": {
"description": "block height after which the packet times out. at least one of timeout_height, timeout_timestamp is required",
"timeout_block": {
"description": "block after which the packet times out. at least one of timeout_block, timeout_timestamp is required",
"anyOf": [
{
"$ref": "#/definitions/IbcTimeoutHeight"
"$ref": "#/definitions/IbcTimeoutBlock"
},
{
"type": "null"
}
]
},
"timeout_timestamp": {
"description": "block timestamp (in nanoseconds) after which the packet times out. at least one of timeout_height, timeout_timestamp is required",
"description": "block timestamp (nanoseconds since UNIX epoch) after which the packet times out. See https://golang.org/pkg/time/#Time.UnixNano at least one of timeout_block, timeout_timestamp is required",
"type": [
"integer",
"null"
Expand Down Expand Up @@ -271,19 +271,19 @@
"data": {
"$ref": "#/definitions/Binary"
},
"timeout_height": {
"description": "block height after which the packet times out. at least one of timeout_height, timeout_timestamp is required",
"timeout_block": {
"description": "block height after which the packet times out. at least one of timeout_block, timeout_timestamp is required",
"anyOf": [
{
"$ref": "#/definitions/IbcTimeoutHeight"
"$ref": "#/definitions/IbcTimeoutBlock"
},
{
"type": "null"
}
]
},
"timeout_timestamp": {
"description": "block timestamp (in nanoseconds) after which the packet times out. at least one of timeout_height, timeout_timestamp is required",
"description": "block timestamp (nanoseconds since UNIX epoch) after which the packet times out. See https://golang.org/pkg/time/#Time.UnixNano at least one of timeout_block, timeout_timestamp is required",
"type": [
"integer",
"null"
Expand Down Expand Up @@ -317,22 +317,22 @@
}
]
},
"IbcTimeoutHeight": {
"IbcTimeoutBlock": {
"description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)",
"type": "object",
"required": [
"revision_number",
"timeout_height"
"height",
"revision"
],
"properties": {
"revision_number": {
"description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)",
"height": {
"description": "block height after which the packet times out. the height within the given revision",
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"timeout_height": {
"description": "block height after which the packet times out. the height within the given revision",
"revision": {
"description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)",
"type": "integer",
"format": "uint64",
"minimum": 0.0
Expand Down
148 changes: 123 additions & 25 deletions contracts/ibc-reflect/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use cosmwasm_std::{
entry_point, from_slice, to_binary, wasm_execute, wasm_instantiate, CosmosMsg, Deps, DepsMut,
Env, HandleResponse, HumanAddr, IbcAcknowledgement, IbcBasicResponse, IbcChannel, IbcOrder,
IbcPacket, IbcReceiveResponse, InitResponse, MessageInfo, Order, QueryResponse, StdError,
StdResult,
attr, entry_point, from_slice, to_binary, wasm_execute, wasm_instantiate, BankMsg, CosmosMsg,
Deps, DepsMut, Empty, Env, HandleResponse, HumanAddr, IbcAcknowledgement, IbcBasicResponse,
IbcChannel, IbcOrder, IbcPacket, IbcReceiveResponse, InitResponse, MessageInfo, Order,
QueryResponse, StdError, StdResult,
};

use crate::msg::{
Expand All @@ -22,7 +22,10 @@ pub fn init(deps: DepsMut, _env: Env, _info: MessageInfo, msg: InitMsg) -> StdRe
};
config(deps.storage).save(&cfg)?;

Ok(InitResponse::default())
Ok(InitResponse {
messages: vec![],
attributes: vec![attr("action", "init")],
})
}

#[entry_point]
Expand Down Expand Up @@ -61,7 +64,11 @@ pub fn handle_init_callback(
}
})?;

Ok(HandleResponse::default())
Ok(HandleResponse {
messages: vec![],
attributes: vec![attr("action", "handle_init_callback")],
data: None,
})
}

#[entry_point]
Expand Down Expand Up @@ -133,24 +140,54 @@ pub fn ibc_channel_connect(

let label = format!("ibc-reflect-{}", &chan_id);
let payload = ReflectInitMsg {
callback_id: Some(chan_id),
callback_id: Some(chan_id.clone()),
};
let msg = wasm_instantiate(cfg.reflect_code_id, &payload, vec![], Some(label))?;
Ok(IbcBasicResponse {
messages: vec![msg.into()],
attributes: vec![],
attributes: vec![attr("action", "ibc_connect"), attr("channel_id", chan_id)],
})
}

#[entry_point]
/// we do nothing
/// TODO: remove the account from the lookup?
/// On closed channel, we take all tokens from reflect contract to this contract.
/// We also delete the channel entry from accounts.
pub fn ibc_channel_close(
_deps: DepsMut,
_env: Env,
_channel: IbcChannel,
deps: DepsMut,
env: Env,
channel: IbcChannel,
) -> StdResult<IbcBasicResponse> {
Ok(IbcBasicResponse::default())
// get contract address and remove lookup
let channel_id = channel.endpoint.channel_id.as_str();
let reflect_addr = accounts(deps.storage).load(channel_id.as_bytes())?;
accounts(deps.storage).remove(channel_id.as_bytes());

// transfer current balance if any (steal the money)
let amount = deps.querier.query_all_balances(&reflect_addr)?;
let messages: Vec<CosmosMsg<Empty>> = if !amount.is_empty() {
let bank_msg: CosmosMsg<Empty> = BankMsg::Send {
to_address: env.contract.address.clone(),
amount,
}
.into();
let reflect_msg = ReflectHandleMsg::ReflectMsg {
msgs: vec![bank_msg.into()],
};
let wasm_msg = wasm_execute(reflect_addr, &reflect_msg, vec![])?;
vec![wasm_msg.into()]
} else {
vec![]
};
let steal_funds = !messages.is_empty();

Ok(IbcBasicResponse {
messages,
attributes: vec![
attr("action", "ibc_close"),
attr("channel_id", channel_id),
attr("steal_funds", steal_funds),
],
})
}

#[entry_point]
Expand Down Expand Up @@ -205,7 +242,7 @@ fn receive_dispatch(
Ok(IbcReceiveResponse {
acknowledgement,
messages: vec![wasm_msg.into()],
attributes: vec![],
attributes: vec![attr("action", "receive_dispatch")],
})
}

Expand All @@ -218,7 +255,7 @@ fn receive_who_am_i(deps: DepsMut, caller: String) -> StdResult<IbcReceiveRespon
Ok(IbcReceiveResponse {
acknowledgement,
messages: vec![],
attributes: vec![],
attributes: vec![attr("action", "receive_who_am_i")],
})
}

Expand All @@ -232,38 +269,44 @@ fn receive_balances(deps: DepsMut, caller: String) -> StdResult<IbcReceiveRespon
Ok(IbcReceiveResponse {
acknowledgement,
messages: vec![],
attributes: vec![],
attributes: vec![attr("action", "receive_balances")],
})
}

#[entry_point]
/// we do nothing
/// never should be called as we do not send packets
pub fn ibc_packet_ack(
_deps: DepsMut,
_env: Env,
_ack: IbcAcknowledgement,
) -> StdResult<IbcBasicResponse> {
Ok(IbcBasicResponse::default())
Ok(IbcBasicResponse {
messages: vec![],
attributes: vec![attr("action", "ibc_packet_ack")],
})
}

#[entry_point]
/// we do nothing
/// never should be called as we do not send packets
pub fn ibc_packet_timeout(
_deps: DepsMut,
_env: Env,
_packet: IbcPacket,
) -> StdResult<IbcBasicResponse> {
Ok(IbcBasicResponse::default())
Ok(IbcBasicResponse {
messages: vec![],
attributes: vec![attr("action", "ibc_packet_timeout")],
})
}

#[cfg(test)]
mod tests {
use super::*;
use cosmwasm_std::testing::{
mock_dependencies, mock_env, mock_ibc_channel, mock_ibc_packet_recv, mock_info, MockApi,
MockQuerier, MockStorage,
MockQuerier, MockStorage, MOCK_CONTRACT_ADDR,
};
use cosmwasm_std::{coins, from_slice, BankMsg, OwnedDeps, WasmMsg};
use cosmwasm_std::{coin, coins, from_slice, BankMsg, OwnedDeps, WasmMsg};

const CREATOR: &str = "creator";
// code id of the reflect contract
Expand Down Expand Up @@ -408,8 +451,8 @@ mod tests {
fn handle_dispatch_packet() {
let mut deps = setup();

let channel_id: &str = "channel-123";
let account: &str = "acct-123";
let channel_id = "channel-123";
let account = "acct-123";

// receive a packet for an unregistered channel returns app-level error (not Result::Err)
let msgs_to_dispatch = vec![BankMsg::Send {
Expand Down Expand Up @@ -477,4 +520,59 @@ mod tests {
let ack: AcknowledgementMsg<DispatchResponse> = from_slice(&res.acknowledgement).unwrap();
assert_eq!(ack.unwrap_err(), "invalid packet: Error parsing into type ibc_reflect::msg::PacketMsg: unknown variant `reflect_code_id`, expected one of `dispatch`, `who_am_i`, `balances`");
}

#[test]
fn check_close_channel() {
let mut deps = setup();

let channel_id = "channel-123";
let account = "acct-123";

// register the channel
connect(deps.as_mut(), channel_id, account);
// assign it some funds
let funds = vec![coin(123456, "uatom"), coin(7654321, "tgrd")];
deps.querier.update_balance(account, funds.clone());

// channel should be listed and have balance
let raw = query(deps.as_ref(), mock_env(), QueryMsg::ListAccounts {}).unwrap();
let res: ListAccountsResponse = from_slice(&raw).unwrap();
assert_eq!(1, res.accounts.len());
let balance = deps.as_ref().querier.query_all_balances(account).unwrap();
assert_eq!(funds, balance);

// close the channel
let channel = mock_ibc_channel(channel_id, IbcOrder::Ordered, IBC_VERSION);
let res = ibc_channel_close(deps.as_mut(), mock_env(), channel).unwrap();

// it pulls out all money from the reflect contract
assert_eq!(1, res.messages.len());
if let CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr, msg, ..
}) = &res.messages[0]
{
assert_eq!(contract_addr.as_str(), account);
let reflect: ReflectHandleMsg = from_slice(msg).unwrap();
match reflect {
ReflectHandleMsg::ReflectMsg { msgs } => {
assert_eq!(1, msgs.len());
assert_eq!(
&msgs[0],
&BankMsg::Send {
to_address: MOCK_CONTRACT_ADDR.into(),
amount: funds
}
.into()
)
}
}
} else {
panic!("Unexpected message: {:?}", &res.messages[0]);
}

// and removes the account lookup
let raw = query(deps.as_ref(), mock_env(), QueryMsg::ListAccounts {}).unwrap();
let res: ListAccountsResponse = from_slice(&raw).unwrap();
assert_eq!(0, res.accounts.len());
}
}
4 changes: 2 additions & 2 deletions contracts/ibc-reflect/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,8 @@ fn proper_handshake_flow() {
fn handle_dispatch_packet() {
let mut deps = setup();

let channel_id: &str = "channel-123";
let account: &str = "acct-123";
let channel_id = "channel-123";
let account = "acct-123";

// receive a packet for an unregistered channel returns app-level error (not Result::Err)
let msgs_to_dispatch = vec![BankMsg::Send {
Expand Down
Loading

0 comments on commit e606c75

Please sign in to comment.