This guide explains what is needed to upgrade contracts when migrating over
major releases of cosmwasm
. Note that you can also view the
complete CHANGELOG to understand the differences.
-
Update
cosmwasm-*
dependencies in Cargo.toml (skip the ones you don't use):[dependencies] cosmwasm-std = "1.1.0" cosmwasm-storage = "1.1.0" # ... [dev-dependencies] cosmwasm-schema = "1.1.0" cosmwasm-vm = "1.1.0" # ...
-
There are changes to how we generate schemas, resulting in less boilerplace maintenance for smart contract devs. Old contracts will continue working for a while, but it's highly recommended to migrate now.
Your contract should have a
cosmwasm_schema
dependency in itsCargo.toml
file. Move it fromdev-dependencies
to regulardependencies
.[dependencies] + cosmwasm-schema = { version = "1.1.0" } cosmwasm-std = { version = "1.1.0", features = ["stargate"] } cw-storage-plus = { path = "../../packages/storage-plus", version = "0.10.0" } schemars = "0.8.1" serde = { version = "1.0.103", default-features = false, features = ["derive"] } thiserror = { version = "1.0.23" } [dev-dependencies] - cosmwasm-schema = { version = "1.1.0" }
Types you send to the contract and receive back are annotated with a bunch of derives and sometimes
serde
annotations. Remove all those attributes and replace them with#[cosmwasm_schema::cw_serde]
.+ use cosmwasm_schema::{cw_serde, QueryResponses}; // *snip* - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] - #[serde(deny_unknown_fields, rename_all = "snake_case")] + #[cw_serde] pub enum ExecuteMsg { Release {}, Argon2 { mem_cost: u32, time_cost: u32, }, }
Derive
cosmwasm_schema::QueryResponses
for yourQueryMsg
type and annotate each query with its return type. This lets the interface description file (schema) generation know what return types to include - and therefore, any clients relying on the generated schemas will also know how to interpret response data from your contract.#[cw_serde] + #[derive(QueryResponses)] pub enum QueryMsg { + #[returns(VerifierResponse)] Verifier {}, + #[returns(Uint128)] Balance { address: String }, }
The boilerplate in
examples/schema.rs
is also replaced with a macro invocation. Just give it all the types sent to the contract's entrypoints. Skip the ones that are not present in the contract - the only mandatory field isinstantiate
.use cosmwasm_schema::write_api; use hackatom::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, SudoMsg}; fn main() { write_api! { instantiate: InstantiateMsg, query: QueryMsg, execute: ExecuteMsg, sudo: SudoMsg, migrate: MigrateMsg, } }
This changes the format of the schemas generated by the contract. They're now in one structured, unified file (parseable by machines) rather than a bunch of arbitrary ones.
-
The minimum Rust supported version is 1.56.1. Verify your Rust version is >= 1.56.1 with:
rustc --version
. Please note that the required Rust version changes over time and we have little control over that due to the dependencies that are used. -
Simplify
mock_dependencies
calls with empty balance:#[test] fn instantiate_fails() { - let mut deps = mock_dependencies(&[]); + let mut deps = mock_dependencies(); let msg = InstantiateMsg {}; let info = mock_info("creator", &coins(1000, "earth"));
Or use the new
mock_dependencies_with_balance
if you need a balance:#[test] fn migrate_cleans_up_data() { - let mut deps = mock_dependencies(&coins(123456, "gold")); + let mut deps = mock_dependencies_with_balance(&coins(123456, "gold")); // store some sample data deps.storage.set(b"foo", b"bar");
-
Replace
ContractResult
withSubMsgResult
inReply
handling:@@ -35,10 +35,10 @@ pub fn instantiate( #[entry_point] pub fn reply(deps: DepsMut, _env: Env, reply: Reply) -> StdResult<Response> { match (reply.id, reply.result) { - (RECEIVE_DISPATCH_ID, ContractResult::Err(err)) => { + (RECEIVE_DISPATCH_ID, SubMsgResult::Err(err)) => { Ok(Response::new().set_data(encode_ibc_error(err))) } - (INIT_CALLBACK_ID, ContractResult::Ok(response)) => handle_init_callback(deps, response), + (INIT_CALLBACK_ID, SubMsgResult::Ok(response)) => handle_init_callback(deps, response), _ => Err(StdError::generic_err("invalid reply id or result")), } }
-
Replace
SubMsgExecutionResponse
withSubMsgResponse
:@@ -387,7 +384,7 @@ mod tests { // fake a reply and ensure this works let response = Reply { id, - result: SubMsgResult::Ok(SubMsgExecutionResponse { + result: SubMsgResult::Ok(SubMsgResponse { events: fake_events(&account), data: None, }),
-
Update CosmWasm dependencies in Cargo.toml (skip the ones you don't use):
[dependencies] cosmwasm-std = "1.0.0-beta" cosmwasm-storage = "1.0.0-beta" # ... [dev-dependencies] cosmwasm-schema = "1.0.0-beta" cosmwasm-vm = "1.0.0-beta" # ...
-
Use type
Record
instead ofPair
// before use cosmwasm_std::Pair; // after use cosmwasm_std::Record;
-
Replace
cosmwasm_std::create_entry_points!
andcosmwasm_std::create_entry_points_with_migration!
with#[entry_point]
annotations. See the 0.13 -> 0.14 entry where#[entry_point]
was introduced. -
If your chain provides a custom queries, add the custom query type as a generic argument to
cosmwasm_std::Deps
,DepsMut
,OwnedDeps
andQuerierWrapper
. Otherwise it defaults toEmpty
. E.g.#[entry_point] pub fn instantiate( - deps: DepsMut, + deps: DepsMut<CyberQueryWrapper>, _env: Env, info: MessageInfo, msg: InstantiateMsg, @@ -38,112 +35,95 @@ pub fn instantiate( }
#[entry_point] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> { +pub fn query(deps: Deps<CyberQueryWrapper>, _env: Env, msg: QueryMsg) -> StdResult<Binary> { match msg {
pub struct CyberQuerier<'a> { - querier: &'a QuerierWrapper<'a>, + querier: &'a QuerierWrapper<'a, CyberQueryWrapper>, } impl<'a> CyberQuerier<'a> { - pub fn new(querier: &'a QuerierWrapper) -> Self { + pub fn new(querier: &'a QuerierWrapper<'a, CyberQueryWrapper>) -> Self { CyberQuerier { querier } } }
Replace
QuerierWrapper::custom_query
withQuerierWrapper::query
which is now fully typed:-let res: CyberlinksAmountResponse = self.querier.custom_query(&request.into())?; +let res: CyberlinksAmountResponse = self.querier.query(&request.into())?;
See cybercongress/cw-cyber#2 for a complete example.
-
Add new
transaction
field toEnv
when creating a custom mock env:@@ -19,6 +19,7 @@ use cosmwasm_std::{ coins, Addr, BlockInfo, Coin, ContractInfo, Env, MessageInfo, Response, Timestamp, + TransactionInfo, }; use cosmwasm_storage::to_length_prefixed; use cosmwasm_vm::testing::{instantiate, mock_info, mock_instance}; @@ -52,6 +53,7 @@ fn mock_env_info_height(signer: &str, sent: &[Coin], height: u64, time: u64) -> contract: ContractInfo { address: Addr::unchecked(MOCK_CONTRACT_ADDR), }, + transaction: Some(TransactionInfo { index: 3 }), }; let info = mock_info(signer, sent); return (env, info);
-
Gas usage increases by a factor of approximately 150_000. Adapt your tests accordingly.
-
Update CosmWasm dependencies in Cargo.toml (skip the ones you don't use):
[dependencies] cosmwasm-std = "0.16.0" cosmwasm-storage = "0.16.0" # ... [dev-dependencies] cosmwasm-schema = "0.16.0" cosmwasm-vm = "0.16.0" # ...
-
The
attr
function now accepts arguments that implementInto<String>
rather thanToString
. This means that "stringly" types like&str
are still accepted, but others (like numbers or booleans) have to be explicitly converted to strings; you can use theto_string
method (from thestd::string::ToString
trait) for that.let steal_funds = true; - attr("steal_funds", steal_funds), + attr("steal_funds", steal_funds.to_string()),
It also means that
&&str
is no longer accepted. -
The
iterator
feature incosmwasm-std
,cosmwasm-vm
andcosmwasm-storage
is now enabled by default. If you want to use it, you don't have to explicitly enable it anymore.If you don't want to use it, you have to disable default features when depending on
cosmwasm-std
. Example:- cosmwasm-std = { version = "0.15.0" } + cosmwasm-std = { version = "0.16.0", default-features = false }
-
The
Event::attr
setter has been renamed toEvent::add_attribute
- this is for consistency with other types, likeResponse
.- let event = Event::new("ibc").attr("channel", "connect"); + let event = Event::new("ibc").add_attribute("channel", "connect");
-
Response
can no longer be built using a struct literal. Please useResponse::new
as well as relevant builder-style setters to set the data.This is a step toward better API stability.
#[entry_point] pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> StdResult<Response> { // ... let send = BankMsg::Send { to_address: msg.payout.clone(), amount: balance, }; let data_msg = format!("burnt {} keys", count).into_bytes(); - Ok(Response { - messages: vec![SubMsg::new(send)], - attributes: vec![attr("action", "burn"), attr("payout", msg.payout)], - events: vec![], - data: Some(data_msg.into()), - }) + Ok(Response::new() + .add_message(send) + .add_attribute("action", "burn") + .add_attribute("payout", msg.payout) + .set_data(data_msg)) }
- Ok(Response { - data: Some((old_size as u32).to_be_bytes().into()), - ..Response::default() - }) + Ok(Response::new().set_data((old_size as u32).to_be_bytes()))
- let res = Response { - messages: msgs, - attributes: vec![attr("action", "reflect_subcall")], - events: vec![], - data: None, - }; - Ok(res) + Ok(Response::new() + .add_attribute("action", "reflect_subcall") + .add_submessages(msgs))
-
For IBC-enabled contracts only: constructing
IbcReceiveResponse
andIbcBasicResponse
follows the same principles now asResponse
above.pub fn ibc_packet_receive( deps: DepsMut, env: Env, msg: IbcPacketReceiveMsg, ) -> StdResult<IbcReceiveResponse> { // ... - Ok(IbcReceiveResponse { - acknowledgement, - messages: vec![], - attributes: vec![], - events: vec![Event::new("ibc").attr("packet", "receive")], - }) + Ok(IbcReceiveResponse::new() + .set_ack(acknowledgement) + .add_event(Event::new("ibc").add_attribute("packet", "receive"))) }
-
For IBC-enabled contracts only: IBC entry points have different signatures. Instead of accepting bare packets, channels and acknowledgements, all of those are wrapped in a
Msg
type specific to the given entry point. Channels, packets and acknowledgements have to be unpacked from those.#[entry_point] - pub fn ibc_channel_open(_deps: DepsMut, _env: Env, channel: IbcChannel) -> StdResult<()> { + pub fn ibc_channel_open(_deps: DepsMut, _env: Env, msg: IbcChannelOpenMsg) -> StdResult<()> { + let channel = msg.channel(); // do things }
#[entry_point] pub fn ibc_channel_connect( deps: DepsMut, env: Env, - channel: IbcChannel, + msg: IbcChannelConnectMsg, ) -> StdResult<IbcBasicResponse> { + let channel = msg.channel(); // do things }
#[entry_point] pub fn ibc_channel_close( deps: DepsMut, env: Env, - channel: IbcChannel, + msg: IbcChannelCloseMsg, ) -> StdResult<IbcBasicResponse> { + let channel = msg.channel(); // do things }
#[entry_point] pub fn ibc_packet_receive( deps: DepsMut, env: Env, - packet: IbcPacket, + msg: IbcPacketReceiveMsg, ) -> StdResult<IbcReceiveResponse> { + let packet = msg.packet; // do things }
#[entry_point] pub fn ibc_packet_receive( deps: DepsMut, env: Env, - ack: IbcAcknowledgementWithPacket, + msg: IbcPacketReceiveMsg, ) -> StdResult<IbcBasicResponse> { // They are the same struct just a different name let ack = msg; // do things }
#[entry_point] pub fn ibc_packet_timeout( deps: DepsMut, env: Env, - packet: IbcPacket, + msg: IbcPacketTimeoutMsg, ) -> StdResult<IbcBasicResponse> { + let packet = msg.packet; // do things }
-
Update CosmWasm dependencies in Cargo.toml (skip the ones you don't use):
[dependencies] cosmwasm-std = "0.15.0" cosmwasm-storage = "0.15.0" # ... [dev-dependencies] cosmwasm-schema = "0.15.0" cosmwasm-vm = "0.15.0" # ...
-
Combine
messages
andsubmessages
on theResponse
object. The new format usesmessages: Vec<SubMsg<T>>
, so copysubmessages
content, and wrap old messages usingSubMsg::new
. Here is how to change messages:let send = BankMsg::Send { to_address, amount }; // before let res = Response { messages: vec![send.into()], ..Response::default() } // after let res = Response { messages: vec![SubMsg::new(send)], ..Response::default() } // alternate approach let mut res = Response::new(); res.add_message(send);
And here is how to change submessages:
// before let sub_msg = SubMsg { id: INIT_CALLBACK_ID, msg: msg.into(), gas_limit: None, reply_on: ReplyOn::Success, }; let res = Response { submessages: vec![sub_msg], ..Response::default() }; // after let msg = SubMsg::reply_on_success(msg, INIT_CALLBACK_ID); let res = Response { messages: vec![msg], ..Response::default() }; // alternate approach let msg = SubMsg::reply_on_success(msg, INIT_CALLBACK_ID); let mut res = Response::new(); res.add_submessage(msg);
Note that this means you can mix "messages" and "submessages" in any execution order. You are no more restricted to doing "submessages" first.
-
Rename the
send
field tofunds
whenever constructing aWasmMsg::Execute
orWasmMsg::Instantiate
value.let exec = WasmMsg::Execute { contract_addr: coin.address.into(), msg: to_binary(&msg)?, - send: vec![], + funds: vec![], };
-
Uint128
field can no longer be constructed using a struct literal. CallUint128::new
(orUint128::zero
) instead.- const TOKENS_PER_WEIGHT: Uint128 = Uint128(1_000); - const MIN_BOND: Uint128 = Uint128(5_000); + const TOKENS_PER_WEIGHT: Uint128 = Uint128::new(1_000); + const MIN_BOND: Uint128 = Uint128::new(5_000);
- assert_eq!(escrow_balance, Uint128(0)); + assert_eq!(escrow_balance, Uint128::zero());
-
If constructing a
Response
using struct literal syntax, add theevents
field.Ok(Response { messages: vec![], attributes, + events: vec![], data: None, })
-
For IBC-enabled contracts only: You need to adapt to the new
IbcAcknowledgementWithPacket
structure and use the embeddeddata
field:// before pub fn ibc_packet_ack( deps: DepsMut, env: Env, ack: IbcAcknowledgement, ) -> StdResult<Response> { let res: AcknowledgementMsg = from_slice(&ack.acknowledgement)?; // ... } // after pub fn ibc_packet_ack( deps: DepsMut, env: Env, ack: IbcAcknowledgementWithPacket, ) -> StdResult<Response> { let res: AcknowledgementMsg = from_slice(&ack.acknowledgement.data)?; // ... }
You also need to update the constructors in test code. Below we show how to do so both for JSON data as well as any custom binary format:
// before (JSON) let ack = IbcAcknowledgement { acknowledgement: to_binary(&AcknowledgementMsg::Ok(())).unwrap() original_packet: packet, }; // after (JSON) let ack = IbcAcknowledgementWithPacket { acknowledgement: IbcAcknowledgement::encode_json(&AcknowledgementMsg::Ok(())).unwrap(), original_packet: packet, }; // before (Custom binary data) let acknowledgement = vec![12, 56, 78]; let ack = IbcAcknowledgement { acknowledgement: Binary(acknowledgement), original_packet: packet, }; // after (Custom binary data) let acknowledgement = vec![12, 56, 78]; let ack = IbcAcknowledgement { acknowledgement: IbcAcknowledgement::new(acknowledgement), original_packet: packet, };
-
The minimum Rust supported version for 0.14 is 1.51.0. Verify your Rust version is >= 1.51.0 with:
rustc --version
-
Update CosmWasm and schemars dependencies in Cargo.toml (skip the ones you don't use):
[dependencies] cosmwasm-std = "0.14.0" cosmwasm-storage = "0.14.0" schemars = "0.8.1" # ... [dev-dependencies] cosmwasm-schema = "0.14.0" cosmwasm-vm = "0.14.0" # ...
-
Rename the
init
entry point toinstantiate
. Also, renameInitMsg
toInstantiateMsg
. -
Rename the
handle
entry point toexecute
. Also, renameHandleMsg
toExecuteMsg
. -
Rename
InitResponse
,HandleResponse
andMigrateResponse
toResponse
. The old names are still supported (with a deprecation warning), and will be removed in the next version. Also, you'll need to add thesubmessages
field toResponse
. -
Remove
from_address
fromBankMsg::Send
, which is now automatically filled with the contract address:// before ctx.add_message(BankMsg::Send { from_address: env.contract.address, to_address: to_addr, amount: balance, }); // after ctx.add_message(BankMsg::Send { to_address: to_addr, amount: balance, });
-
Use the new entry point system. From
lib.rs
remove#[cfg(target_arch = "wasm32")] cosmwasm_std::create_entry_points!(contract); // or #[cfg(target_arch = "wasm32")] cosmwasm_std::create_entry_points_with_migration!(contract);
Then add the macro attribute
#[entry_point]
to yourcontract.rs
as follows:use cosmwasm_std::{entry_point, … }; // … #[entry_point] pub fn init( _deps: DepsMut, _env: Env, _info: MessageInfo, _msg: InitMsg, ) -> StdResult<Response> { // … } #[entry_point] pub fn execute( _deps: DepsMut, _env: Env, _info: MessageInfo, _msg: ExecuteMsg, ) -> StdResult<Response> { // … } // only if you have migrate #[entry_point] pub fn migrate( deps: DepsMut, env: Env, _info: MessageInfo, msg: MigrateMsg, ) -> StdResult<Response> { // … } #[entry_point] pub fn query(_deps: Deps, _env: Env, _msg: QueryMsg) -> StdResult<QueryResponse> { // … }
-
Since
Response
contains adata
field, convertingContext
intoResponse
always succeeds.// before pub fn init(deps: DepsMut, env: Env, info: MessageInfo, msg: InitMsg) -> Result<InitResponse, HackError> { // … let mut ctx = Context::new(); ctx.add_attribute("Let the", "hacking begin"); Ok(ctx.try_into()?) } // after pub fn init(deps: DepsMut, env: Env, info: MessageInfo, msg: InitMsg) -> Result<Response, HackError> { // … let mut ctx = Context::new(); ctx.add_attribute("Let the", "hacking begin"); Ok(ctx.into()) }
-
Remove the
info: MessageInfo
field from themigrate
entry point:// Before pub fn migrate( deps: DepsMut, env: Env, _info: MessageInfo, msg: MigrateMsg, ) -> StdResult<MigrateResponse> { // ... } // After pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> StdResult<Response> { // ... }
MessageInfo::funds
was always empty since MsgMigrateContract does not have a funds field.MessageInfo::sender
should not be needed for authentication because the chain checks permissions before callingmigrate
. If the sender's address is needed for anything else, this should be expressed as part of the migrate message. -
Add mutating helper methods to
Response
that can be used instead of creating aContext
that is later converted to a response:// before pub fn handle_impl(deps: DepsMut, env: Env, info: MessageInfo) -> Result<Response, ContractError> { // ... // release counter_offer to creator let mut ctx = Context::new(); ctx.add_message(BankMsg::Send { to_address: state.creator, amount: state.counter_offer, }); // release collateral to sender ctx.add_message(BankMsg::Send { to_address: state.owner, amount: state.collateral, }); // .. ctx.add_attribute("action", "execute"); Ok(ctx.into()) } // after pub fn execute_impl(deps: DepsMut, env: Env, info: MessageInfo) -> Result<Response, ContractError> { // ... // release counter_offer to creator let mut resp = Response::new(); resp.add_message(BankMsg::Send { to_address: state.creator, amount: state.counter_offer, }); // release collateral to sender resp.add_message(BankMsg::Send { to_address: state.owner, amount: state.collateral, }); // .. resp.add_attribute("action", "execute"); Ok(resp) }
-
Use type
Pair
instead ofKV
// before use cosmwasm_std::KV; // after use cosmwasm_std::Pair;
-
If necessary, add a wildcard arm to the
match
of now non-exhaustive message typesBankMsg
,BankQuery
,WasmMsg
andWasmQuery
. -
HumanAddr
has been deprecated in favour of simplyString
. It never added any significant safety bonus overString
and was just a marker type. The new typeAddr
was created to hold validated addresses. Those can be created viaAddr::unchecked
,Api::addr_validate
,Api::addr_humanize
and JSON deserialization. In order to maintain type safety, deserialization intoAddr
must only be done from trusted sources like a contract's state or a query response. User inputs must be deserialized intoString
. This newAddr
type makes it easy to use human readable addresses in state:With pre-validated
Addr
fromMessageInfo
:// before pub struct State { pub owner: CanonicalAddr, } let state = State { owner: deps.api.canonical_address(&info.sender /* of type HumanAddr */)?, }; // after pub struct State { pub owner: Addr, } let state = State { owner: info.sender.clone() /* of type Addr */, };
With user input in
msg
:// before pub struct State { pub verifier: CanonicalAddr, pub beneficiary: CanonicalAddr, pub funder: CanonicalAddr, } deps.storage.set( CONFIG_KEY, &to_vec(&State { verifier: deps.api.canonical_address(&msg.verifier /* of type HumanAddr */)?, beneficiary: deps.api.canonical_address(&msg.beneficiary /* of type HumanAddr */)?, funder: deps.api.canonical_address(&info.sender /* of type HumanAddr */)?, })?, ); // after pub struct State { pub verifier: Addr, pub beneficiary: Addr, pub funder: Addr, } deps.storage.set( CONFIG_KEY, &to_vec(&State { verifier: deps.api.addr_validate(&msg.verifier /* of type String */)?, beneficiary: deps.api.addr_validate(&msg.beneficiary /* of type String */)?, funder: info.sender /* of type Addr */, })?, );
The existing
CanonicalAddr
remains unchanged and can be used in cases in which a compact binary representation is desired. For JSON state this does not save much data (e.g. the bech32 address cosmos1pfq05em6sfkls66ut4m2257p7qwlk448h8mysz takes 45 bytes as direct ASCII and 28 bytes when its canonical representation is base64 encoded). For fixed length database keysCanonicalAddr
remains handy though. -
Replace
StakingMsg::Withdraw
withDistributionMsg::SetWithdrawAddress
andDistributionMsg::WithdrawDelegatorReward
.StakingMsg::Withdraw
was a shorthand for the two distribution messages. However, it was unintuitive because it did not set the address for one withdraw only but for all following withdrawls. Since withdrawls are triggered by different events such as validators changing their commission rate, an address that was set for a one-time withdrawl would be used for future withdrawls not considered by the contract author.If the contract never set a withdraw address other than the contract itself (
env.contract.address
), you can simply replaceStakingMsg::Withdraw
withDistributionMsg::WithdrawDelegatorReward
. It is then never changed from the default. Otherwise you need to carefully track what the current withdraw address is. A one-time change can be implemented by emitted 3 messages:SetWithdrawAddress { address: recipient }
to temporarily change the recipientWithdrawDelegatorReward { validator }
to do a manual withdrawl from the given validatorSetWithdrawAddress { address: env.contract.address.into() }
to change it back for all future withdrawls
-
The block time in
env.block.time
is now aTimestamp
which stores nanosecond precision.env.block.time_nanos
was removed. If you need the compnents as before, uselet seconds = env.block.time.nanos() / 1_000_000_000; let nsecs = env.block.time.nanos() % 1_000_000_000;
-
The minimum Rust supported version for 0.13 is 1.47.0. Verify your Rust version is >= 1.47.0 with:
rustc --version
-
Update CosmWasm dependencies in Cargo.toml (skip the ones you don't use):
[dependencies] cosmwasm-std = "0.13.0" cosmwasm-storage = "0.13.0" # ... [dev-dependencies] cosmwasm-schema = "0.13.0" cosmwasm-vm = "0.13.0" # ...
-
Update CosmWasm dependencies in Cargo.toml (skip the ones you don't use):
[dependencies] cosmwasm-std = "0.12.0" cosmwasm-storage = "0.12.0" # ... [dev-dependencies] cosmwasm-schema = "0.12.0" cosmwasm-vm = "0.12.0" # ...
-
In your contract's
.cargo/config
remove--features backtraces
, which is now available in Rust nightly only:@@ -1,6 +1,6 @@ [alias] wasm = "build --release --target wasm32-unknown-unknown" wasm-debug = "build --target wasm32-unknown-unknown" -unit-test = "test --lib --features backtraces" +unit-test = "test --lib" integration-test = "test --test integration" schema = "run --example schema"
In order to use backtraces for debugging, run
RUST_BACKTRACE=1 cargo +nightly unit-test --features backtraces
. -
Rename the type
Extern
toDeps
, and radically simplify theinit
/handle
/migrate
/query
entrypoints. Rather than&mut Extern<S, A, Q>
, useDepsMut
. And instead of&Extern<S, A, Q>
, useDeps
. If you ever pass eg.foo<A: Api>(api: A)
around, you must now use dynamic trait pointers:foo(api: &dyn Api)
. Here is the quick search-replace guide on how to fixcontract.rs
:In production (non-test) code:
<S: Storage, A: Api, Q: Querier>
=> ``&mut Extern<S, A, Q>
=>DepsMut
&Extern<S, A, Q>
=>Deps
&mut deps.storage
=>deps.storage
where passing intostate.rs
helpers&deps.storage
=>deps.storage
where passing intostate.rs
helpers
On the top, remove
use cosmwasm_std::{Api, Extern, Querier, Storage}
. Adduse cosmwasm_std::{Deps, DepsMut}
.In test code only:
&mut deps,
=>deps.as_mut(),
&deps,
=>deps.as_ref(),
You may have to add
use cosmwasm_std::{Storage}
if the compile complains about the traitIf you use cosmwasm-storage, in
state.rs
:<S: Storage>
=> ``<S: ReadonlyStorage>
=> ``<S,
=><
&mut S
=>&mut dyn Storage
&S
=>&dyn Storage
-
If you have any references to
ReadonlyStorage
left after the above, please replace them withStorage
-
Update CosmWasm dependencies in Cargo.toml (skip the ones you don't use):
[dependencies] cosmwasm-std = "0.11.0" cosmwasm-storage = "0.11.0" # ... [dev-dependencies] cosmwasm-schema = "0.11.0" cosmwasm-vm = "0.11.0" # ...
-
Contracts now support any custom error type
E: ToString + From<StdError>
. Previously this has beenStdError
, which you can still use. However, you can now create a much more structured error experience for your unit tests that handels exactly the error cases of your contract. In order to get a convenient implementation forToString
andFrom<StdError>
, we use the crate thiserror, which needs to be added to the contracts dependencies inCargo.toml
. To create the custom error, create an error modulesrc/errors.rs
as follows:use cosmwasm_std::{CanonicalAddr, StdError}; use thiserror::Error; // thiserror implements Display and ToString if you // set the `#[error("…")]` attribute for all cases #[derive(Error, Debug)] pub enum MyCustomError { #[error("{0}")] // let thiserror implement From<StdError> for you Std(#[from] StdError), // this is whatever we want #[error("Permission denied: the sender is not the current owner")] NotCurrentOwner { expected: CanonicalAddr, actual: CanonicalAddr, }, #[error("Messages empty. Must reflect at least one message")] MessagesEmpty, }
Then add
mod errors;
tosrc/lib.rs
anduse crate::errors::MyCustomError;
tosrc/contract.rs
. Now adapt the return types as follows:fn init
:Result<InitResponse, MyCustomError>
,fn migrate
(if you have it):Result<MigrateResponse, MyCustomError>
,fn handle
:Result<HandleResponse, MyCustomError>
,fn query
:Result<Binary, MyCustomError>
.
If one of your funtions does not use the custom error, you can continue to use
StdError
as before. I.e. you can havehandle
returningResult<HandleResponse, MyCustomError>
andquery
returningStdResult<Binary>
.You can have a top-hevel
init
/migrate
/handle
/query
that returns a custom error but some of its implementations only return errors from the standard library (StdResult<HandleResponse>
aka.Result<HandleResponse, StdError>
). Then useOk(std_result?)
to convert between the result types. E.g.pub fn handle<S: Storage, A: Api, Q: Querier>( deps: &mut Extern<S, A, Q>, env: Env, msg: HandleMsg, ) -> Result<HandleResponse, StakingError> { match msg { // conversion to Result<HandleResponse, StakingError> HandleMsg::Bond {} => Ok(bond(deps, env)?), // this already returns Result<HandleResponse, StakingError> HandleMsg::_BondAllTokens {} => _bond_all_tokens(deps, env), } }
or
pub fn init<S: Storage, A: Api, Q: Querier>( deps: &mut Extern<S, A, Q>, env: Env, msg: InitMsg, ) -> Result<InitResponse, HackError> { // … let mut ctx = Context::new(); ctx.add_attribute("Let the", "hacking begin"); Ok(ctx.try_into()?) }
Once you got familiar with the concept, you can create different error types for each of the contract's functions.
You can also try a different error library than thiserror. The staking development contract shows how this would look like using snafu.
-
Change order of arguments such that
storage
is always first followed by namespace inBucket::new
,Bucket::multilevel
,ReadonlyBucket::new
,ReadonlyBucket::multilevel
,PrefixedStorage::new
,PrefixedStorage::multilevel
,ReadonlyPrefixedStorage::new
,ReadonlyPrefixedStorage::multilevel
,bucket
,bucket_read
,prefixed
andprefixed_read
.// before let mut bucket = bucket::<_, Data>(b"data", &mut store); // after let mut bucket = bucket::<_, Data>(&mut store, b"data");
-
Rename
InitResponse::log
,MigrateResponse::log
andHandleResponse::log
toInitResponse::attributes
,MigrateResponse::attributes
andHandleResponse::attributes
. Replace calls tolog
withattr
:// before Ok(HandleResponse { log: vec![log("action", "change_owner"), log("owner", owner)], ..HandleResponse::default() }) // after Ok(HandleResponse { attributes: vec![attr("action", "change_owner"), attr("owner", owner)], ..HandleResponse::default() })
-
Rename
Context::add_log
toContext::add_attribute
:// before let mut ctx = Context::new(); ctx.add_log("action", "release"); ctx.add_log("destination", &to_addr); // after let mut ctx = Context::new(); ctx.add_attribute("action", "release"); ctx.add_attribute("destination", &to_addr);
-
Add result type to
Bucket::update
andSingleton::update
:// before bucket.update(b"maria", |mayd: Option<Data>| { let mut d = mayd.ok_or(StdError::not_found("Data"))?; old_age = d.age; d.age += 1; Ok(d) }) // after bucket.update(b"maria", |mayd: Option<Data>| -> StdResult<_> { let mut d = mayd.ok_or(StdError::not_found("Data"))?; old_age = d.age; d.age += 1; Ok(d) })
-
Remove all
canonical_length
arguments from mock APIs in tests:// before let mut deps = mock_dependencies(20, &[]); let mut deps = mock_dependencies(20, &coins(123456, "gold")); let deps = mock_dependencies_with_balances(20, &[(&rich_addr, &rich_balance)]); let api = MockApi::new(20); // after let mut deps = mock_dependencies(&[]); let mut deps = mock_dependencies(&coins(123456, "gold")); let deps = mock_dependencies_with_balances(&[(&rich_addr, &rich_balance)]); let api = MockApi::default();
-
Add
MessageInfo
as separate arg afterEnv
forinit
,handle
,migrate
. AddEnv
arg toquery
. Useinfo.sender
instead ofenv.message.sender
andinfo.sent_funds
rather thanenv.message.sent_funds
. Just changing the function signatures of the 3-4 export functions should be enough, then the compiler will warn you anywhere you useenv.message
// before pub fn init<S: Storage, A: Api, Q: Querier>( deps: &mut Extern<S, A, Q>, env: Env, msg: InitMsg, ) { deps.storage.set( CONFIG_KEY, &to_vec(&State { verifier: deps.api.canonical_address(&msg.verifier)?, beneficiary: deps.api.canonical_address(&msg.beneficiary)?, funder: deps.api.canonical_address(&env.message.sender)?, })?, ); } // after pub fn init<S: Storage, A: Api, Q: Querier>( deps: &mut Extern<S, A, Q>, _env: Env, info: MessageInfo, msg: InitMsg, ) { deps.storage.set( CONFIG_KEY, &to_vec(&State { verifier: deps.api.canonical_address(&msg.verifier)?, beneficiary: deps.api.canonical_address(&msg.beneficiary)?, funder: deps.api.canonical_address(&info.sender)?, })?, ); }
-
Test code now has
mock_info
which takes the same argsmock_env
used to. You can just passmock_env()
directly into the function calls unless you need to change height/time. -
One more object to pass in for both unit and integration tests. To do this quickly, I just highlight all copies of
env
and replace them withinfo
(using Ctrl+D in VSCode or Alt+J in IntelliJ). Then I select alldeps, info
sections and replace that withdeps, mock_env(), info
. This fixes up allinit
andhandle
calls, then just add an extramock_env()
to the query calls.// before: unit test let env = mock_env(creator.as_str(), &[]); let res = init(&mut deps, env, msg).unwrap(); let query_response = query(&deps, QueryMsg::Verifier {}).unwrap(); // after: unit test let info = mock_info(creator.as_str(), &[]); let res = init(&mut deps, mock_env(), info, msg).unwrap(); let query_response = query(&deps, mock_env(), QueryMsg::Verifier {}).unwrap(); // before: integration test let env = mock_env("creator", &coins(1000, "earth")); let res: InitResponse = init(&mut deps, env, msg).unwrap(); let query_response = query(&mut deps, QueryMsg::Verifier {}).unwrap(); // after: integration test let info = mock_info("creator", &coins(1000, "earth")); let res: InitResponse = init(&mut deps, mock_env(), info, msg).unwrap(); let query_response = query(&mut deps, mock_env(), QueryMsg::Verifier {}).unwrap();
-
Update CosmWasm dependencies in Cargo.toml (skip the ones you don't use):
[dependencies] cosmwasm-std = "0.10.0" cosmwasm-storage = "0.10.0" # ... [dev-dependencies] cosmwasm-schema = "0.10.0" cosmwasm-vm = "0.10.0" # ...
Integration tests:
-
Calls to
Api::human_address
andApi::canonical_address
now return a pair of result and gas information. Change// before verifier: deps.api.canonical_address(&verifier).unwrap(), // after verifier: deps.api.canonical_address(&verifier).0.unwrap(),
The same applies for all calls of
Querier
andStorage
.
All Tests:
All usages of mock_env
will have to remove the first argument (no need of
API).
// before
let env = mock_env(&deps.api, "creator", &coins(1000, "earth"));
// after
let env = mock_env("creator", &coins(1000, "earth"));
Contracts:
- All code that uses
message.sender
orcontract.address
should deal withHumanAddr
notCanonicalAddr
. Many times this means you can remove a conversion step.
-
Update CosmWasm dependencies in Cargo.toml (skip the ones you don't use):
[dependencies] cosmwasm-std = "0.9.0" cosmwasm-storage = "0.9.0" # ... [dev-dependencies] cosmwasm-schema = "0.9.0" cosmwasm-vm = "0.9.0" # ...
lib.rs
:
-
The C export boilerplate can now be reduced to the following code (see e.g. in hackatom/src/lib.rs):
mod contract; // contains init, handle, query // maybe additional modules here #[cfg(target_arch = "wasm32")] cosmwasm_std::create_entry_points!(contract);
Contract code and uni tests:
cosmwasm_storage::get_with_prefix
,cosmwasm_storage::set_with_prefix
,cosmwasm_storage::RepLog::commit
,cosmwasm_std::ReadonlyStorage::get
,cosmwasm_std::ReadonlyStorage::range
,cosmwasm_std::Storage::set
andcosmwasm_std::Storage::remove
now return the value directly that was wrapped in a result before.- Error creator functions are now in type itself, e.g.
StdError::invalid_base64
instead ofinvalid_base64
. The free functions are deprecated and will be removed before 1.0. - Remove
InitResponse.data
ininit
. Before 0.9 this was not stored to chain but ignored. - Use
cosmwasm_storage::transactional
instead of the removedcosmwasm_storage::transactional_deps
. - Replace
cosmwasm_std::Never
withcosmwasm_std::Empty
.
Integration tests:
- Replace
cosmwasm_vm::ReadonlyStorage
withcosmwasm_vm::Storage
, which now contains all backend storage methods. - Storage getters (and iterators) now return a result of
(Option<Vec<u8>>, u64)
, where the first component is the element and the second one is the gas cost. Thus in a few places.0
must be added to access the element.
Cargo.toml
dependencies:
- Update to
schemars = "0.7"
- Replace
cosmwasm = "0.7"
withcosmwasm-std = "0.8"
- Replace
cosmwasm-vm = "0.7"
withcosmwasm-vm = "0.8"
- Replace
cw-storage = "0.2"
withcosmwasm-storage = "0.8"
- Remove explicit
snafu
dependency.cosmwasm_std
still uses it internally but doesn't expose snafu specifics anymore. See more details on errors below.
(Note: until release of 0.8
, you need to use git references for all
cosmwasm_*
packages)
Cargo.toml
features:
- Replace
"cosmwasm/backtraces"
with"cosmwasm-std/backtraces"
Imports:
- Replace all
use cosmwasm::X::Y
withuse cosmwasm_std::Y
, except for mock - Replace all
use cosmwasm::mock::Y
withuse cosmwasm_std::testing::Y
. This should only be used in test code. - Replace
cw_storage:X
withcosmwasm_storage::X
- Replace
cosmwasm_std::Response
withcosmwasm_std::HandleResponse
andcosmwasm_std::InitResponse
(different type for each call)
src/lib.rs
:
This has been re-written, but is generic boilerplate and should be (almost) the same in all contracts:
- copy the new version from
contracts/queue
- Add
pub mod XYZ
directives for any modules you use besidescontract
Contract Code:
-
Add query to extern:
- Before:
my_func<S: Storage, A: Api>(deps: &Extern<S, A>, ...
- After:
my_func<S: Storage, A: Api, Q: Querier>(deps: &Extern<S, A, Q>, ...
- Remember to add
use cosmwasm_std::Querier;
- Before:
-
query
now returnsStdResult<Binary>
instead ofResult<Vec<u8>>
- You can also replace
to_vec(...)
withto_binary(...)
- You can also replace
-
No
.context(...)
is required afterfrom_slice
andto_vec
, they return propercosmwasm_std::Error
variants on errors. -
env.message.signer
becomesenv.message.sender
. -
If you used
env.contract.balance
, you must now use the querier. The following code block should work:// before (in env) let foo = env.contract.balance; // after (query my balance) let contract_addr = deps.api.human_address(&env.contract.address)?; let balance = deps.querier.query_all_balances(&contract_addr)?; let foo = balance.amount;
-
Update the
CosmosMsg
enums used:CosmosMsg::Send{}
=>CosmosMsg::Bank(BankMsg::Send{})
CosmosMsg::Opaque{ data }
=>CosmosMsg::Native{ msg }
CosmosMsg::Contract
=>CosmosMsg::Wasm(WasmMsg::Execute{})
-
Complete overhaul of
cosmwasm::Error
intocosmwasm_std::StdError
:- Auto generated snafu error constructor structs like
NotFound
/ParseErr
/… have been privatized in favour of error generation helpers likenot_found
/parse_err
/… - All error generator functions now return errors instead of results, such
that e.g.
return unauthorized();
becomesreturn Err(unauthorized());
- Error cases don't contain
source
fields anymore. Instead source errors are converted to standard types likeString
. For this reason, bothsnafu::ResultExt
andsnafu::OptionExt
cannot be used anymore. An error wrapper now looks like.map_err(invalid_base64)
and anOption::None
to error mapping looks like.ok_or_else(|| not_found("State"))
. - Backtraces became optional. Use
RUST_BACKTRACE=1
to enable them for unit tests. Utf8Err
/Utf8StringErr
merged intoStdError::InvalidUtf8
Base64Err
renamed intoStdError::InvalidBase64
ContractErr
/DynContractErr
merged intoStdError::GenericErr
, thus bothcontract_err
anddyn_contract_err
must be replaced withgeneric_err
.- The unused
ValidationErr
was removed
- Auto generated snafu error constructor structs like
At this point cargo wasm
should pass.
Both:
-
Update all imports from
cosmwasm::mock::*
tocosmwasm_std::testing::*
-
Use
from_binary
notfrom_slice
on all query responses (update imports)from_slice(res.as_slice())
->from_binary(&res)
-
Replace
coin("123", "FOO")
withcoins(123, "FOO")
. We renamed it to coins to be more explicit that it returnsVec<Coin>
, and now accept au128
as the first argument for better type-safety.coin
is now an alias toCoin::new
and returns oneCoin
. -
Remove the 4th argument (contract balance) from all calls to
mock_env
, this is no longer stored in the environment. -
dependencies
was renamed tomock_dependencies
.mock_dependencies
andmock_instance
take a 2nd argument to set the contract balance (visible for the querier). If you need to set more balances, usemock_XX_with_balances
. The follow code block explains:// before: balance as last arg in mock_env let mut deps = dependencies(20); let env = mock_env(&deps.api, "creator", &coins(15, "earth"), &coins(1015, "earth")); // after: balance as last arg in mock_dependencies let mut deps = mock_dependencies(20, &coins(1015, "earth")); let env = mock_env(&deps.api, "creator", &coins(15, "earth"));
Unit Tests:
- Replace
dependencies
withmock_dependencies
Integration Tests:
- We no longer check errors as strings but have rich types:
- Before:
match err { ContractResult::Err(msg) => assert_eq!(msg, "Unauthorized"), ... }
- After:
match err { Err(StdError::Unauthorized{ .. }) => {}, ... }
- Before:
- Remove all imports / use of
ContractResult
- You must specify
CosmosMsg::Native
type when callingcosmwasm_vm::testing::{handle, init}
. You will want touse cosmwasm_std::{HandleResult, InitResult}
oruse cosmwasm_std::{HandleResponse, InitResponse}
. If you don't use custom native types, simply update calls as follows:let res = init(...)
=>let res: InitResult = init(...)
let res = init(...).unwrap()
=>let res: InitResponse = init(...).unwrap()
let res = handle(...)
=>let res: HandleResult = handle(...)
let res = handle(...).unwrap()
=>let res: HandleResponse = handle(...).unwrap()
All helper functions have been moved into a new cosmwasm-schema
package.
- Add
cosmwasm-schema = "0.8"
to[dev-dependencies]
inCargo.toml
- Remove
serde_json
[dev-dependency]
if there, as cosmwasm-schema will handle JSON output internally. - Update
examples/schema.rs
to look more like queue, but replacing all the imports and type names with those you currently have. - Regenerate schemas with
cargo schema
After so many changes, remember to let the linters do their jobs.
cargo fmt
cargo clippy