Skip to content

Commit

Permalink
Merge pull request #886 from CosmWasm/move-ensure-from-older-version
Browse files Browse the repository at this point in the history
Add `ensure_from_older_version`
  • Loading branch information
chipshort authored Nov 13, 2023
2 parents 49a2ed0 + 9eca047 commit 12a5f5a
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 6 deletions.
5 changes: 3 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion contracts/cw20-base/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ library = []

[dependencies]
cosmwasm-schema = { version = "1.4.0" }
cw-utils = "1.0.1"
cw2 = { path = "../../packages/cw2", version = "1.1.1" }
cw20 = { path = "../../packages/cw20", version = "1.1.1" }
cw-storage-plus = "1.1.0"
Expand All @@ -31,3 +30,4 @@ thiserror = { version = "1.0.49" }

[dev-dependencies]
cw-multi-test = "0.16.5"
cw-utils = "1.0.1"
3 changes: 1 addition & 2 deletions contracts/cw20-base/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@ use cosmwasm_std::{
to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Uint128,
};

use cw2::set_contract_version;
use cw2::{ensure_from_older_version, set_contract_version};
use cw20::{
BalanceResponse, Cw20Coin, Cw20ReceiveMsg, DownloadLogoResponse, EmbeddedLogo, Logo, LogoInfo,
MarketingInfoResponse, MinterResponse, TokenInfoResponse,
};
use cw_utils::ensure_from_older_version;

use crate::allowances::{
execute_burn_from, execute_decrease_allowance, execute_increase_allowance, execute_send_from,
Expand Down
1 change: 1 addition & 0 deletions packages/cw2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ cosmwasm-schema = "1.4.0"
cosmwasm-std = { version = "1.4.0", default-features = false }
cw-storage-plus = "1.1.0"
schemars = "0.8.15"
semver = { version = "1.0.20", default-features = false }
serde = { version = "1.0.188", default-features = false, features = ["derive"] }
thiserror = "1.0.49"
4 changes: 3 additions & 1 deletion packages/cw2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ wasmd query wasm contract-state raw [contract_addr] 636F6E74726163745F696E666F -
```

When the `migrate` function is called, then the new contract
can read that data andsee if this is an expected contract we can
can read that data and see if this is an expected contract we can
migrate from. And also contain extra version information if we
support multiple migrate paths.
This crate provides an `ensure_from_older_version` helper that
handles this.

### Data structures

Expand Down
4 changes: 4 additions & 0 deletions packages/cw2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ use cosmwasm_std::{
use cw_storage_plus::Item;
use thiserror::Error;

mod migrate;

pub use migrate::ensure_from_older_version;

pub const CONTRACT: Item<ContractVersion> = Item::new("contract_info");

#[cw_serde]
Expand Down
100 changes: 100 additions & 0 deletions packages/cw2/src/migrate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
use crate::{get_contract_version, set_contract_version};
use cosmwasm_std::{StdError, StdResult, Storage};
use semver::Version;

/// This function not only validates that the right contract and version can be migrated, but also
/// updates the contract version from the original (stored) version to the new version.
/// It returns the original version for the convenience of doing external checks.
pub fn ensure_from_older_version(
storage: &mut dyn Storage,
name: &str,
new_version: &str,
) -> StdResult<Version> {
let version: Version = new_version.parse().map_err(from_semver)?;
let stored = get_contract_version(storage)?;
let storage_version: Version = stored.version.parse().map_err(from_semver)?;

if name != stored.contract {
let msg = format!("Cannot migrate from {} to {}", stored.contract, name);
return Err(StdError::generic_err(msg));
}

if storage_version > version {
let msg = format!(
"Cannot migrate from newer version ({}) to older ({})",
stored.version, new_version
);
return Err(StdError::generic_err(msg));
}
if storage_version < version {
// we don't need to save anything if migrating from the same version
set_contract_version(storage, name, new_version)?;
}

Ok(storage_version)
}

fn from_semver(err: semver::Error) -> StdError {
StdError::generic_err(format!("Semver: {err}"))
}

#[cfg(test)]
mod tests {
use super::*;
use cosmwasm_std::testing::MockStorage;

#[test]
fn accepts_identical_version() {
let mut storage = MockStorage::new();
set_contract_version(&mut storage, "demo", "0.1.2").unwrap();
// ensure this matches
ensure_from_older_version(&mut storage, "demo", "0.1.2").unwrap();
}

#[test]
fn accepts_and_updates_on_newer_version() {
let mut storage = MockStorage::new();
set_contract_version(&mut storage, "demo", "0.4.0").unwrap();
// ensure this matches
let original_version = ensure_from_older_version(&mut storage, "demo", "0.4.2").unwrap();

// check the original version is returned
assert_eq!(original_version.to_string(), "0.4.0".to_string());

// check the version is updated
let stored = get_contract_version(&storage).unwrap();
assert_eq!(stored.contract, "demo".to_string());
assert_eq!(stored.version, "0.4.2".to_string());
}

#[test]
fn errors_on_name_mismatch() {
let mut storage = MockStorage::new();
set_contract_version(&mut storage, "demo", "0.1.2").unwrap();
// ensure this matches
let err = ensure_from_older_version(&mut storage, "cw20-base", "0.1.2").unwrap_err();
assert!(err.to_string().contains("cw20-base"), "{}", err);
assert!(err.to_string().contains("demo"), "{}", err);
}

#[test]
fn errors_on_older_version() {
let mut storage = MockStorage::new();
set_contract_version(&mut storage, "demo", "0.10.2").unwrap();
// ensure this matches
let err = ensure_from_older_version(&mut storage, "demo", "0.9.7").unwrap_err();
assert!(err.to_string().contains("0.10.2"), "{}", err);
assert!(err.to_string().contains("0.9.7"), "{}", err);
}

#[test]
fn errors_on_broken_version() {
let mut storage = MockStorage::new();
let err = ensure_from_older_version(&mut storage, "demo", "0.a.7").unwrap_err();
assert!(
err.to_string().contains("unexpected character 'a'"),
"{}",
err
);
}
}

0 comments on commit 12a5f5a

Please sign in to comment.