Migrating contracts
This chapter explains what is needed to upgrade smart contracts when migrating over major releases of CosmWasm.
You can also view the complete CHANGELOG to understand the differences.
2.x.y -> 3.0.x
-
Update
cosmwasm-*dependencies in Cargo.toml (skip the ones you don't use):[dependencies]
cosmwasm-std = "3.0.2"
cosmwasm-schema = "3.0.2"
# ...
[dev-dependencies]
cosmwasm-vm = "3.0.2"
# ... -
If you were using
cosmwasm-std'sabortfeature, you can remove it, as it is the default now. Thecraneliftfeature also needs to be removed. It didn't do anything anymore since 2.2.0, and now it is removed completely. -
If you are using
cosmwasm-stdwithdefault-features = falsein a contract (not a library), you need to enable theexportsfeature now. -
If you were still using the deprecated functions
from_slice,from_binary,to_vecorto_binary, you now need to replace them with the new equivalents:-cosmwasm_std::from_slice(&data)
+cosmwasm_std::from_json(&data)
-cosmwasm_std::from_binary(&data)
+cosmwasm_std::from_json(&data)
-cosmwasm_std::to_vec(&data)
+cosmwasm_std::to_json_vec(&data)
-cosmwasm_std::to_binary(&data)
+cosmwasm_std::to_json_binary(&data) -
TransactionInfois nownon_exhaustive, so if you were destructuring it before, you will need to access the fields individually instead. Similarly,GovMsgis alsonon_exhaustivenow, so if you were matching on it, you will need to use a wildcard pattern or access the fields individually. -
Replace all calls of
{Uint256, Uint512, Int256, Int512}::newwith{Uint256, Uint512, Int256, Int512}::from_be_bytes. Alternatively, you can pass au128/i128tonew. -
Replace calls to the deprecated
{Decimal, Decimal256, SignedDecimal, SignedDecimal256}::rawwith normal constructors:-Decimal::raw(value)
+Decimal::new(Uint128::new(value))
-Decimal256::raw(value)
+Decimal256::new(Uint256::new(value))
-SignedDecimal::raw(value)
+SignedDecimal::new(Int128::new(value))
-SignedDecimal256::raw(value)
+SignedDecimal256::new(Int256::new(value)) -
Replace all uses of
Uint256::from_u128andInt256::from_i128:-Uint256::from_u128(value)
+Uint256::new(value)
-Int256::from_i128(value)
+Int256::new(value) -
If you were using
cosmwasm_std::MemoryStorage, you need to replace it withcosmwasm_std::testing::MockStorage. -
The previously deprecated
mock_infohas been removed. Usemessage_infoinstead:-let info = mock_info("creator", &coins(1000, "earth"));
+let info = message_info(&mock_api.addr_make("creator), &coins(1000, "earth")); -
Coin::amounthas been changed toUint256instead ofUint128, so you probably have to change a lot of the math you do with it. For example:-let amount = coin.amount * Uint128::new(2);
+let amount = coin.amount * Uint256::new(2); -
BankQuery::AllBalancesandIbcQuery::ListChannelshave been removed without replacement. If you were using them, you will need to redesign your protocol to not rely on them. If you were usingBankQuery::AllBalances, you can useBankQuery::Balanceinstead. If you were usingIbcQuery::ListChannels, you can save the channel information for each channel yourself in contract storage. -
ExternalApi,ExternalQuerierandExternalStorageexports have been removed, as they are not intended to be used outside cosmwasm-std. -
We did some big changes to
StdError. Most of the constructors have been removed. Instead, you can just convert any type implementing theErrortrait intoStdErrorusing the?operator. Optionally, you can add an error kind using the new.kind(...)method.-Err(StdError::generic_err("Invalid input"))
+Err(StdError::msg("Invalid input"))
-let hrp = Hrp::parse(self.prefix).map_err(|e| StdError::generic_err(e.to_string()))?;
+let hrp = Hrp::parse(self.prefix)?; -
If you were using something like anyhow, you probably want to replace
anyhow::Resultwithcosmwasm_std::StdResult, since the newStdErrordoes not implement theErrortrait anymore, so you cannot convert it intoanyhow::Erroranymore.-) -> anyhow::Result<AppResponse> {
+) -> StdResult<AppResponse> {
1.5.x -> 2.0.x
-
Update
cosmwasm-*dependencies in Cargo.toml (skip the ones you don't use):[dependencies]
cosmwasm-std = "2.0.0"
# ...
[dev-dependencies]
cosmwasm-schema = "2.0.0"
cosmwasm-vm = "2.0.0"
# ...If you were using cosmwasm-std's
ibc3feature, you can remove it, as it is the default now. Depending on your usage, you might have to enable thestargatefeature instead, since it was previously implied byibc3.Also remove any uses of the
backtracesfeature. You can use aRUST_BACKTRACE=1environment variable for this now.If you were using
cosmwasm-stdwithdefault-features = false, you probably want to enable thestdfeature now, as we might move certain existing functionality to that feature in the future to support no_std environments:-cosmwasm-std = { version = "2.0.0", default-features = false, features = [...] }
+cosmwasm-std = { version = "2.0.0", default-features = false, features = ["std", ...] } -
If you want to use a feature that is only available on CosmWasm 2.0+ chains, use this feature:
-cosmwasm-std = { version = "1.4.0", features = ["stargate"] }
+cosmwasm-std = { version = "1.4.0", features = ["stargate", "cosmwasm_2_0"] }Please note that
cosmwasm_2_0impliescosmwasm_1_4,cosmwasm_1_3and so on, so there is no need to set multiple. -
ContractInfoResponse::newnow takes all fields of the response as parameters:-ContractInfoResponse::new(code_id, creator)
+ContractInfoResponse::new(code_id, creator, admin, pinned, ibc_port)Please note that, in the future, this function signature can change between minor versions.
-
Replace all uses of
SubMsgExecutionResponsewithSubMsgResponse. -
Replace all uses of
PartialEq<&str> for AddrwithPartialEq<Addr> for Addrlike this:-if addr == "admin" {
- // ...
-}
+let admin = deps.api.addr_validate("admin")?;
+if addr == admin {
+ // ...
+}If you really want to compare the string representation (e.g. in tests), you can use
Addr::as_str:-assert_eq!(addr, "admin");
+assert_eq!(addr.as_str(), "admin");But keep in mind that this is case-sensitive (while addresses are not).
-
Replace all uses of
Mul<Decimal> for Uint128andMul<Decimal256> for Uint256withUint{128,256}::mul_floor:-Uint128::new(123456) * Decimal::percent(1);
+Uint128::new(123456).mul_floor(Decimal::percent(1)); -
When calling
Coin::new, you now have to explicitly specify the integer type:-Coin::new(1234, "uatom")
+Coin::new(1234u128, "uatom") -
When creating a
BinaryorSizeinstance from an inner value, you now have to explicitly callnew:-Binary(vec![1u8])
+Binary::new(vec![1u8]) -
When accessing the inner value of a
CanonicalAddrorBinary, useas_sliceinstead:-&canonical_addr.0
+canonical_addr.as_slice() -
If you use any
u128ori128in storage or message types, replace them withUint128andInt128respectively to preserve the current serialization. Failing to do this will result in deserialization errors!#[cw_serde]
struct MyStorage {
- a: u128,
- b: i128,
+ a: Uint128,
+ b: Int128,
}
const map: Map<u128, MyStorage> = Map::new("map");
-const item: Item<u128> = Item::new("item");
+const item: Item<Uint128> = Item::new("item"); -
Replace all uses of
IbcReceiveResponse::set_ackandIbcReceiveResponse::defaultwith calls toIbcReceiveResponse::new:-Ok(IbcReceiveResponse::new().set_ack(b"{}"))
+Ok(IbcReceiveResponse::new(b"{}"))
-Ok(IbcReceiveResponse::default())
+Ok(IbcReceiveResponse::new(b"")) -
Replace all uses of
CosmosMsg::StargatewithCosmosMsg::Any:-CosmosMsg::Stargate { type_url, value }
+CosmosMsg::Any(AnyMsg { type_url, value }) -
Replace all direct construction of
StdErrorwith the use of the corresponding constructor:-StdError::GenericErr { msg }
+StdError::generic_err(msg) -
Replace addresses in unit tests with valid Bech32 addresses. This has to be done for all addresses that are validated or canonicalized during the test or within the contract. The easiest way to do this is by using
MockApi::addr_make. It generates a Bech32 address from any string:-let msg = InstantiateMsg {
- verifier: "verifier".to_string(),
- beneficiary: "beneficiary".to_string(),
-};
+let msg = InstantiateMsg {
+ verifier: deps.api.addr_make("verifier").to_string(),
+ beneficiary: deps.api.addr_make("beneficiary").to_string(),
+}; -
Replace addresses in integration tests using
cosmwasm-vmwith valid Bech32 addresses. This has to be done for all addresses that are validated or canonicalized during the test or within the contract. The easiest way to do this is by usingMockApi::addr_make. It generates a Bech32 address from any string:-let msg = InstantiateMsg {
- verifier: "verifier".to_string(),
- beneficiary: "beneficiary".to_string(),
-};
+let msg = InstantiateMsg {
+ verifier: instance.api().addr_make("verifier").to_string(),
+ beneficiary: instance.api().addr_make("beneficiary").to_string(),
+}; -
The
update_balance,set_denom_metadata,set_withdraw_address,set_withdraw_addressesandclear_withdraw_addressesfunctions were removed from theMockQuerier. Use the newly exposed modules to access them directly:-querier.update_balance("addr", coins(1000, "ATOM"));
+querier.bank.update_balance("addr", coins(1000, "ATOM"));
-querier.set_withdraw_address("delegator", "withdrawer");
+querier.distribution.set_withdraw_address("delegator", "withdrawer");
-querier.update_staking(denom, &[], &[]);
+querier.staking.update(denom, &[], &[]);
-querier.update_ibc(port_id, &[]);
+querier.ibc.update(port_id, &[]); -
If you were using
QueryRequest::Stargate, you might want to enable thecosmwasm_2_0cargo feature and migrate toQueryRequest::Grpcinstead. While the stargate query sometimes returns protobuf-encoded data and sometimes JSON encoded data, depending on the chain, the gRPC query always returns protobuf-encoded data.-deps.querier.query(&QueryRequest::Stargate {
- path: "/service.Path/ServiceMethod".to_string(),
- data: Binary::new(b"DATA"),
-})?;
+deps.querier.query(&QueryRequest::Grpc(GrpcQuery {
+ path: "/service.Path/ServiceMethod".to_string(),
+ data: Binary::new(b"DATA"),
+}))?; -
A new
payloadfield allows you to send arbitrary data from the original contract into thereply. If you constructSubMsgmanually, add thepayloadfield:SubMsg {
id: 12,
+ payload: Binary::default(),
msg: my_bank_send,
gas_limit: Some(12345u64),
reply_on: ReplyOn::Always,
},or with data:
SubMsg {
id: 12,
+ payload: Binary::new(vec![9, 8, 7, 6, 5]),
msg: my_bank_send,
gas_limit: Some(12345u64),
reply_on: ReplyOn::Always,
},If you use a constructor function, you can set the payload as follows:
SubMsg::new(BankMsg::Send {
to_address: payout,
amount: coins(123456u128,"gold")
})
+.with_payload(vec![9, 8, 7, 6, 5])The payload data will then be available in the new field
Reply.payloadin thereplyentry point. This functionality is an optional addition introduced in 2.0. To keep the CosmWasm 1.x behavior, just set payload toBinary::default(). -
In test code, replace calls to
mock_infowithmessage_info. This takes a&Addras the first argument which you get by using ownedAddrin the test bodies.
1.4.x -> 1.5.0
-
Update
cosmwasm-*dependencies in Cargo.toml (skip the ones you don't use):[dependencies]
cosmwasm-std = "1.5.0"
cosmwasm-storage = "1.5.0"
# ...
[dev-dependencies]
cosmwasm-schema = "1.5.0"
cosmwasm-vm = "1.5.0"
# ...
1.3.x -> 1.4.0
-
Update
cosmwasm-*dependencies in Cargo.toml (skip the ones you don't use):[dependencies]
cosmwasm-std = "1.4.0"
cosmwasm-storage = "1.4.0"
# ...
[dev-dependencies]
cosmwasm-schema = "1.4.0"
cosmwasm-vm = "1.4.0"
# ... -
If you want to use a feature that is only available on CosmWasm 1.4+ chains, use this feature:
-cosmwasm-std = { version = "1.4.0", features = ["stargate"] }
+cosmwasm-std = { version = "1.4.0", features = ["stargate", "cosmwasm_1_4"] }Please note that
cosmwasm_1_2impliescosmwasm_1_1, andcosmwasm_1_3impliescosmwasm_1_2, and so on, so there is no need to set multiple.
1.2.x -> 1.3.0
-
Update
cosmwasm-*dependencies in Cargo.toml (skip the ones you don't use):[dependencies]
cosmwasm-std = "1.3.0"
cosmwasm-storage = "1.3.0"
# ...
[dev-dependencies]
cosmwasm-schema = "1.3.0"
cosmwasm-vm = "1.3.0"
# ... -
If you want to use a feature that is only available on CosmWasm 1.3+ chains, use this feature:
-cosmwasm-std = { version = "1.3.0", features = ["stargate"] }
+cosmwasm-std = { version = "1.3.0", features = ["stargate", "cosmwasm_1_3"] }Please note that
cosmwasm_1_2impliescosmwasm_1_1, andcosmwasm_1_3impliescosmwasm_1_2, and so on, so there is no need to set multiple.
1.1.x -> 1.2.0
-
Update
cosmwasm-*dependencies in Cargo.toml (skip the ones you don't use):[dependencies]
cosmwasm-std = "1.2.0"
cosmwasm-storage = "1.2.0"
# ...
[dev-dependencies]
cosmwasm-schema = "1.2.0"
cosmwasm-vm = "1.2.0"
# ... -
If you want to use a feature that is only available on CosmWasm 1.2+ chains, use this feature:
-cosmwasm-std = { version = "1.1.0", features = ["stargate"] }
+cosmwasm-std = { version = "1.1.0", features = ["stargate", "cosmwasm_1_2"] }Please note that
cosmwasm_1_2impliescosmwasm_1_1, so there is no need to set both. -
If you use mixed type multiplication between
Uint{64,128,256}andDecimal{,256}, check outmul_floor/checked_mul_floor/mul_ceil/checked_mul_ceil. Mixed type arithmetic will be removed at some point.let a = Uint128::new(123);
let b = Decimal::percent(150)
-let c = a * b;
+let c = a.mul_floor(b);
1.0.0 -> 1.1.0
-
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 boilerplate 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_schemadependency in itsCargo.tomlfile. Move it fromdev-dependenciesto 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
serdeannotations. Remove all those attributes and replace them with#[cosmwasm_schema::cw_serde].+ use cosmwasm_schema::{cw_serde, QueryResponses};
// ...
- #[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::QueryResponsesfor yourQueryMsgtype 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.rsis 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.
1.0.0-beta -> 1.0.0
-
The minimum Rust supported version is 1.56.1. Verify your Rust version is >= 1.56.1 with:
$ rustc --versionPlease 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_dependenciescalls 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_balanceif 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
ContractResultwithSubMsgResultinReplyhandling:@@ -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
SubMsgExecutionResponsewithSubMsgResponse:// 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,
}),
0.16 -> 1.0.0-beta
-
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
Recordinstead ofPair- use cosmwasm_std::Pair;
+ use cosmwasm_std::Record; -
Replace
cosmwasm_std::create_entry_points!andcosmwasm_std::create_entry_points_with_migration!with#[entry_point]
pub fn ...annotations. See the 0.13 -> 0.14 entry where
#[entry_point]was introduced. -
If your chain provides a custom query, add the custom query type as a generic argument to
cosmwasm_std::Deps,DepsMut,OwnedDepsandQuerierWrapper. Otherwise, it defaults toEmpty, e.g.#[entry_point]
pub fn instantiate(
- deps: DepsMut,
+ deps: DepsMut<CyberQueryWrapper>,
_env: Env,
info: MessageInfo,
msg: InstantiateMsg,
}#[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_querywithQuerierWrapper::querywhich is now fully typed:-let res: CyberlinksAmountResponse = self.querier.custom_query(&request.into())?;
+let res: CyberlinksAmountResponse = self.querier.query(&request.into())?;See https://github.com/cybercongress/cw-cyber/pull/2 for a complete example.
Integration tests
-
Add new
transactionfield toEnvwhen creating a custom mock env: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};
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.
0.15 -> 0.16
-
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
attrfunction now accepts arguments that implementInto<String>rather thanToString. This means that "string" types like&strare still accepted, but others (like numbers or booleans) have to be explicitly converted to strings; you can use theto_stringmethod (from thestd::string::ToStringtrait) for that.let steal_funds = true;
- attr("steal_funds", steal_funds),
+ attr("steal_funds", steal_funds.to_string()),It also means that
&&stris no longer accepted. -
The
iteratorfeature incosmwasm-std,cosmwasm-vmandcosmwasm-storageis 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, for example:- cosmwasm-std = { version = "0.15.0" }
+ cosmwasm-std = { version = "0.16.0", default-features = false } -
The
Event::attrsetter 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"); -
Responsecan no longer be built using a struct literal. Please useResponse::newas 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
IbcReceiveResponseandIbcBasicResponsefollows the same principles now asResponseabove.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
Msgtype 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
}
0.14 -> 0.15
-
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
messagesandsubmessageson theResponseobject. The new format usesmessages: Vec<SubMsg<T>>, so copysubmessagescontent, 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
sendfield tofundswhenever constructing aWasmMsg::ExecuteorWasmMsg::Instantiatevalue.let exec = WasmMsg::Execute {
contract_addr: coin.address.into(),
msg: to_binary(&msg)?,
- send: vec![],
+ funds: vec![],
}; -
Uint128field 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
Responseusing struct literal syntax, add theeventsfield.Ok(Response {
messages: vec![],
attributes,
+ events: vec![],
data: None,
}) -
For IBC-enabled contracts only: You need to adapt to the new
IbcAcknowledgementWithPacketstructure and use the embeddeddatafield:// 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 and for 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,
};
0.13 -> 0.14
-
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
schemarsdependencies 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
initentry point toinstantiate. Also, renameInitMsgtoInstantiateMsg. -
Rename the
handleentry point toexecute. Also, renameHandleMsgtoExecuteMsg. -
Rename
InitResponse,HandleResponseandMigrateResponsetoResponse. The old names are still supported (with a deprecation warning), and will be removed in the next version. Also, you'll need to add thesubmessagesfield toResponse. -
Remove
from_addressfromBankMsg::Send, which is now automatically filled with the contract address:-ctx.add_message(BankMsg::Send {
- from_address: env.contract.address,
- to_address: to_addr,
- amount: balance,
-});
+ctx.add_message(BankMsg::Send {
+ to_address: to_addr,
+ amount: balance,
+}); -
Use the new entry point system. Remove from
lib.rsthe following:#[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.rsas 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
Responsecontains adatafield, convertingContextintoResponsealways succeeds.-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()?)
-}
+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: MessageInfofield from themigrateentry point:pub fn migrate(
deps: DepsMut,
env: Env,
- _info: MessageInfo,
msg: MigrateMsg,
) -> StdResult<MigrateResponse> {
// ...
}MessageInfo::fundswas always empty since MsgMigrateContract does not have a funds field.MessageInfo::sendershould 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
Responsethat can be used instead of creating aContextthat is later converted to a response:-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())
-}
+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
Pairinstead ofKV- use cosmwasm_std::KV;
+ use cosmwasm_std::Pair; -
If necessary, add a wildcard arm to the
matchof now non-exhaustive message typesBankMsg,BankQuery,WasmMsgandWasmQuery. -
HumanAddrhas been deprecated in favour of simplyString. It never added any significant safety bonus overStringand was just a marker type. The new typeAddrwas created to hold validated addresses. Those can be created viaAddr::unchecked,Api::addr_validate,Api::addr_humanizeand JSON deserialization. In order to maintain type safety, deserialization intoAddrmust only be done from trusted sources like a contract's state or a query response. User inputs must be deserialized intoString.This new
Addrtype makes it easy to use human-readable addresses in state:-
with pre-validated
AddrfromMessageInfo:-pub struct State {
- pub owner: CanonicalAddr,
-}
-
-let state = State {
- owner: deps.api.canonical_address(&info.sender /* of type HumanAddr */)?,
-};
+pub struct State {
+ pub owner: Addr,
+}
+
+let state = State {
+ owner: info.sender.clone() /* of type Addr */,
+}; -
with user input in
msg:-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 */)?,
- })?,
-);
+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
CanonicalAddrremains 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 addresscosmos1pfq05em6sfkls66ut4m2257p7qwlk448h8mysztakes 45 bytes as direct ASCII and 28 bytes when its canonical representation is base64 encoded). For fixed-length database keysCanonicalAddrremains handy though. -
-
Replace
StakingMsg::WithdrawwithDistributionMsg::SetWithdrawAddressandDistributionMsg::WithdrawDelegatorReward.StakingMsg::Withdrawwas a shorthand for the two distribution messages. However, it was unintuitive because it did not set the address for one withdrawal only but for all following withdrawals. Since withdrawals are triggered by different events such as validators changing their commission rate, an address that was set for a one-time withdrawal would be used for future withdrawals not considered by the contract author.If the contract never set a withdrawal address other than the contract itself (
env.contract.address), you can simply replaceStakingMsg::WithdrawwithDistributionMsg::WithdrawDelegatorReward. It is then never changed from the default. Otherwise, you need to carefully track what the current withdrawal address is. A one-time change can be implemented by emitting 3 messages:SetWithdrawAddress { address: recipient }to temporarily change the recipient.WithdrawDelegatorReward { validator }to do a manual withdrawal from the given validator.SetWithdrawAddress { address: env.contract.address.into() }to change it back for all future withdrawals.
-
The block time in
env.block.timeis now aTimestampwhich stores nanosecond precision.env.block.time_nanoswas removed. If you need the components as before, uselet seconds = env.block.time.nanos() / 1_000_000_000;
let nsecs = env.block.time.nanos() % 1_000_000_000;
0.12 -> 0.13
-
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"
# ...
0.11 -> 0.12
-
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/configremove--features backtraces, which is now available in Rust nightly only:[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
ExterntoDeps, and radically simplify theinit/handle/migrate/queryentrypoints. Rather than&mut Extern<S, A, Q>, useDepsMut. And instead of&Extern<S, A, Q>, useDeps. If you ever pass e.g.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.storagewhere passing intostate.rshelpers&deps.storage=>deps.storagewhere passing intostate.rshelpers
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 compiler 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
ReadonlyStorageleft after the above, please replace them withStorage.
0.10 -> 0.11
-
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 handles exactly the error cases of your contract. In order to get a convenient implementation forToStringandFrom<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.rsas 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.rsanduse 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 functions does not use the custom error, you can continue to use
StdErroras before. For example, you can havehandlereturningResult<HandleResponse, MyCustomError>andqueryreturningStdResult<Binary>.You can have a top-level
init/migrate/handle/querythat 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 get 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
storageis always first followed by namespace inBucket::new,Bucket::multilevel,ReadonlyBucket::new,ReadonlyBucket::multilevel,PrefixedStorage::new,PrefixedStorage::multilevel,ReadonlyPrefixedStorage::new,ReadonlyPrefixedStorage::multilevel,bucket,bucket_read,prefixedandprefixed_read.-let mut bucket = bucket::<_, Data>(b"data", &mut store);
+let mut bucket = bucket::<_, Data>(&mut store, b"data"); -
Rename
InitResponse::log,MigrateResponse::logandHandleResponse::logtoInitResponse::attributes,MigrateResponse::attributesandHandleResponse::attributes. Replace calls tologwithattr:-Ok(HandleResponse {
- log: vec![log("action", "change_owner"), log("owner", owner)],
- ..HandleResponse::default()
-})
+Ok(HandleResponse {
+ attributes: vec![attr("action", "change_owner"), attr("owner", owner)],
+ ..HandleResponse::default()
+}) -
Rename
Context::add_logtoContext::add_attribute:-let mut ctx = Context::new();
-ctx.add_log("action", "release");
-ctx.add_log("destination", &to_addr);
+let mut ctx = Context::new();
+ctx.add_attribute("action", "release");
+ctx.add_attribute("destination", &to_addr); -
Add result type to
Bucket::updateandSingleton::update:-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)
-})
+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_lengtharguments from mock APIs in tests:-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);
+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
MessageInfoas separate arg afterEnvforinit,handle,migrate. AddEnvarg toquery. Useinfo.senderinstead ofenv.message.senderandinfo.sent_fundsrather 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-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)?,
- })?,
- );
-}
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_infowhich takes the same argsmock_envused 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
envand replace them withinfo(using Ctrl+D in VSCode or Alt+J in IntelliJ). Then I select alldeps, infosections and replace that withdeps, mock_env(), info. This fixes up allinitandhandlecalls, 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();
0.9 -> 0.10
-
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_addressandApi::canonical_addressnow return a pair of result and gas information. Change-verifier: deps.api.canonical_address(&verifier).unwrap(),
+verifier: deps.api.canonical_address(&verifier).0.unwrap(),The same applies for all calls of
QuerierandStorage.
All Tests:
-
All usages of
mock_envwill have to remove the first argument (no need of API).let env = mock_env(&deps.api, "creator", &coins(1000, "earth"));
let env = mock_env("creator", &coins(1000, "earth"));
Contracts:
- All code that uses
message.senderorcontract.addressshould deal withHumanAddrnotCanonicalAddr. Many times this means you can remove a conversion step.
0.8 -> 0.9
-
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::setandcosmwasm_std::Storage::removenow returns the value directly that was wrapped in a result before.- Error creator functions are now in type itself, e.g.
StdError::invalid_base64instead ofinvalid_base64. The free functions are deprecated and will be removed before 1.0. - Remove
InitResponse.dataininit. Before 0.9 this was not stored to chain but ignored. - Use
cosmwasm_storage::transactionalinstead of the removedcosmwasm_storage::transactional_deps. - Replace
cosmwasm_std::Neverwithcosmwasm_std::Empty.
Integration tests:
- Replace
cosmwasm_vm::ReadonlyStoragewithcosmwasm_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.0must be added to access the element.
0.7.2 -> 0.8
Update wasm code
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
snafudependency.cosmwasm_stdstill 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::Ywithuse cosmwasm_std::Y, except for mock - Replace all
use cosmwasm::mock::Ywithuse cosmwasm_std::testing::Y. This should only be used in test code. - Replace
cw_storage:Xwithcosmwasm_storage::X - Replace
cosmwasm_std::Responsewithcosmwasm_std::HandleResponseandcosmwasm_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 XYZdirectives 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:
-
querynow returnsStdResult<Binary>instead ofResult<Vec<u8>>- You can also replace
to_vec(...)withto_binary(...)
- You can also replace
-
No
.context(...)is required afterfrom_sliceandto_vec, they return propercosmwasm_std::Errorvariants on errors. -
env.message.signerbecomesenv.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
CosmosMsgenums used:CosmosMsg::Send{}=>CosmosMsg::Bank(BankMsg::Send{})CosmosMsg::Opaque{ data }=>CosmosMsg::Native{ msg }CosmosMsg::Contract=>CosmosMsg::Wasm(WasmMsg::Execute{})
-
Complete overhaul of
cosmwasm::Errorintocosmwasm_std::StdError:- Auto generated snafu error constructor structs like
NotFound/ParseErr/… have been privatized in favor 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
sourcefields anymore. Instead, source errors are converted to standard types likeString. For this reason, bothsnafu::ResultExtandsnafu::OptionExtcannot be used anymore. An error wrapper now looks like.map_err(invalid_base64)and anOption::Noneto error mapping looks like.ok_or_else(|| not_found("State")). - Backtraces became optional. Use
RUST_BACKTRACE=1to enable them for unit tests. Utf8Err/Utf8StringErrmerged intoStdError::InvalidUtf8Base64Errrenamed intoStdError::InvalidBase64ContractErr/DynContractErrmerged intoStdError::GenericErr, thus bothcontract_erranddyn_contract_errmust be replaced withgeneric_err.- The unused
ValidationErrwas removed
- Auto generated snafu error constructor structs like
At this point cargo wasm should pass.
Update test code
Both:
-
Update all imports from
cosmwasm::mock::*tocosmwasm_std::testing::* -
Use
from_binarynotfrom_sliceon 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 au128as the first argument for better type-safety.coinis now an alias toCoin::newand returns oneCoin. -
Remove the 4th argument (contract balance) from all calls to
mock_env, this is no longer stored in the environment. -
dependencieswas renamed tomock_dependencies.mock_dependenciesandmock_instancetake a 2nd argument to set the contract balance (visible for the querier). If you need to set more balances, usemock_XX_with_balances. The following 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
dependencieswithmock_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::Nativetype 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()
Update schema code
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.rsto look more like queue, but replacing all the imports and type names with those you currently have. - Regenerate schemas with
cargo schema
Polishing
After many changes, remember to let the linters do their job:
$ cargo fmt
$ cargo clippy