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

IBC Enhancements #728

Merged
merged 11 commits into from
Jan 20, 2021
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
144 changes: 121 additions & 23 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 @@ -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: &str = "channel-123";
let account: &str = "acct-123";
Copy link
Member

Choose a reason for hiding this comment

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

Note: explicit type should only be needed for consts. Here it can be derived.


// 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());
}
}
Loading