From 80f5fb26e6fdcdabede5ab55c2abc2e0005bace8 Mon Sep 17 00:00:00 2001 From: Tomasz Kulik Date: Thu, 17 Oct 2024 09:41:35 +0200 Subject: [PATCH] docs: Remove docs files that are duplicated in cw documentation website --- EntryPoints.md | 83 ------- IBC.md | 589 +------------------------------------------------ SEMANTICS.md | 408 +--------------------------------- 3 files changed, 5 insertions(+), 1075 deletions(-) delete mode 100644 EntryPoints.md diff --git a/EntryPoints.md b/EntryPoints.md deleted file mode 100644 index f65cfed42c..0000000000 --- a/EntryPoints.md +++ /dev/null @@ -1,83 +0,0 @@ -# Defining Entry Points to Wasm - -## Exports - -`exports` are the functions that we export to the outside world. After -compilation these will be the only entry points that the application can call -into the web assembly contract. In general, the runtime will define a fixed set -of `exports` that must be implemented by the contract. - -To make an export in rust code, you can add the following lines: - -```rust -#[no_mangle] -pub extern "C" fn double(n: i32) -> i32 { - n * 2 -} -``` - -Note that you need the `#[no_mangle]` directive to keep the naming, and declare -it as `pub extern "C"` to create a proper C ABI, which is the standard interface -for Web Assembly, as well as FFI. - -## Imports - -If we want to interact with the outside world, the smart contract needs to -define a set of `imports`. These are function signatures we expect to be -implemented by the environment and provided when the VM is instantiated. If the -proper imports are not provided, you will receive an error upon instantiating -the contract. - -```rust -extern "C" { - fn c_read() -> *mut c_char; - fn c_write(string: *mut c_char); -} -``` - -The above expects the runtime to provide read/write access to some (persistent) -singleton. Notably, the contract has no knowledge how it is stored, and no way -to "jailbreak" or access other element of the database. - -## Memory Management - -If you look closely, you will see every function definition in `exports` accepts -a fixed number of arguments of type `i32` and returns one result of type `i32` -(or `void`). With such limitations, how can one pass in a `struct` to the -contract, or even a serialized byte array (eg. json blob). And how can we return -a string back? - -There is one more way in which the runtime can interact with a smart contract -instance. It can directly read and write to the linear memory of the smart -contract. In general, contracts are expected to export two well-defined -functions: - -```rust -#[no_mangle] -pub extern "C" fn allocate(size: usize) -> *mut c_void { - let mut buffer = Vec::with_capacity(size); - let pointer = buffer.as_mut_ptr(); - mem::forget(buffer); - pointer as *mut c_void -} - -#[no_mangle] -pub extern "C" fn deallocate(pointer: *mut c_void, capacity: usize) { - unsafe { - let _ = Vec::from_raw_parts(pointer, 0, capacity); - } -} -``` - -`allocate` heap allocates `size` bytes and tells the wasm alloc library not to -clear it (forget), after which it returns the pointer (integer offset) to the -caller. The caller can now safely write up to `size` bytes to the given offset, -eg. `copy(data, vm.Memory[offset:offset+size])`. Of course, this passes the -responsibility of freeing the memory from the wasm code to the caller, so make -sure to call `deallocate()` on the memory reference after the function call -finishes. - -We can explore more complex and idiomatic ways of passing data between the -environment and the wasm contract, but for now, just ensure you export these two -functions and the runtime will make use of them to get `string` and `[]byte` -into and out of the wasm contract. diff --git a/IBC.md b/IBC.md index 5a4eb6434c..3aeefc91f8 100644 --- a/IBC.md +++ b/IBC.md @@ -1,588 +1 @@ -# IBC interfaces for CosmWasm contracts - -If you import `cosmwasm-std` with the `stargate` feature flag, it will expose a -number of IBC-related functionality. This requires that the host chain is -running an IBC-enabled version of -[`x/wasmd`](https://github.com/CosmWasm/wasmd/tree/master/x/wasm), that is -`v0.16.0` or higher. You will get an error when you upload the contract if the -chain doesn't support this functionality. - -## Sending Tokens via ICS20 - -There are two ways to use IBC. The simplest one, available to all contracts, is -simply to send tokens to another chain on a pre-established ICS20 channel. ICS20 -is the protocol that is used to move fungible tokens between Cosmos blockchains. -To this end, we expose a -[new `CosmosMsg::Ibc(IbcMsg::Transfer{})` message variant](https://github.com/CosmWasm/cosmwasm/blob/v0.14.0-beta4/packages/std/src/ibc.rs#L25-L40) -that works similar to `CosmosMsg::Bank(BankMsg::Send{})`, but with a few extra -fields: - -```rust -pub enum IbcMsg { - /// Sends bank tokens owned by the contract to the given address on another chain. - /// The channel must already be established between the ibctransfer module on this chain - /// and a matching module on the remote chain. - /// We cannot select the port_id, this is whatever the local chain has bound the ibctransfer - /// module to. - Transfer { - /// existing channel to send the tokens over - channel_id: String, - /// address on the remote chain to receive these tokens - to_address: String, - /// packet data only supports one coin - /// https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20 - amount: Coin, - /// when packet times out, measured on remote chain - timeout: IbcTimeout, - } -} - -/// In IBC each package must set at least one type of timeout: -/// the timestamp or the block height. Using this rather complex enum instead of -/// two timeout fields we ensure that at least one timeout is set. -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct IbcTimeout { - block: Option, - timestamp: Option, -} -``` - -Note the `to_address` is likely not a valid `Addr`, as it uses the bech32 prefix -of the _receiving_ chain. In addition to the info you need in `BankMsg::Send`, -you need to define the `channel` to send upon as well as a timeout specified -either in block height or block time (or both). If the packet is not relayed -before the timeout passes (measured on the receiving chain), you can request -your tokens back. - -## Writing New Protocols - -However, we go beyond simply _using_ existing IBC protocols, and allow you to -_implement_ your own ICS protocols inside the contract. A good example to -understand this is the -[`cw20-ics20` contract](https://github.com/CosmWasm/cosmwasm-plus/tree/v0.6.0-beta1/contracts/cw20-ics20) -included in the `cosmwasm-plus` repo. This contract speaks the `ics20-1` -protocol to an external blockchain just as if it were the `ibctransfer` module -in Go. However, we can implement any logic we want there and even hot-load it on -a running blockchain. - -This particular contract above accepts -[cw20 tokens](https://github.com/CosmWasm/cosmwasm-plus/tree/v0.6.0-beta1/packages/cw20) -and sends those to a remote chain, as well as receiving the tokens back and -releasing the original cw20 token to a new owner. It does not (yet) allow -minting coins originating from the remote chain. I recommend opening up the -source code for that contract and referring to it when you want a concrete -example for anything discussed below. - -In order to enable IBC communication, a contract must expose the following 6 -entry points. Upon detecting such an "IBC-Enabled" contract, the -[`x/wasm` runtime](https://github.com/CosmWasm/wasmd) will automatically bind a -port for this contract (`wasm.`), which allows a relayer to -create channels between this contract and another chain. Once channels are -created, the contract will process all packets and receipts. - -### Channel Lifecycle - -You should first familiarize yourself with the -[4 step channel handshake protocol](https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#channel-lifecycle-management) -from the IBC spec. After realizing that it was 2 slight variants of 2 steps, we -simplified the interface for the contracts. Each side will receive 2 calls to -establish a new channel, and returning an error in any of the steps will abort -the handshake. Below we will refer to the chains as A and B - A is where the -handshake initialized at. - -#### Channel Open - -The first step of a handshake on either chain is `ibc_channel_open`, which -combines `ChanOpenInit` and `ChanOpenTry` from the spec. The only valid action -of the contract is to accept the channel or reject it. This is generally based -on the ordering and version in the `IbcChannel` information, but you could -enforce other constraints as well: - -```rust -#[entry_point] -/// enforces ordering and versioning constraints -pub fn ibc_channel_open(deps: DepsMut, env: Env, msg: IbcChannelOpenMsg) -> StdResult<()> { } -``` - -This is the -[IbcChannel structure](https://github.com/CosmWasm/cosmwasm/blob/v0.15.0/packages/std/src/ibc.rs#L117-L128) -used heavily in the handshake process: - -```rust -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct IbcChannel { - pub endpoint: IbcEndpoint, - pub counterparty_endpoint: IbcEndpoint, - pub order: IbcOrder, - pub version: String, - /// CounterpartyVersion can be None when not known this context, yet - pub counterparty_version: Option, - /// The connection upon which this channel was created. If this is a multi-hop - /// channel, we only expose the first hop. - pub connection_id: String, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct IbcEndpoint { - pub port_id: String, - pub channel_id: String, -} -``` - -This `IbcChannel` value has to be wrapped in the `IbcChannelOpenMsg` type. - -```rust -let msg = IbcChannelOpenMsg::new_init(channel); -// or -let msg = IbcChannelOpenMsg::new_try(channel, counterparty_version); -``` - -Note that neither `counterparty_version` nor `counterparty_endpoint` is set in -`ibc_channel_open` for chain A. Chain B should enforce any -`counterparty_version` constraints in `ibc_channel_open`. Chain A must enforce -`counterparty_version` or `counterparty_endpoint` restrictions in -`ibc_channel_connect`. - -(Just test if the `counterparty_version` field is `Some(x)` in both calls and -then enforce the counterparty restrictions if set. That will check these once at -the proper place for both chain A and chain B). - -You should save any state only in `ibc_channel_connect` once the channel has -been approved by the remote side. - -#### Channel Connect - -Once both sides have returned `Ok()` to `ibc_channel_open`, we move onto the -second step of the handshake, which is equivalent to `ChanOpenAck` and -`ChanOpenConfirm` from the spec: - -```rust -#[entry_point] -/// once it's established, we may take some setup action -pub fn ibc_channel_connect( - deps: DepsMut, - env: Env, - msg: IbcChannelConnectMsg, -) -> StdResult { } -``` - -At this point, it is expected that the contract updates its internal state and -may return `CosmosMsg` in the `Response` to interact with other contracts, just -like in `execute`. In particular, you will most likely want to store the local -channel_id (`channel.endpoint.channel_id`) in the contract's storage, so it -knows what open channels it has (and can expose those via queries or maintain -state for each one). - -Once this has been called, you may expect to send and receive any number of -packets with the contract. The packets will only stop once the channel is closed -(which may never happen). - -### Channel Close - -A contract may request to close a channel that belongs to it via the following -`CosmosMsg::Ibc`: - -```rust -pub enum IbcMsg { - /// This will close an existing channel that is owned by this contract. - /// Port is auto-assigned to the contract's IBC port - CloseChannel { channel_id: String }, -} -``` - -Once a channel is closed, whether due to an IBC error, at our request, or at the -request of the other side, the following callback is made on the contract, which -allows it to take appropriate cleanup action: - -```rust -#[entry_point] -pub fn ibc_channel_close( - deps: DepsMut, - env: Env, - msg: IbcChannelCloseMsg, -) -> StdResult { } -``` - -### Packet Lifecycle - -Unfortunately the -[IBC spec on Packet Lifecycle](https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#packet-flow--handling) -is missing all useful diagrams, but it may provide some theoretical background -for this text if you wish to look. - -In short, IBC allows us to send packets from chain A to chain B and get a -response from them. The first step is the contract/module in chain A requesting -to send a packet. This is then relayed to chain B, where it "receives" the -packet and calculates an "acknowledgement" (which may contain a success result -or an error message, as opaque bytes to be interpreted by the sending contract). -The acknowledgement is then relayed back to chain A, completing the cycle. - -In some cases, the packet may never be delivered, and if it is proven not to be -delivered before the timeout, this can abort the packet, calling the "timeout" -handler on chain A. In this case, chain A sends and later gets "timeout". No -"receive" nor "acknowledgement" callbacks are ever executed. - -#### Sending a Packet - -In order to send a packet, a contract can simply return `IbcMsg::SendPacket` -along with the channel over which to send the packet (which you saved in -`ibc_channel_connect`), as well as opaque data bytes to be interpreted by the -other side. You must also return a timeout either as block height or block time -of the remote chain, just like in the ICS20 `Transfer` messages above: - -```rust -pub enum IbcMsg { - /// Sends an IBC packet with given data over the existing channel. - /// Data should be encoded in a format defined by the channel version, - /// and the module on the other side should know how to parse this. - SendPacket { - channel_id: String, - data: Binary, - /// when packet times out, measured on remote chain - timeout: IbcTimeout, - }, -} -``` - -For the content of the `data` field, we recommend that you model it on the -format of `ExecuteMsg` (an enum with serde) and encode it via -`cosmwasm_std::to_binary(&packet_msg)?`. This is the approach for a new protocol -you develop with cosmwasm contracts. If you are working with an existing -protocol, please read their spec and create the proper type along with JSON or -Protobuf encoders for it as the protocol requires. - -#### Receiving a Packet - -After a contract on chain A sends a packet, it is generally processed by the -contract on chain B on the other side of the channel. This is done by executing -the following entry point on chain B: - -```rust -#[entry_point] -pub fn ibc_packet_receive( - deps: DepsMut, - env: Env, - msg: IbcPacketReceiveMsg, -) -> Result { - // ... -} -``` - -This is a very special entry point as it has a unique workflow. (Please see the -[Acknowledging Errors section](#Acknowledging-Errors) below to understand it -fully). - -Also note the different return response here (`IbcReceiveResponse` rather than -`IbcBasicResponse`). This is because it has an extra field -`acknowledgement: Binary`, which must be filled out. All successful message must -return an encoded `Acknowledgement` response in this field, that can be parsed -by the sending chain. - -The -[`IbcPacket` structure](https://github.com/CosmWasm/cosmwasm/blob/v0.15.0/packages/std/src/ibc.rs#L176-L187) -contains all information needed to process the receipt. This info has already -been verified by the core IBC modules via light client and merkle proofs. It -guarantees all metadata in the `IbcPacket` structure is valid, and the `data` -field was written on the remote chain. Furthermore, it guarantees that the -packet is processed at most once (zero times if it times out). Fields like -`dest.channel_id` and `sequence` have a similar trust level to `MessageInfo`, -which we use to authorize normal transactions. The `data` field should be -treated like the `ExecuteMsg` data, which is only as valid as the entity that -signed it. - -You can generally ignore `timeout_*` (this entry point is only called if it -hasn't yet timed out) and `sequence` (which is used by the IBC framework to -avoid duplicates). I generally use `dest.channel_id` like `info.sender` to -authenticate the packet, and parse `data` into a `PacketMsg` structure, using -the same encoding rules as we discussed in the last section. After that you can -process `PacketMsg` more or less like an `ExecuteMsg`, including calling into -other contracts. - -```rust -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct IbcPacket { - /// The raw data send from the other side in the packet - pub data: Binary, - /// identifies the channel and port on the sending chain. - pub src: IbcEndpoint, - /// identifies the channel and port on the receiving chain. - pub dest: IbcEndpoint, - /// The sequence number of the packet on the given channel - pub sequence: u64, - pub timeout: IbcTimeout, -} -``` - -`IbcPacketReceiveMsg` is currently a wrapper around `IbcPacket`. - -##### Acknowledging Errors - -A major issue that is unique to `ibc_packet_receive` is that it is expected to -often reject an incoming packet, yet it cannot abort the transaction. We -actually expect all state changes from the contract (as well as dispatched -messages) to be reverted when the packet is rejected, but the transaction to -properly commit an acknowledgement with encoded error. In other words, this "IBC -Handler" will error and revert, but the "IBC Router" must succeed and commit an -acknowledgement message (that can be parsed by the sending chain as an error). - -The atomicity issue was first -[analyzed in the Cosmos SDK implementation](https://github.com/cosmos/ibc-go/issues/68) -and refined into -[changing semantics of the OnRecvPacket SDK method](https://github.com/cosmos/ibc-go/issues/91), -which was -[implemented in April 2021](https://github.com/cosmos/ibc-go/pull/107), likely -to be released with Cosmos SDK 0.43 or 0.44. Since we want the best, -future-proof interface for contracts, we will use an approach inspired by that -work, and add an adapter in `wasmd` until we can upgrade to a Cosmos SDK version -that implements this. - -After quite some -[discussion on how to encode the errors](https://github.com/CosmWasm/cosmwasm/issues/762), -we struggled to map this idea to the CosmWasm model. However, we also discovered -a deep similarity between these requirements and the -[submessage semantics](./SEMANTICS.md#submessages). It just requires some -careful coding on the contract developer's side to not throw errors. This -produced 3 suggestions on how to handle errors and rollbacks _inside -`ibc_packet_receive`_ - -1. If the message doesn't modify any state directly, you can simply put the - logic in a closure, and capture errors, converting them into error - acknowledgements. This would look something like the - [main dispatch loop in `ibc-reflect`](https://github.com/CosmWasm/cosmwasm/blob/cd784cd1148ee395574f3e564f102d0d7b5adcc3/contracts/ibc-reflect/src/contract.rs#L217-L248): - - ```rust - pub fn ibc_packet_receive( - deps: DepsMut, - _env: Env, - msg: IbcPacketReceiveMsg, - ) -> Result { - (|| { - // which local channel did this packet come on - let caller = packet.dest.channel_id; - let msg: PacketMsg = from_slice(&packet.data)?; - match msg { - PacketMsg::Dispatch { msgs } => receive_dispatch(deps, caller, msgs), - PacketMsg::WhoAmI {} => receive_who_am_i(deps, caller), - PacketMsg::Balances {} => receive_balances(deps, caller), - } - })() - .or_else(|e| { - // we try to capture all app-level errors and convert them into - // acknowledgement packets that contain an error code. - let acknowledgement = encode_ibc_error(format!("invalid packet: {}", e)); - Ok(IbcReceiveResponse { - acknowledgement, - submessages: vec![], - messages: vec![], - attributes: vec![], - }) - }) - } - ``` - -2. If we modify state with an external call, we need to wrap it in a - `submessage` and capture the error. This approach requires we use _exactly - one_ submessage. If we have multiple, we may commit #1 and rollback #2 (see - example 3 for that case). The main point is moving `messages` to - `submessages` and reformatting the error in `reply`. Note that if you set the - `Response.data` field in `reply` it will override the acknowledgement - returned from the parent call. (See - [bottom of reply section](./SEMANTICS.md#handling-the-reply)). You can see a - similar example in how - [`ibc-reflect` handles `receive_dispatch`](https://github.com/CosmWasm/cosmwasm/blob/eebb9395ccf315320e3f2fcc526ee76788f89174/contracts/ibc-reflect/src/contract.rs#L307-L336). - Note how we use a unique reply ID for this and use that to catch any - execution failure and return an error acknowledgement instead: - - ```rust - fn receive_dispatch( - deps: DepsMut, - caller: String, - msgs: Vec, - ) -> StdResult { - // what is the reflect contract here - let reflect_addr = accounts(deps.storage).load(caller.as_bytes())?; - - // let them know we're fine - let acknowledgement = to_binary(&AcknowledgementMsg::::Ok(()))?; - // create the message to re-dispatch to the reflect contract - let reflect_msg = ReflectExecuteMsg::ReflectMsg { msgs }; - let wasm_msg = wasm_execute(reflect_addr, &reflect_msg, vec![])?; - - // we wrap it in a submessage to properly report errors - let sub_msg = SubMsg { - id: RECEIVE_DISPATCH_ID, - msg: wasm_msg.into(), - gas_limit: None, - reply_on: ReplyOn::Error, - }; - - Ok(IbcReceiveResponse { - acknowledgement, - submessages: vec![sub_msg], - messages: vec![], - attributes: vec![attr("action", "receive_dispatch")], - }) - } - - #[entry_point] - pub fn reply(deps: DepsMut, _env: Env, reply: Reply) -> StdResult { - match (reply.id, reply.result) { - (RECEIVE_DISPATCH_ID, ContractResult::Err(err)) => Ok(Response { - data: Some(encode_ibc_error(err)), - ..Response::default() - }), - (INIT_CALLBACK_ID, ContractResult::Ok(response)) => handle_init_callback(deps, response), - _ => Err(StdError::generic_err("invalid reply id or result")), - } - } - ``` - -3. For a more complex case, where we are modifying local state and possibly - sending multiple messages, we need to do a self-call via submessages. What I - mean is that we create a new `ExecuteMsg` variant, which returns an error if - called by anyone but the contract itself - (`if info.sender != env.contract.address { return Err() }`). When receiving - the IBC packet, we can create a submessage with `ExecuteMsg::DoReceivePacket` - and any args we need to pass down. - - `DoReceivePacket` should return a proper acknowledgement payload on success. - And return an error on failure, just like a normal `execute` call. However, - here we capture both success and error cases in the `reply` handler (use - `ReplyOn::Always`). For success, we return this data verbatim to be set as - the packet acknowledgement, and for errors, we encode them as we did above. - There is not any example code using this (yet), but it is just recombining - pieces we already have. For clarity, the `reply` statement should look - something like: - - ```rust - #[entry_point] - pub fn reply(_deps: DepsMut, _env: Env, reply: Reply) -> StdResult { - if reply.id != DO_IBC_RECEIVE_ID { - return Err(StdError::generic_err("invalid reply id")); - } - let data = match reply.result { - ContractResult::Ok(response) => response.data, - ContractResult::Err(err) => Some(encode_ibc_error(err)), - }; - Ok(Response { - data, - ..Response::default() - }) - } - ``` - -##### Standard Acknowledgement Envelope - -Although the ICS spec leave the actual acknowledgement as opaque bytes, it does -provide a recommendation for the format you can use, allowing contracts to -easily differentiate between success and error (and allow IBC explorers to label -such packets without knowing every protocol). - -It is defined as part of the -[ICS4 - Channel Spec](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/ibc/core/channel/v1/channel.proto#L134-L147). - -```proto -message Acknowledgement { - // response contains either a result or an error and must be non-empty - oneof response { - bytes result = 21; - string error = 22; - } -} -``` - -Although it suggests this is a Protobuf object, the ICS spec doesn't define -whether to encode it as JSON or Protobuf. In the ICS20 implementation, this is -JSON encoded when returned from a contract. In ICS27, the authors are discussing -using a Protobuf-encoded form of this structure. - -Note that it leaves the actual success response as app-specific bytes where you -can place anything, but does provide a standard way for an observer to check -success-or-error. If you are designing a new protocol, I encourage you to use -this struct in either of the encodings as the acknowledgement envelope. - -You can find a -[CosmWasm-compatible definition of this format](https://github.com/CosmWasm/cosmwasm-plus/blob/v0.6.0-beta1/contracts/cw20-ics20/src/ibc.rs#L52-L72) -as part of the `cw20-ics20` contract, along with JSON-encoding. Protobuf -encoding version can be produced upon request. - -#### Receiving an Acknowledgement - -If chain B successfully received the packet (even if the contract returned an -error message), chain A will eventually get an acknowledgement: - -```rust -#[entry_point] -/// never should be called as we do not send packets -pub fn ibc_packet_ack( - deps: DepsMut, - env: Env, - msg: IbcPacketAckMsg, -) -> StdResult { } -``` - -The -[`IbcAcknowledgement` structure](https://github.com/CosmWasm/cosmwasm/blob/v0.15.0/packages/std/src/ibc.rs#L195-L200) -contains both the original packet that was sent as well as the acknowledgement -bytes returned from executing the remote contract. You can use the -`original_packet` to -[map it the proper handler](https://github.com/CosmWasm/cosmwasm/blob/378b029707ebaed4505b3666c620bed69ff9a682/contracts/ibc-reflect-send/src/ibc.rs#L111-L136) -(after parsing your custom data format), and parse the `acknowledgement` there, -to determine how to respond: - -```rust -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct IbcAcknowledgement { - pub acknowledgement: Binary, - pub original_packet: IbcPacket, -} -``` - -On success, you will want to commit the pending state. For some contracts like -`cw20-ics20`, you accept the tokens before sending the packet, so no need to -commit any more state. On other contracts, you may want to store the data -returned as part of the acknowledgement (like -[storing the remote address after calling "WhoAmI"](https://github.com/CosmWasm/cosmwasm/blob/v0.14.0-beta4/contracts/ibc-reflect-send/src/ibc.rs#L157-L192) -in our simple `ibc-reflect` example. - -On error, you will want to revert any state that was pending based on the -packet. For example, in ics20, if the -[remote chain rejects the packet](https://github.com/CosmWasm/cw-plus/blob/v0.10.0/contracts/cw20-ics20/src/ibc.rs#L248), -we must -[return the funds to the original sender](https://github.com/CosmWasm/cw-plus/blob/v0.10.0/contracts/cw20-ics20/src/ibc.rs#L291-L313). - -#### Handling Timeouts - -If the packet was not received on chain B before the timeout, we can be certain -that it will never be processed there. In such a case, a relayer can return a -timeout proof to cancel the pending packet. In such a case the calling contract -will never get `ibc_packet_ack`, but rather `ibc_packet_timeout`. One of the two -calls will eventually get called for each packet that is sent as long as there -is a functioning relayer. (In the absence of a functioning relayer, it will -never get a response). - -The timeout callback looks like this: - -```rust -#[entry_point] -/// never should be called as we do not send packets -pub fn ibc_packet_timeout( - deps: DepsMut, - env: Env, - msg: IbcPacketTimeoutMsg, -) -> StdResult {} -``` - -It is generally handled just like the error case in `ibc_packet_ack`, reverting -the state change from sending the packet (eg. if we send tokens over ICS20, both -[an ack failure](https://github.com/CosmWasm/cw-plus/blob/v0.10.0/contracts/cw20-ics20/src/ibc.rs#L248) -as well as -[a timeout](https://github.com/CosmWasm/cw-plus/blob/v0.10.0/contracts/cw20-ics20/src/ibc.rs#L261) -will return those tokens to the original sender. In fact they both dispatch to -the same `on_packet_failure` function). - -Note that like `ibc_packet_ack`, we get the original packet we sent, which must -contain all information needed to revert itself. Thus the ICS20 packet contains -the original sender address, even though that is unimportant in the receiving -chain. +Information on IBC can be found in [the CosmWasm documentation](https://docs.cosmwasm.com/ibc) \ No newline at end of file diff --git a/SEMANTICS.md b/SEMANTICS.md index 4e5d750b6c..f8073ebdb8 100644 --- a/SEMANTICS.md +++ b/SEMANTICS.md @@ -1,404 +1,4 @@ -# Contract Semantics - -This document aims to clarify the semantics of how a CosmWasm contract interacts -with its environment. There are two main types of actions: _mutating_ actions, -which receive `DepsMut` and are able to modify the state of the blockchain, and -_query_ actions, which are run on a single node with read-only access to the -data. - -## Execution - -In the section below, we will discuss how the `execute` call works, but the same -semantics apply to any other _mutating_ action - `instantiate`, `migrate`, -`sudo`, etc. - -### SDK Context - -Before looking at CosmWasm, we should look at the (somewhat under-documented) -semantics enforced by the blockchain framework we integrate with - the -[Cosmos SDK](https://v1.cosmos.network/sdk). It is based on the -[Tendermint BFT](https://tendermint.com/core/) Consensus Engine. Let us first -look at how they process transactions before they arrive in CosmWasm (and after -they leave). - -First, the Tendermint engine will seek 2/3+ consensus on a list of transactions -to be included in the next block. This is done _without executing them_. They -are simply subjected to a minimal pre-filter by the Cosmos SDK module, to ensure -they are validly formatted transactions, with sufficient gas fees, and signed by -an account with sufficient fees to pay it. Notably, this means many transactions -that error may be included in a block. - -Once a block is committed (typically every 5s or so), the transactions are then -fed to the Cosmos SDK sequentially in order to execute them. Each one returns a -result or error along with event logs, which are recorded in the `TxResults` -section of the next block. The `AppHash` (or merkle proof or blockchain state) -after executing the block is also included in the next block. - -The Cosmos SDK `BaseApp` handles each transaction in an isolated context. It -first verifies all signatures and deducts the gas fees. It sets the "Gas Meter" -to limit the execution to the amount of gas paid for by the fees. Then it makes -an isolated context to run the transaction. This allows the code to read the -current state of the chain (after the last transaction is finished), but it only -writes to a cache, which may be committed or rolled back on error. - -A transaction may consist of multiple messages and each one is executed in turn -under the same context and same gas limit. If all messages succeed, the context -will be committed to the underlying blockchain state and the results of all -messages will be stored in the `TxResult`. If one message fails, all later -messages are skipped and all state changes are reverted. This is very important -for atomicity. That means Alice and Bob can both sign a transaction with 2 -messages: Alice pays Bob 1000 ATOM, Bob pays Alice 50 ETH, and if Bob doesn't -have the funds in his account, Alice's payment will also be reverted. This is -just like a DB Transaction typically works. - -[`x/wasm`](https://github.com/CosmWasm/wasmd/tree/master/x/wasm) is a custom -Cosmos SDK module, which processes certain messages and uses them to upload, -instantiate, and execute smart contracts. In particular, it accepts a properly -signed -[`MsgExecuteContract`](https://github.com/CosmWasm/wasmd/blob/master/proto/cosmwasm/wasm/v1beta1/tx.proto#L76-L89), -routes it to -[`Keeper.Execute`](https://github.com/CosmWasm/wasmd/blob/master/x/wasm/keeper/keeper.go#L311-L355), -which loads the proper smart contract and calls `execute` on it. Note that this -method may either return a success (with data and events) or an error. In the -case of an error here, it will revert the entire transaction in the block. This -is the context we find ourselves in when our contract receives the `execute` -call. - -### Basic Execution - -When we implement a contract, we provide the following entry point: - -```rust -pub fn execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> Result { } -``` - -With `DepsMut`, this can read and write to the backing `Storage`, as well as use -the `Api` to validate addresses, and `Query` the state of other contracts or -native modules. Once it is done, it returns either `Ok(Response)` or -`Err(ContractError)`. Let's examine what happens next: - -If it returns `Err`, this error is converted to a string representation -(`err.to_string()`), and this is returned to the SDK module. _All state changes -are reverted_ and `x/wasm` returns this error message, which will _generally_ -(see submessage exception below) abort the transaction, and return this same -error message to the external caller. - -If it returns `Ok`, the `Response` object is parsed and processed. Let's look at -the parts here: - -```rust -pub struct Response -where - T: Clone + fmt::Debug + PartialEq + JsonSchema, -{ - /// Optional list of "subcalls" to make. These will be executed in order - /// (and this contract's subcall_response entry point invoked) - /// *before* any of the "fire and forget" messages get executed. - pub submessages: Vec>, - /// After any submessages are processed, these are all dispatched in the host blockchain. - /// If they all succeed, then the transaction is committed. If any fail, then the transaction - /// and any local contract state changes are reverted. - pub messages: Vec>, - /// The attributes that will be emitted as part of a "wasm" event - pub attributes: Vec, - pub data: Option, -} -``` - -In the Cosmos SDK, a transaction returns a number of events to the user, along -with an optional data "result". This result is hashed into the next block hash -to be provable and can return some essential state (although in general client -apps rely on Events more). This result is more commonly used to pass results -between contracts or modules in the sdk. Note that the `ResultHash` includes -only the `Code` (non-zero meaning error) and `Result` (data) from the -transaction. Events and logs are available via queries, but there are no -light-client proofs possible. - -If the contract sets `data`, this will be returned in the `result` field. -`attributes` is a list of `{key, value}` pairs which will be -[appended to a default event](https://github.com/CosmWasm/wasmd/blob/master/x/wasm/types/types.go#L302-L321). -The final result looks like this to the client: - -```json -{ - "type": "wasm", - "attributes": [ - { "key": "contract_addr", "value": "cosmos1234567890qwerty" }, - { "key": "custom-key-1", "value": "custom-value-1" }, - { "key": "custom-key-2", "value": "custom-value-2" } - ] -} -``` - -### Dispatching Messages - -Now let's move on to the `messages` field. Some contracts are fine only talking -with themselves, such as a cw20 contract just adjusting its balances on -transfers. But many want to move tokens (native or cw20) or call into other -contracts for more complex actions. This is where messages come in. We return -[`CosmosMsg`, which is a serializable representation](https://github.com/CosmWasm/cosmwasm/blob/v0.14.0-beta4/packages/std/src/results/cosmos_msg.rs#L18-L40) -of any external call the contract can make. It looks something like this (with -`stargate` feature flag enabled): - -```rust -pub enum CosmosMsg -where - T: Clone + fmt::Debug + PartialEq + JsonSchema, -{ - Bank(BankMsg), - /// This can be defined by each blockchain as a custom extension - Custom(T), - Staking(StakingMsg), - Distribution(DistributionMsg), - Stargate { - type_url: String, - value: Binary, - }, - Ibc(IbcMsg), - Wasm(WasmMsg), -} -``` - -If a contract returns two messages - M1 and M2, these will both be parsed and -executed in `x/wasm` _with the permissions of the contract_ (meaning -`info.sender` will be the contract not the original caller). If they return -success, they will emit a new event with the custom attributes, the `data` field -will be ignored, and any messages they return will also be processed. If they -return an error, the parent call will return an error, thus rolling back the -state of the whole transaction. - -Note that the messages are executed -[_depth-first_](https://en.wikipedia.org/wiki/Depth-first_search). This means if -contract A returns M1 (`WasmMsg::Execute`) and M2 (`BankMsg::Send`), and -contract B (from the `WasmMsg::Execute`) returns N1 and N2 (eg. `StakingMsg` and -`DistributionMsg`), the order of execution would be **M1, N1, N2, M2**. - -```mermaid -graph TD; - A[contract A] - M1[M1 / contract B] - M2[M2 / bank send] - N1[N1 / staking] - N2[N2 / distribution] - A --> M1; - A --> M2; - M1 --> N1; - M1 --> N2; -``` - -This may be hard to understand at first. "Why can't I just call another -contract?", you may ask. However, we do this to prevent one of the most -widespread and hardest to detect security holes in Ethereum contracts - -reentrancy. We do this by following the actor model, which doesn't nest function -calls, but returns messages that will be executed later. This means all state -that is carried over between one call and the next happens in storage and not in -memory. For more information on this design, I recommend you read -[our docs on the Actor Model](https://book.cosmwasm.com/actor-model.html). - -### Submessages - -As of CosmWasm 0.14 (April 2021), we have added yet one more way to dispatch -calls from the contract. A common request was the ability to get the result from -one of the messages you dispatched. For example, you want to create a new -contract with `WasmMsg::Instantiate`, but then you need to store the address of -the newly created contract in the caller. With `submessages`, this is now -possible. It also solves a similar use-case of capturing the error results, so -if you execute a message from eg. a cron contract, it can store the error -message and mark the message as run, rather than aborting the whole transaction. -It also allows for limiting the gas usage of the submessage (this is not -intended to be used for most cases, but is needed for eg. the cron job to -protect it from an infinite loop in the submessage burning all gas and aborting -the transaction). - -Submessage is a generalization of the message concept: indeed, a message is -simply a submessage that never handles any response. - -This makes use of `CosmosMsg` as above, but it wraps it inside a `SubMsg` -envelope: - -```rust -pub struct SubMsg -where - T: Clone + fmt::Debug + PartialEq + JsonSchema, -{ - pub id: u64, - pub msg: CosmosMsg, - pub gas_limit: Option, - pub reply_on: ReplyOn, -} - -pub enum ReplyOn { - /// Always perform a callback after SubMsg is processed - Always, - /// Only callback if SubMsg returned an error, no callback on success case - Error, - /// Only callback if SubMsg was successful, no callback on error case - Success, - /// Never make as callback - equivalent to a message - Never, -} -``` - -What are the semantics of a submessage execution? First, we create a -sub-transaction context around the state, allowing it to read the latest state -written by the caller, but write to yet-another cache. If `gas_limit` is set, it -is sandboxed to how much gas it can use until it aborts with `OutOfGasError`. -This error is caught and returned to the caller like any other error returned -from contract execution (unless it burned the entire gas limit of the -transaction). What is more interesting is what happens on completion. - -If it returns success, the temporary state is committed (into the caller's -cache), and the `Response` is processed as normal (an event is added to the -current EventManager, messages and submessages are executed). Once the -`Response` is fully processed, this may then be intercepted by the calling -contract (for `ReplyOn::Always` and `ReplyOn::Success`). On an error, the -subcall will revert any partial state changes due to this message, but not -revert any state changes in the calling contract. The error may then be -intercepted by the calling contract (for `ReplyOn::Always` and -`ReplyOn::Error`). _In this case, the messages error doesn't abort the whole -transaction_ - -Note, that error doesn't abort the whole transaction _if and only if_ the -`reply` is called - so in the case of `ReplyOn::Always` and `ReplyOn::Error`. If -the submessage is called with `ReplyOn::Success` (or `ReplyOn::Never`, which -makes it effectively a normal message), the error in subsequent call would -result in failing the whole transaction and not committing the changes for it. -The rule here is as follows: if for any reason you want your message handling to -succeed on submessage failure, you always have to reply on failure. - -Obviously - on the successful processing of sub-message, if the reply is not -called (in particular `ReplyOn::Error`), the whole transaction is assumed to -succeed, and is committed. - -#### Handling the Reply - -In order to make use of `submessages`, the calling contract must have an extra -entry point: - -```rust -#[entry_point] -pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result { } - -pub struct Reply { - pub id: u64, - pub gas_used: u64, - /// SubMsgResult is just a nicely serializable version of `Result` - pub result: SubMsgResult, -} - -pub struct SubMsgResponse { - pub events: Vec, - pub data: Option, -} -``` - -After the `submessage` is finished, the caller will get a chance to handle the -result. It will get the original `id` of the subcall so it can switch on how to -process this, and the `Result` of the execution, both success and error. Note -that it includes all events returned by the submessage, which applies to native -sdk modules (like Bank) as well as the data returned from below. This and the -original call id provide all context to continue processing it. If you need more -state, you must save some local context to the store (under the `id`) before -returning the `submessage` in the original `execute`, and load it in `reply`. We -explicitly prohibit passing information in contract memory, as that is the key -vector for reentrancy attacks, which are a large security surface area in -Ethereum. - -The `reply` call may return `Err` itself, in which case it is treated like the -caller errored, and aborting the transaction. However, on successful processing, -`reply` may return a normal `Response`, which will be processed as normal - -events added to the EventManager, and all `messages` and `submessages` -dispatched as described above. - -The one _critical difference_ with `reply`, is that we _do not drop data_. If -`reply` returns `data: Some(value)` in the `Response` object, we will overwrite -the `data` field returned by the caller. That is, if `execute` returns -`data: Some(b"first thought")` and the `reply` (with all the extra information -it is privy to) returns `data: Some(b"better idea")`, then this will be returned -to the caller of `execute` (either the client or another transaction), just as -if the original `execute` and returned `data: Some(b"better idea")`. If `reply` -returns `data: None`, it will not modify any previously set data state. If there -are multiple submessages all setting this, only the last one is used (they all -overwrite any previous `data` value). As a consequence, you can use -`data: Some(b"")` to clear previously set data. This will be represented as a -JSON string instead of `null` and handled as any other `Some` value. - -#### Order and Rollback - -Submessages follow the same _depth first_ order rules as `messages`, with their -replies considered as an immediate additional message call. Here is a simple -example. Contract A returns submessages S1 and S2, and message M1. Submessage S1 -returns message N1. The order will be: **S1, N1, reply(S1), S2, reply(S2), M1**. - -Please keep in mind that submessage `execution` and `reply` can happen within -the context of another submessage. For example -`contract-A--submessage --> contract-B--submessage --> contract-C`. Then -`contract-B` can revert the state for `contract-C` and itself by returning `Err` -in the submessage `reply`, but not revert contract-A or the entire transaction. -It just ends up returning `Err` to contract-A's `reply` function. - -Note that errors are not handled with `ReplyOn::Success`, meaning, in such a -case, an error will be treated just like a normal `message` returning an error. -This diagram may help explain. Imagine a contract returned two submessages - (a) -with `ReplyOn::Success` and (b) with `ReplyOn::Error`: - -| processing a) | processing b) | reply called | may overwrite result from reply | note | -| ------------- | ------------- | ------------ | ------------------------------- | ------------------------------------------------- | -| ok | ok | a) | a) | returns success | -| err | err | none | none | returns error (abort parent transaction) | -| err | ok | none | none | returns error (abort parent transaction) | -| ok | err | a)b) | a)b) | if both a) and b) overwrite, only b) will be used | - -## Query Semantics - -Until now, we have focused on the `Response` object, which allows us to execute -code in other contracts via the actor model. That is, each contract is run -sequentially, one after another, and no nested calls are possible. This is -essential to avoid reentrancy, which is when calling into another contract can -change my state while I am in the middle of a transaction. - -However, there are many times we need access to information from other contracts -in the middle of processing, such as determining the contract's bank balance -before sending funds. To enable this, we have exposed the _read only_ `Querier` -to enable _synchronous_ calls in the middle of the execution. By making it -read-only (and enforcing that in the VM level), we can prevent the possibility -of reentrancy, as the query cannot modify any state or execute our contract. - -When we "make a query", we serialize a -[`QueryRequest` struct](https://github.com/CosmWasm/cosmwasm/blob/v0.14.0-beta4/packages/std/src/query/mod.rs#L27-L48) -that represents all possible calls, and then pass that over FFI to the runtime, -where it is interpreted in the `x/wasm` SDK module. This is extensible with -blockchain-specific custom queries just like `CosmosMsg` accepts custom results. -Also note the ability to perform raw protobuf "Stargate" queries: - -```rust -pub enum QueryRequest { - Bank(BankQuery), - Custom(C), - Staking(StakingQuery), - Stargate { - /// this is the fully qualified service path used for routing, - /// eg. custom/cosmos_sdk.x.bank.v1.Query/QueryBalance - path: String, - /// this is the expected protobuf message type (not any), binary encoded - data: Binary, - }, - Ibc(IbcQuery), - Wasm(WasmQuery), -} -``` - -While this is flexible and needed encoding for the cross-language -representation, this is a bit of mouthful to generate and use when I just want -to find my bank balance. To help that, we often use -[`QuerierWrapper`](https://github.com/CosmWasm/cosmwasm/blob/v0.14.0-beta4/packages/std/src/traits.rs#L148-L314), -which wraps a `Querier` and exposes a lot of convenience methods that just use -`QueryRequest` and `Querier.raw_query` under the hood. - -You can read a longer explanation of the -[`Querier` design in our docs](https://docs.cosmwasm.com/0.13/architecture/query.html). +This file was updated and moved to: + * [Semantics](https://docs.cosmwasm.com/core/architecture/semantics) + * [Transactions](https://docs.cosmwasm.com/core/architecture/transactions) + * [Reply](https://docs.cosmwasm.com/core/entrypoints/reply)