Skip to content

Commit

Permalink
feat: Allow user to pick constant as a migrate version
Browse files Browse the repository at this point in the history
  • Loading branch information
kulikthebird committed Sep 2, 2024
1 parent bb79d14 commit 0b589db
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 116 deletions.
11 changes: 9 additions & 2 deletions contracts/hackatom/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Response, HackError> {
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)
Expand Down
3 changes: 3 additions & 0 deletions contracts/hackatom/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
224 changes: 110 additions & 114 deletions packages/derive/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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<Response> {
/// pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg, migrate_info: MigrateInfo) -> StdResult<Response> {
/// todo!();
/// }
/// ```
Expand All @@ -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<Response> {
/// #[migrate_version(CONTRACT_VERSION)]
/// pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg, migrate_info: MigrateInfo) -> StdResult<Response> {
/// todo!();
/// }
/// ```
Expand Down Expand Up @@ -153,69 +154,50 @@ fn expand_attributes(func: &mut ItemFn) -> syn::Result<TokenStream> {
));
}

let (const_name, version): (Option<syn::Path>, 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::<u64>()?;
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<const N: usize>(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.")
}
}
};
};
}

Expand Down Expand Up @@ -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! {
Expand All @@ -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! {
Expand All @@ -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<const N: usize>(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
Expand All @@ -354,25 +328,47 @@ 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
}
};

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<const N: usize>(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
Expand Down

0 comments on commit 0b589db

Please sign in to comment.