From 1e4d6586fc67647d58a199f2c2123ee201a7d7cb Mon Sep 17 00:00:00 2001 From: Aumetra Weisman Date: Tue, 23 Apr 2024 14:27:04 +0200 Subject: [PATCH 01/12] Add `set_contract_state_version` macro --- packages/derive/src/lib.rs | 93 ++++++++++++++++++++++++++++++++++++-- packages/std/src/lib.rs | 2 +- 2 files changed, 91 insertions(+), 4 deletions(-) diff --git a/packages/derive/src/lib.rs b/packages/derive/src/lib.rs index 6692c15e76..ec3c3aef2e 100644 --- a/packages/derive/src/lib.rs +++ b/packages/derive/src/lib.rs @@ -1,10 +1,10 @@ use proc_macro2::TokenStream; use quote::{format_ident, quote, ToTokens}; use syn::{ - parse::{Parse, ParseStream}, + parse::{Parse, ParseStream, Parser}, parse_quote, punctuated::Punctuated, - Token, + MetaNameValue, Token, }; macro_rules! maybe { @@ -129,12 +129,99 @@ fn entry_point_impl(attr: TokenStream, mut item: TokenStream) -> TokenStream { item } +/// Set the version of the state of your contract. +/// The VM will use this as a hint whether it needs to run the migrate function of your contract or not. +/// +/// ``` +/// # use cosmwasm_std::{ +/// # DepsMut, entry_point, Env, set_contract_state_version, +/// # Response, StdResult, +/// # }; +/// # +/// # type MigrateMsg = (); +/// #[entry_point] +/// #[set_contract_state_version(version = 2)] +/// pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> StdResult { +/// todo!(); +/// } +/// ``` +#[proc_macro_attribute] +pub fn set_contract_state_version( + attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + set_contract_state_version_impl(attr.into(), item.into()).into() +} + +fn set_contract_state_version_impl(attr: TokenStream, item: TokenStream) -> TokenStream { + let name_value = + maybe!(Punctuated::::parse_separated_nonempty.parse2(attr)); + + let mut version = None; + for pair @ MetaNameValue { path, value, .. } in &name_value { + if !path.is_ident("version") { + return syn::Error::new_spanned(pair, "unexpected key-value pair").into_compile_error(); + } + + let syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Int(version_num), + .. + }) = value + else { + return syn::Error::new_spanned(value, "expected number").into_compile_error(); + }; + + version = Some(version_num.base10_digits()); + } + + let Some(version) = version else { + return syn::Error::new_spanned(name_value, "expected \"version\"").into_compile_error(); + }; + + quote! { + #[allow(unused)] + #[doc(hidden)] + #[link_section = "cw_contract_state_version"] + /// This is an internal constant exported as a custom section denoting the contract state version. + /// The format and even the existence of this value is an implementation detail, DO NOT RELY ON THIS! + static __CW_CONTRACT_STATE_VERSION: &str = #version; + + #item + } +} + #[cfg(test)] mod test { use proc_macro2::TokenStream; use quote::quote; - use crate::entry_point_impl; + use crate::{entry_point_impl, set_contract_state_version_impl}; + + #[test] + fn contract_state_expansion() { + let attribute = quote!(version = 5); + let code = quote! { + fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> Response { + // Logic here + } + }; + + let actual = set_contract_state_version_impl(attribute, code); + let expected = quote! { + #[allow(unused)] + #[doc(hidden)] + #[link_section = "cw_contract_state_version"] + /// This is an internal constant exported as a custom section denoting the contract state version. + /// The format and even the existence of this value is an implementation detail, DO NOT RELY ON THIS! + static __CW_CONTRACT_STATE_VERSION: &str = "5"; + + fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> Response { + // Logic here + } + }; + + assert_eq!(actual.to_string(), expected.to_string()); + } #[test] fn default_expansion() { diff --git a/packages/std/src/lib.rs b/packages/std/src/lib.rs index 6860b9bc09..e38a06b405 100644 --- a/packages/std/src/lib.rs +++ b/packages/std/src/lib.rs @@ -122,4 +122,4 @@ pub use cosmwasm_core::{ #[cfg(not(target_arch = "wasm32"))] pub use cosmwasm_core::assert_approx_eq; -pub use cosmwasm_derive::entry_point; +pub use cosmwasm_derive::{entry_point, set_contract_state_version}; From 6f1ee61b6b80bdc579078e9cd37378b132fad771 Mon Sep 17 00:00:00 2001 From: Aumetra Weisman Date: Tue, 23 Apr 2024 14:32:23 +0200 Subject: [PATCH 02/12] Only allow macro to be called on migrate functions --- packages/derive/src/lib.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/derive/src/lib.rs b/packages/derive/src/lib.rs index ec3c3aef2e..42a8253b47 100644 --- a/packages/derive/src/lib.rs +++ b/packages/derive/src/lib.rs @@ -4,7 +4,7 @@ use syn::{ parse::{Parse, ParseStream, Parser}, parse_quote, punctuated::Punctuated, - MetaNameValue, Token, + ItemFn, MetaNameValue, Token, }; macro_rules! maybe { @@ -154,6 +154,15 @@ pub fn set_contract_state_version( } fn set_contract_state_version_impl(attr: TokenStream, item: TokenStream) -> TokenStream { + let function = maybe!(syn::parse2::(item)); + if function.sig.ident != "migrate" { + return syn::Error::new_spanned( + function.sig.ident, + "you only want to add this macro to your migrate function", + ) + .into_compile_error(); + } + let name_value = maybe!(Punctuated::::parse_separated_nonempty.parse2(attr)); @@ -186,7 +195,7 @@ fn set_contract_state_version_impl(attr: TokenStream, item: TokenStream) -> Toke /// The format and even the existence of this value is an implementation detail, DO NOT RELY ON THIS! static __CW_CONTRACT_STATE_VERSION: &str = #version; - #item + #function } } From 6f7ec50be105924463e55f9544cc914d90d83f41 Mon Sep 17 00:00:00 2001 From: Aumetra Weisman Date: Tue, 23 Apr 2024 15:05:49 +0200 Subject: [PATCH 03/12] Integrate the macro logic into the `entry_point` macro --- packages/derive/src/lib.rs | 160 ++++++++++++++++++------------------- packages/std/src/lib.rs | 2 +- 2 files changed, 81 insertions(+), 81 deletions(-) diff --git a/packages/derive/src/lib.rs b/packages/derive/src/lib.rs index 42a8253b47..16ce86aa52 100644 --- a/packages/derive/src/lib.rs +++ b/packages/derive/src/lib.rs @@ -1,10 +1,10 @@ use proc_macro2::TokenStream; use quote::{format_ident, quote, ToTokens}; use syn::{ - parse::{Parse, ParseStream, Parser}, + parse::{Parse, ParseStream}, parse_quote, punctuated::Punctuated, - ItemFn, MetaNameValue, Token, + ItemFn, Token, }; macro_rules! maybe { @@ -93,48 +93,14 @@ impl Parse for Options { /// /// where `InstantiateMsg`, `ExecuteMsg`, and `QueryMsg` are contract defined /// types that implement `DeserializeOwned + JsonSchema`. -#[proc_macro_attribute] -pub fn entry_point( - attr: proc_macro::TokenStream, - item: proc_macro::TokenStream, -) -> proc_macro::TokenStream { - entry_point_impl(attr.into(), item.into()).into() -} - -fn entry_point_impl(attr: TokenStream, mut item: TokenStream) -> TokenStream { - let cloned = item.clone(); - let function: syn::ItemFn = maybe!(syn::parse2(cloned)); - let Options { crate_path } = maybe!(syn::parse2(attr)); - - // The first argument is `deps`, the rest is region pointers - let args = function.sig.inputs.len() - 1; - let fn_name = function.sig.ident; - let wasm_export = format_ident!("__wasm_export_{fn_name}"); - let do_call = format_ident!("do_{fn_name}"); - - let decl_args = (0..args).map(|item| format_ident!("ptr_{item}")); - let call_args = decl_args.clone(); - - let new_code = quote! { - #[cfg(target_arch = "wasm32")] - mod #wasm_export { // new module to avoid conflict of function name - #[no_mangle] - extern "C" fn #fn_name(#( #decl_args : u32 ),*) -> u32 { - #crate_path::#do_call(&super::#fn_name, #( #call_args ),*) - } - } - }; - - item.extend(new_code); - item -} - -/// Set the version of the state of your contract. +/// +/// ## Set the version of the state of your contract +/// /// The VM will use this as a hint whether it needs to run the migrate function of your contract or not. /// /// ``` /// # use cosmwasm_std::{ -/// # DepsMut, entry_point, Env, set_contract_state_version, +/// # DepsMut, entry_point, Env, /// # Response, StdResult, /// # }; /// # @@ -146,56 +112,82 @@ fn entry_point_impl(attr: TokenStream, mut item: TokenStream) -> TokenStream { /// } /// ``` #[proc_macro_attribute] -pub fn set_contract_state_version( +pub fn entry_point( attr: proc_macro::TokenStream, item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { - set_contract_state_version_impl(attr.into(), item.into()).into() + entry_point_impl(attr.into(), item.into()).into() } -fn set_contract_state_version_impl(attr: TokenStream, item: TokenStream) -> TokenStream { - let function = maybe!(syn::parse2::(item)); - if function.sig.ident != "migrate" { - return syn::Error::new_spanned( - function.sig.ident, - "you only want to add this macro to your migrate function", - ) - .into_compile_error(); - } - - let name_value = - maybe!(Punctuated::::parse_separated_nonempty.parse2(attr)); +fn expand_attributes(func: &mut ItemFn) -> syn::Result { + let mut attributes = std::mem::take(&mut func.attrs); + let mut stream = TokenStream::new(); + while let Some(attribute) = attributes.pop() { + if !attribute.path().is_ident("set_contract_state_version") { + func.attrs.push(attribute); + continue; + } - let mut version = None; - for pair @ MetaNameValue { path, value, .. } in &name_value { - if !path.is_ident("version") { - return syn::Error::new_spanned(pair, "unexpected key-value pair").into_compile_error(); + if func.sig.ident != "migrate" { + return Err(syn::Error::new_spanned( + &func.sig.ident, + "you only want to add this macro to your migrate function", + )); } - let syn::Expr::Lit(syn::ExprLit { - lit: syn::Lit::Int(version_num), - .. - }) = value - else { - return syn::Error::new_spanned(value, "expected number").into_compile_error(); - }; + attribute.parse_nested_meta(|meta| { + if !meta.path.is_ident("version") { + return Err(meta.error("unexpected attribute")); + } - version = Some(version_num.base10_digits()); + let version: syn::LitInt = meta.value()?.parse()?; + let version = version.base10_digits(); + + stream = quote! { + #stream + + #[allow(unused)] + #[doc(hidden)] + #[link_section = "cw_contract_state_version"] + /// This is an internal constant exported as a custom section denoting the contract state version. + /// The format and even the existence of this value is an implementation detail, DO NOT RELY ON THIS! + static __CW_CONTRACT_STATE_VERSION: &str = #version; + }; + + Ok(()) + })?; } - let Some(version) = version else { - return syn::Error::new_spanned(name_value, "expected \"version\"").into_compile_error(); - }; + Ok(stream) +} + +fn entry_point_impl(attr: TokenStream, item: TokenStream) -> TokenStream { + let mut function: syn::ItemFn = maybe!(syn::parse2(item)); + let Options { crate_path } = maybe!(syn::parse2(attr)); + + let attribute_code = maybe!(expand_attributes(&mut function)); + + // The first argument is `deps`, the rest is region pointers + let args = function.sig.inputs.len().saturating_sub(1); + let fn_name = &function.sig.ident; + let wasm_export = format_ident!("__wasm_export_{fn_name}"); + let do_call = format_ident!("do_{fn_name}"); + + let decl_args = (0..args).map(|item| format_ident!("ptr_{item}")); + let call_args = decl_args.clone(); quote! { - #[allow(unused)] - #[doc(hidden)] - #[link_section = "cw_contract_state_version"] - /// This is an internal constant exported as a custom section denoting the contract state version. - /// The format and even the existence of this value is an implementation detail, DO NOT RELY ON THIS! - static __CW_CONTRACT_STATE_VERSION: &str = #version; + #attribute_code #function + + #[cfg(target_arch = "wasm32")] + mod #wasm_export { // new module to avoid conflict of function name + #[no_mangle] + extern "C" fn #fn_name(#( #decl_args : u32 ),*) -> u32 { + #crate_path::#do_call(&super::#fn_name, #( #call_args ),*) + } + } } } @@ -204,29 +196,37 @@ mod test { use proc_macro2::TokenStream; use quote::quote; - use crate::{entry_point_impl, set_contract_state_version_impl}; + use crate::entry_point_impl; #[test] - fn contract_state_expansion() { - let attribute = quote!(version = 5); + fn contract_state_version_expansion() { let code = quote! { + #[set_contract_state_version(version = 2)] fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> Response { // Logic here } }; - let actual = set_contract_state_version_impl(attribute, code); + let actual = entry_point_impl(TokenStream::new(), code); let expected = quote! { #[allow(unused)] #[doc(hidden)] #[link_section = "cw_contract_state_version"] /// This is an internal constant exported as a custom section denoting the contract state version. /// The format and even the existence of this value is an implementation detail, DO NOT RELY ON THIS! - static __CW_CONTRACT_STATE_VERSION: &str = "5"; + static __CW_CONTRACT_STATE_VERSION: &str = "2"; fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> Response { // Logic here } + + #[cfg(target_arch = "wasm32")] + mod __wasm_export_migrate { + #[no_mangle] + extern "C" fn migrate(ptr_0: u32, ptr_1: u32) -> u32 { + ::cosmwasm_std::do_migrate(&super::migrate, ptr_0, ptr_1) + } + } }; assert_eq!(actual.to_string(), expected.to_string()); diff --git a/packages/std/src/lib.rs b/packages/std/src/lib.rs index e38a06b405..6860b9bc09 100644 --- a/packages/std/src/lib.rs +++ b/packages/std/src/lib.rs @@ -122,4 +122,4 @@ pub use cosmwasm_core::{ #[cfg(not(target_arch = "wasm32"))] pub use cosmwasm_core::assert_approx_eq; -pub use cosmwasm_derive::{entry_point, set_contract_state_version}; +pub use cosmwasm_derive::entry_point; From 9ea997f3dff4309a167946fb3e29b750397d348f Mon Sep 17 00:00:00 2001 From: Aumetra Weisman Date: Wed, 24 Apr 2024 15:04:13 +0200 Subject: [PATCH 04/12] Iterate in the correct order, add error to attribute --- packages/derive/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/derive/src/lib.rs b/packages/derive/src/lib.rs index 16ce86aa52..7c0c831383 100644 --- a/packages/derive/src/lib.rs +++ b/packages/derive/src/lib.rs @@ -120,9 +120,9 @@ pub fn entry_point( } fn expand_attributes(func: &mut ItemFn) -> syn::Result { - let mut attributes = std::mem::take(&mut func.attrs); + let attributes = std::mem::take(&mut func.attrs); let mut stream = TokenStream::new(); - while let Some(attribute) = attributes.pop() { + for attribute in attributes { if !attribute.path().is_ident("set_contract_state_version") { func.attrs.push(attribute); continue; @@ -130,7 +130,7 @@ fn expand_attributes(func: &mut ItemFn) -> syn::Result { if func.sig.ident != "migrate" { return Err(syn::Error::new_spanned( - &func.sig.ident, + &attribute, "you only want to add this macro to your migrate function", )); } From 41b657b661bc07ddc957b3fff93184cd8b2ed212 Mon Sep 17 00:00:00 2001 From: Aumetra Weisman Date: Wed, 24 Apr 2024 15:52:05 +0200 Subject: [PATCH 05/12] Remove version key-value requirement --- packages/derive/src/lib.rs | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/packages/derive/src/lib.rs b/packages/derive/src/lib.rs index 7c0c831383..53e9520762 100644 --- a/packages/derive/src/lib.rs +++ b/packages/derive/src/lib.rs @@ -106,7 +106,7 @@ impl Parse for Options { /// # /// # type MigrateMsg = (); /// #[entry_point] -/// #[set_contract_state_version(version = 2)] +/// #[set_contract_state_version(2)] /// pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> StdResult { /// todo!(); /// } @@ -135,27 +135,19 @@ fn expand_attributes(func: &mut ItemFn) -> syn::Result { )); } - attribute.parse_nested_meta(|meta| { - if !meta.path.is_ident("version") { - return Err(meta.error("unexpected attribute")); - } - - let version: syn::LitInt = meta.value()?.parse()?; - let version = version.base10_digits(); - - stream = quote! { - #stream + let version: syn::LitInt = attribute.parse_args()?; + let version = version.base10_digits(); - #[allow(unused)] - #[doc(hidden)] - #[link_section = "cw_contract_state_version"] - /// This is an internal constant exported as a custom section denoting the contract state version. - /// The format and even the existence of this value is an implementation detail, DO NOT RELY ON THIS! - static __CW_CONTRACT_STATE_VERSION: &str = #version; - }; + stream = quote! { + #stream - Ok(()) - })?; + #[allow(unused)] + #[doc(hidden)] + #[link_section = "cw_contract_state_version"] + /// This is an internal constant exported as a custom section denoting the contract state version. + /// The format and even the existence of this value is an implementation detail, DO NOT RELY ON THIS! + static __CW_CONTRACT_STATE_VERSION: &str = #version; + }; } Ok(stream) @@ -201,7 +193,7 @@ mod test { #[test] fn contract_state_version_expansion() { let code = quote! { - #[set_contract_state_version(version = 2)] + #[set_contract_state_version(2)] fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> Response { // Logic here } From d4d110874da6f0e8d17e410d07a71132e48fb70d Mon Sep 17 00:00:00 2001 From: Aumetra Weisman Date: Wed, 24 Apr 2024 16:27:04 +0200 Subject: [PATCH 06/12] Rename to `set_state_version` --- packages/derive/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/derive/src/lib.rs b/packages/derive/src/lib.rs index 53e9520762..9b28a32d27 100644 --- a/packages/derive/src/lib.rs +++ b/packages/derive/src/lib.rs @@ -106,7 +106,7 @@ impl Parse for Options { /// # /// # type MigrateMsg = (); /// #[entry_point] -/// #[set_contract_state_version(2)] +/// #[set_state_version(2)] /// pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> StdResult { /// todo!(); /// } @@ -123,7 +123,7 @@ fn expand_attributes(func: &mut ItemFn) -> syn::Result { let attributes = std::mem::take(&mut func.attrs); let mut stream = TokenStream::new(); for attribute in attributes { - if !attribute.path().is_ident("set_contract_state_version") { + if !attribute.path().is_ident("set_state_version") { func.attrs.push(attribute); continue; } @@ -193,7 +193,7 @@ mod test { #[test] fn contract_state_version_expansion() { let code = quote! { - #[set_contract_state_version(2)] + #[set_state_version(2)] fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> Response { // Logic here } From 41632e0745100ee9e39f2fd317d55e7b97e924c6 Mon Sep 17 00:00:00 2001 From: Aumetra Weisman Date: Wed, 24 Apr 2024 16:31:49 +0200 Subject: [PATCH 07/12] Update packages/derive/src/lib.rs Co-authored-by: Simon Warta <2603011+webmaster128@users.noreply.github.com> --- packages/derive/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/derive/src/lib.rs b/packages/derive/src/lib.rs index 9b28a32d27..48f2ceb2f9 100644 --- a/packages/derive/src/lib.rs +++ b/packages/derive/src/lib.rs @@ -143,10 +143,10 @@ fn expand_attributes(func: &mut ItemFn) -> syn::Result { #[allow(unused)] #[doc(hidden)] - #[link_section = "cw_contract_state_version"] + #[link_section = "cw_state_version"] /// This is an internal constant exported as a custom section denoting the contract state version. /// The format and even the existence of this value is an implementation detail, DO NOT RELY ON THIS! - static __CW_CONTRACT_STATE_VERSION: &str = #version; + static __CW_STATE_VERSION: &str = #version; }; } @@ -203,10 +203,10 @@ mod test { let expected = quote! { #[allow(unused)] #[doc(hidden)] - #[link_section = "cw_contract_state_version"] + #[link_section = "cw_state_version"] /// This is an internal constant exported as a custom section denoting the contract state version. /// The format and even the existence of this value is an implementation detail, DO NOT RELY ON THIS! - static __CW_CONTRACT_STATE_VERSION: &str = "2"; + static __CW_STATE_VERSION: &str = "2"; fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> Response { // Logic here From 18ef64a22b48a2b3c4906edd027ddc5cf2edd2db Mon Sep 17 00:00:00 2001 From: Aumetra Weisman Date: Thu, 25 Apr 2024 10:37:57 +0200 Subject: [PATCH 08/12] Enforce value to be in u64 range --- packages/derive/src/lib.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/derive/src/lib.rs b/packages/derive/src/lib.rs index 48f2ceb2f9..2439215ba5 100644 --- a/packages/derive/src/lib.rs +++ b/packages/derive/src/lib.rs @@ -136,6 +136,8 @@ fn expand_attributes(func: &mut ItemFn) -> syn::Result { } let version: syn::LitInt = attribute.parse_args()?; + // Enforce that the version is in range of a u64 + version.base10_parse::()?; let version = version.base10_digits(); stream = quote! { @@ -190,6 +192,23 @@ mod test { use crate::entry_point_impl; + #[test] + fn contract_state_version_in_u64() { + let code = quote! { + #[set_state_version(0xDEAD_BEEF_FFFF_DEAD_2BAD)] + fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> Response { + // Logic here + } + }; + + let actual = entry_point_impl(TokenStream::new(), code); + let expected = quote! { + ::core::compile_error! { "number too large to fit in target type" } + }; + + assert_eq!(actual.to_string(), expected.to_string()); + } + #[test] fn contract_state_version_expansion() { let code = quote! { From 80be776aa383739a242c8989fa7a428fa0c9b22f Mon Sep 17 00:00:00 2001 From: Aumetra Weisman Date: Thu, 25 Apr 2024 11:05:39 +0200 Subject: [PATCH 09/12] Remove `set_` prefix --- packages/derive/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/derive/src/lib.rs b/packages/derive/src/lib.rs index 2439215ba5..a01999b7e7 100644 --- a/packages/derive/src/lib.rs +++ b/packages/derive/src/lib.rs @@ -106,7 +106,7 @@ impl Parse for Options { /// # /// # type MigrateMsg = (); /// #[entry_point] -/// #[set_state_version(2)] +/// #[state_version(2)] /// pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> StdResult { /// todo!(); /// } @@ -123,7 +123,7 @@ fn expand_attributes(func: &mut ItemFn) -> syn::Result { let attributes = std::mem::take(&mut func.attrs); let mut stream = TokenStream::new(); for attribute in attributes { - if !attribute.path().is_ident("set_state_version") { + if !attribute.path().is_ident("state_version") { func.attrs.push(attribute); continue; } @@ -195,7 +195,7 @@ mod test { #[test] fn contract_state_version_in_u64() { let code = quote! { - #[set_state_version(0xDEAD_BEEF_FFFF_DEAD_2BAD)] + #[state_version(0xDEAD_BEEF_FFFF_DEAD_2BAD)] fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> Response { // Logic here } @@ -212,7 +212,7 @@ mod test { #[test] fn contract_state_version_expansion() { let code = quote! { - #[set_state_version(2)] + #[state_version(2)] fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> Response { // Logic here } From c90c0149efc2eef50cdf0425bda85d3b82f4c549 Mon Sep 17 00:00:00 2001 From: Aumetra Weisman Date: Thu, 25 Apr 2024 11:21:42 +0200 Subject: [PATCH 10/12] Test attribute on non-migrate function --- packages/derive/src/lib.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/derive/src/lib.rs b/packages/derive/src/lib.rs index a01999b7e7..e43499af41 100644 --- a/packages/derive/src/lib.rs +++ b/packages/derive/src/lib.rs @@ -131,7 +131,7 @@ fn expand_attributes(func: &mut ItemFn) -> syn::Result { if func.sig.ident != "migrate" { return Err(syn::Error::new_spanned( &attribute, - "you only want to add this macro to your migrate function", + "you only want to add this attribute to your migrate function", )); } @@ -192,6 +192,23 @@ mod test { use crate::entry_point_impl; + #[test] + fn contract_state_version_on_non_migratee() { + let code = quote! { + #[state_version(42)] + fn anything_else() -> Response { + // Logic here + } + }; + + let actual = entry_point_impl(TokenStream::new(), code); + let expected = quote! { + ::core::compile_error! { "you only want to add this attribute to your migrate function" } + }; + + assert_eq!(actual.to_string(), expected.to_string()); + } + #[test] fn contract_state_version_in_u64() { let code = quote! { From 1312c0051f61134177f6187c8e80638809979d92 Mon Sep 17 00:00:00 2001 From: Aumetra Weisman Date: Thu, 25 Apr 2024 11:35:28 +0200 Subject: [PATCH 11/12] Add zero check --- packages/derive/src/lib.rs | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/packages/derive/src/lib.rs b/packages/derive/src/lib.rs index e43499af41..4575530190 100644 --- a/packages/derive/src/lib.rs +++ b/packages/derive/src/lib.rs @@ -136,8 +136,14 @@ fn expand_attributes(func: &mut ItemFn) -> syn::Result { } let version: syn::LitInt = attribute.parse_args()?; - // Enforce that the version is in range of a u64 - version.base10_parse::()?; + // Enforce that the version is a valid u64 and non-zero + if version.base10_parse::()? == 0 { + return Err(syn::Error::new_spanned( + version, + "please start versioning with 1", + )); + } + let version = version.base10_digits(); stream = quote! { @@ -193,7 +199,24 @@ mod test { use crate::entry_point_impl; #[test] - fn contract_state_version_on_non_migratee() { + fn contract_state_zero_not_allowed() { + let code = quote! { + #[state_version(0)] + fn migrate() -> Response { + // Logic here + } + }; + + let actual = entry_point_impl(TokenStream::new(), code); + let expected = quote! { + ::core::compile_error! { "please start versioning with 1" } + }; + + assert_eq!(actual.to_string(), expected.to_string()); + } + + #[test] + fn contract_state_version_on_non_migrate() { let code = quote! { #[state_version(42)] fn anything_else() -> Response { From d8c301ee217d54556cbc7e2619761f0fb48b02a9 Mon Sep 17 00:00:00 2001 From: Aumetra Weisman Date: Thu, 25 Apr 2024 11:39:15 +0200 Subject: [PATCH 12/12] Changelog entry --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca12c32e53..3b4002a0b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,8 @@ and this project adheres to `Uint64::strict_add`/`::strict_sub` and document overflows. ([#2098], [#2107]) - cosmwasm-std: Add `QuerierWrapper::query_grpc` helper for gRPC queries. ([#2120]) +- cosmwasm-derive: Add `state_version` attribute for `migrate` entrypoints + ([#2124]) [#1983]: https://github.com/CosmWasm/cosmwasm/pull/1983 [#2057]: https://github.com/CosmWasm/cosmwasm/pull/2057 @@ -40,6 +42,7 @@ and this project adheres to [#2099]: https://github.com/CosmWasm/cosmwasm/pull/2099 [#2107]: https://github.com/CosmWasm/cosmwasm/pull/2107 [#2120]: https://github.com/CosmWasm/cosmwasm/pull/2120 +[#2124]: https://github.com/CosmWasm/cosmwasm/pull/2124 ### Changed