diff --git a/contracts/hackatom/src/contract.rs b/contracts/hackatom/src/contract.rs index 28251b947..f2e03c272 100644 --- a/contracts/hackatom/src/contract.rs +++ b/contracts/hackatom/src/contract.rs @@ -35,14 +35,21 @@ pub fn instantiate( Ok(Response::new().add_attribute("Let the", "hacking begin")) } +const CONTRACT_MIGRATE_VERSION: u64 = 420; + #[entry_point] -#[migrate_version(42)] +#[migrate_version(CONTRACT_MIGRATE_VERSION)] pub fn migrate( deps: DepsMut, _env: Env, msg: MigrateMsg, - _migrate_info: MigrateInfo, + migrate_info: MigrateInfo, ) -> Result { + if let Some(old_version) = migrate_info.old_migrate_version { + if CONTRACT_MIGRATE_VERSION <= old_version { + return Err(HackError::Downgrade); + } + } let data = deps .storage .get(CONFIG_KEY) diff --git a/contracts/hackatom/src/errors.rs b/contracts/hackatom/src/errors.rs index 7b46b00b6..a72d29246 100644 --- a/contracts/hackatom/src/errors.rs +++ b/contracts/hackatom/src/errors.rs @@ -9,4 +9,7 @@ pub enum HackError { // this is whatever we want #[error("Unauthorized")] Unauthorized {}, + // this is whatever we want + #[error("Downgrade is not supported")] + Downgrade, } diff --git a/packages/derive/src/lib.rs b/packages/derive/src/lib.rs index 41d4c8db7..253b10f94 100644 --- a/packages/derive/src/lib.rs +++ b/packages/derive/src/lib.rs @@ -1,6 +1,5 @@ use proc_macro2::TokenStream; use quote::{format_ident, quote, ToTokens}; -use syn::spanned::Spanned; use syn::{ parse::{Parse, ParseStream}, parse_quote, @@ -101,14 +100,14 @@ impl Parse for Options { /// /// ``` /// # use cosmwasm_std::{ -/// # DepsMut, entry_point, Env, +/// # DepsMut, entry_point, Env, MigrateInfo, /// # Response, StdResult, /// # }; /// # /// # type MigrateMsg = (); /// #[entry_point] /// #[migrate_version(2)] -/// pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> StdResult { +/// pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg, migrate_info: MigrateInfo) -> StdResult { /// todo!(); /// } /// ``` @@ -118,14 +117,16 @@ impl Parse for Options { /// /// ``` /// # use cosmwasm_std::{ -/// # DepsMut, entry_point, Env, +/// # DepsMut, entry_point, Env, MigrateInfo, /// # Response, StdResult, /// # }; /// # /// # type MigrateMsg = (); +/// const CONTRACT_VERSION: u64 = 66; +/// /// #[entry_point] -/// #[migrate_version(CONTRACT_VERSION = 2)] -/// pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> StdResult { +/// #[migrate_version(CONTRACT_VERSION)] +/// pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg, migrate_info: MigrateInfo) -> StdResult { /// todo!(); /// } /// ``` @@ -153,69 +154,50 @@ fn expand_attributes(func: &mut ItemFn) -> syn::Result { )); } - let (const_name, version): (Option, syn::LitInt) = - match attribute.parse_args()? { - syn::Expr::Assign(syn::ExprAssign { left, right, .. }) => match (*left, *right) { - ( - syn::Expr::Path(syn::ExprPath { path, .. }), - syn::Expr::Lit(syn::ExprLit { - lit: syn::Lit::Int(version), - .. - }), - ) => (Some(path), version), - _ => { - return Err(syn::Error::new( - attribute.span(), - "Expected version number or `CONST_NAME = {version_number}`", - )) - } - }, - syn::Expr::Lit(syn::ExprLit { - lit: syn::Lit::Int(version), - .. - }) => (None, version), - _ => { - return Err(syn::Error::new( - attribute.span(), - "Expected version number or `CONST_NAME = {version_number}`", - )) - } - }; - - // Enforce that the version is a valid u64 and non-zero - let numeric_version = version.base10_parse::()?; - if numeric_version == 0 { + let version: syn::Expr = attribute.parse_args()?; + if !(matches!(version, syn::Expr::Lit(_)) || matches!(version, syn::Expr::Path(_))) { return Err(syn::Error::new_spanned( - version, - "please start versioning with 1", + &attribute, + "Expected `u64` or `path::to::constant` in the migrate_version attribute", )); } - let const_assignment = if let Some(const_name) = const_name { - quote! { - #[allow(unused)] - #[doc(hidden)] - pub const #const_name: u64 = #numeric_version; - } - } else { - quote! {} - }; - - let version = version.base10_digits(); - let n = version.len(); - let version = proc_macro2::Literal::byte_string(version.as_bytes()); stream = quote! { #stream - #[allow(unused)] - #[doc(hidden)] - #[cfg(target_arch = "wasm32")] - #[link_section = "cw_migrate_version"] - /// This is an internal constant exported as a custom section denoting the contract migrate version. - /// The format and even the existence of this value is an implementation detail, DO NOT RELY ON THIS! - static __CW_MIGRATE_VERSION: [u8; #n] = *#version; + const _: () = { + #[allow(unused)] + #[doc(hidden)] + #[cfg(target_arch = "wasm32")] + #[link_section = "cw_migrate_version"] + /// This is an internal constant exported as a custom section denoting the contract migrate version. + /// The format and even the existence of this value is an implementation detail, DO NOT RELY ON THIS! + static __CW_MIGRATE_VERSION: [u8; version_size(#version)] = stringify_version(#version); + + #[allow(unused)] + #[doc(hidden)] + const fn stringify_version(mut version: u64) -> [u8; N] { + let mut result: [u8; N] = [0; N]; + let mut index = N; + while index > 0 { + let digit: u8 = (version%10) as u8; + result[index-1] = digit + b'0'; + version /= 10; + index -= 1; + } + result + } - #const_assignment + #[allow(unused)] + #[doc(hidden)] + const fn version_size(version: u64) -> usize { + if version > 0 { + (version.ilog10()+1) as usize + } else { + panic!("Contract migrate version should be greater than 0.") + } + } + }; }; } @@ -265,23 +247,6 @@ mod test { use crate::entry_point_impl; - #[test] - fn contract_state_zero_not_allowed() { - let code = quote! { - #[migrate_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_migrate_version_on_non_migrate() { let code = quote! { @@ -299,23 +264,6 @@ mod test { assert_eq!(actual.to_string(), expected.to_string()); } - #[test] - fn contract_migrate_version_in_u64() { - let code = quote! { - #[migrate_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_migrate_version_expansion() { let code = quote! { @@ -327,13 +275,39 @@ mod test { let actual = entry_point_impl(TokenStream::new(), code); let expected = quote! { - #[allow(unused)] - #[doc(hidden)] - #[cfg(target_arch = "wasm32")] - #[link_section = "cw_migrate_version"] - /// This is an internal constant exported as a custom section denoting the contract migrate version. - /// The format and even the existence of this value is an implementation detail, DO NOT RELY ON THIS! - static __CW_MIGRATE_VERSION: [u8; 1usize] = *b"2"; + const _: () = { + #[allow(unused)] + #[doc(hidden)] + #[cfg(target_arch = "wasm32")] + #[link_section = "cw_migrate_version"] + /// This is an internal constant exported as a custom section denoting the contract migrate version. + /// The format and even the existence of this value is an implementation detail, DO NOT RELY ON THIS! + static __CW_MIGRATE_VERSION: [u8; version_size(2)] = stringify_version(2); + + #[allow(unused)] + #[doc(hidden)] + const fn stringify_version(mut version: u64) -> [u8; N] { + let mut result: [u8; N] = [0; N]; + let mut index = N; + while index > 0 { + let digit: u8 = (version%10) as u8; + result[index-1] = digit + b'0'; + version /= 10; + index -= 1; + } + result + } + + #[allow(unused)] + #[doc(hidden)] + const fn version_size(version: u64) -> usize { + if version > 0 { + (version.ilog10()+1) as usize + } else { + panic!("Contract migrate version should be greater than 0.") + } + } + }; fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> Response { // Logic here @@ -354,7 +328,7 @@ mod test { #[test] fn contract_migrate_version_with_const_expansion() { let code = quote! { - #[migrate_version(CONTRACT_VERSION = 66)] + #[migrate_version(CONTRACT_VERSION)] fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> Response { // Logic here } @@ -362,17 +336,39 @@ mod test { let actual = entry_point_impl(TokenStream::new(), code); let expected = quote! { - #[allow(unused)] - #[doc(hidden)] - #[cfg(target_arch = "wasm32")] - #[link_section = "cw_migrate_version"] - /// This is an internal constant exported as a custom section denoting the contract migrate version. - /// The format and even the existence of this value is an implementation detail, DO NOT RELY ON THIS! - static __CW_MIGRATE_VERSION: [u8; 2usize] = *b"66"; - - #[allow(unused)] - #[doc(hidden)] - pub const CONTRACT_VERSION: u64 = 66u64; + const _: () = { + #[allow(unused)] + #[doc(hidden)] + #[cfg(target_arch = "wasm32")] + #[link_section = "cw_migrate_version"] + /// This is an internal constant exported as a custom section denoting the contract migrate version. + /// The format and even the existence of this value is an implementation detail, DO NOT RELY ON THIS! + static __CW_MIGRATE_VERSION: [u8; version_size(CONTRACT_VERSION)] = stringify_version(CONTRACT_VERSION); + + #[allow(unused)] + #[doc(hidden)] + const fn stringify_version(mut version: u64) -> [u8; N] { + let mut result: [u8; N] = [0; N]; + let mut index = N; + while index > 0 { + let digit: u8 = (version%10) as u8; + result[index-1] = digit + b'0'; + version /= 10; + index -= 1; + } + result + } + + #[allow(unused)] + #[doc(hidden)] + const fn version_size(version: u64) -> usize { + if version > 0 { + (version.ilog10()+1) as usize + } else { + panic!("Contract migrate version should be greater than 0.") + } + } + }; fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> Response { // Logic here