diff --git a/contracts/cw20-taxed/src/contract.rs b/contracts/cw20-taxed/src/contract.rs index b650e9e..6002a4d 100644 --- a/contracts/cw20-taxed/src/contract.rs +++ b/contracts/cw20-taxed/src/contract.rs @@ -20,14 +20,15 @@ use crate::enumerable::{query_all_accounts, query_owner_allowances, query_spende use crate::error::ContractError; use crate::msg::{Cw20TaxedExecuteMsg as ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; use crate::state::{ - MinterData, TokenInfo, ALLOWANCES, ALLOWANCES_SPENDER, BALANCES, LOGO, MARKETING_INFO, TAX_INFO, TOKEN_INFO + self, MinterData, TokenInfo, ALLOWANCES, ALLOWANCES_SPENDER, BALANCES, LOGO, MARKETING_INFO, TAX_INFO, TOKEN_INFO }; use crate::tax::{self, TaxMap}; // version info for migration info -const CONTRACT_NAME: &str = "crates.io:cw20-base"; -const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); +pub const CONTRACT_NAME: &str = "crates.io:cw20-base"; +pub const CONTRACT_NAME_TERRAPORT: &str = "crates.io:terraport-token"; +pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); const LOGO_SIZE_CAP: usize = 5 * 1024; @@ -722,6 +723,12 @@ pub fn query_download_logo(deps: Deps) -> StdResult { #[cfg_attr(not(feature = "library"), entry_point)] pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result { + + // merge upgrade paths + // crates.io:terraport-token 0.0.0 -> crates.io:cw20-base 1.1.0 + // crates.io:cw20-base 1.1.0 -> crates.io:cw20-base 1.1.0 + state::migrate_v1::ensure_known_upgrade_path(deps.storage)?; + let original_version = ensure_from_older_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; @@ -736,12 +743,20 @@ pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result().unwrap() { - // Add tax map - let tax_map = match msg.tax_map { - Some(x) => x, - None => TaxMap::default(), - }; - TAX_INFO.save(deps.storage, &tax_map)?; + match TAX_INFO.load(deps.storage) { + // there seems to be an existing tax map, so we don't need to do anything + Ok(_) => {}, + + // no tax map, so we need to add one + Err(_) => { + // Add tax map + let tax_map = match msg.tax_map { + Some(x) => x, + None => TaxMap::default(), + }; + TAX_INFO.save(deps.storage, &tax_map)?; + } + } } Ok(Response::default()) } diff --git a/contracts/cw20-taxed/src/state.rs b/contracts/cw20-taxed/src/state.rs index 3dd05a6..adab4fe 100644 --- a/contracts/cw20-taxed/src/state.rs +++ b/contracts/cw20-taxed/src/state.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, Decimal, Uint128}; +use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; use cw_storage_plus::{Item, Map}; use cw20::{AllowanceResponse, Logo, MarketingInfoResponse}; @@ -39,3 +39,105 @@ pub const ALLOWANCES_SPENDER: Map<(&Addr, &Addr), AllowanceResponse> = // specific for TAXED token pub const TAX_INFO: Item = Item::new("tax_info"); + +// specific only for migration from Terraport Tokens +pub mod migrate_v1 { + use std::{backtrace::Backtrace, str::FromStr}; + + use cosmwasm_std::{to_json_binary, Addr, CosmosMsg, Order, StdError, StdResult, Storage, Uint128}; + use cw2::{get_contract_version, set_contract_version}; + use cw_storage_plus::{Map, SnapshotMap, Strategy}; + use semver::Version; + + use crate::contract::{CONTRACT_NAME, CONTRACT_NAME_TERRAPORT}; + + pub const BALANCES: SnapshotMap<&Addr, Uint128> = SnapshotMap::new( + "balance", + "balance__checkpoints", + "balance__changelog", + Strategy::EveryBlock, + ); + + pub const TOTAL_SUPPLY_HISTORY: Map = Map::new("total_supply_history"); + + pub fn is_terraport_token_v0(store: &dyn Storage) -> StdResult { + let version = get_contract_version(store)?; + Ok(version.contract == CONTRACT_NAME_TERRAPORT && version.version == "0.0.0") + } + + pub fn is_cw20_taxed_v0(store: &dyn Storage) -> StdResult { + let version = get_contract_version(store)?; + let this_version = Version::from_str( + version.version.as_str(), + ).map_err(|_| StdError::generic_err("no valid version in store"))?; + let expect_v0 = Version::from_str("1.1.0") + .map_err(|_| StdError::generic_err("could not parse version 1.0.0"))?; + Ok(version.contract == CONTRACT_NAME && expect_v0 <= this_version) + } + + pub fn ensure_known_upgrade_path(store: &mut dyn Storage) -> StdResult<()> { + + if is_terraport_token_v0(store)? { + set_contract_version(store, "crates.io:cw20-base", "1.1.0")?; + return Ok(()); + } else if is_cw20_taxed_v0(store)? { + return Ok(()); + } else { + return Err(StdError::generic_err("This is not a knowledable migration path")); + } + } + + #[cfg(test)] + mod tests { + use super::*; + use cosmwasm_std::testing::MockStorage; + use cw2::set_contract_version; + + // setup a terraport style balances store + fn setup(balances: Vec<(Addr, Uint128, u64)>) -> MockStorage { + let mut store = MockStorage::new(); + set_contract_version(&mut store, "crates.io:terraport-token", "0.0.0").unwrap(); + for (addr, balance, height) in balances { + BALANCES.save(&mut store, &addr, &balance, height).unwrap(); + } + store + } + + #[test] + fn test_is_terraport_token_v0() { + let mut store = MockStorage::new(); + + set_contract_version(&mut store, "crates.io:cw20-base", "1.0.6").unwrap(); + assert_eq!(is_terraport_token_v0(&store).unwrap(), false); + + set_contract_version(&mut store, "crates.io:cw20-base", "0.0.0").unwrap(); + assert_eq!(is_terraport_token_v0(&store).unwrap(), false); + + set_contract_version(&mut store, "crates.io:terraport-token", "0.0.0").unwrap(); + assert_eq!(is_terraport_token_v0(&store).unwrap(), true); + + set_contract_version(&mut store, "crates.io:terraport-token", "1.0.0").unwrap(); + assert_eq!(is_terraport_token_v0(&store).unwrap(), false); + } + + #[test] + fn test_terraport_snapshot_map_is_compatible_with_map() { + let mut store = setup(vec![ + // initial balances + (Addr::unchecked("addr1"), Uint128::new(1234), 123), + (Addr::unchecked("addr2"), Uint128::new(1234), 123), + (Addr::unchecked("addr3"), Uint128::new(4455), 123), + + // mock a transfer at later height + (Addr::unchecked("addr1"), Uint128::new(1233), 456), + (Addr::unchecked("addr2"), Uint128::new(1235), 456), + ]); + + // ensure the new data is compatible + assert_eq!(super::BALANCES.load(&store, &Addr::unchecked("addr1")).unwrap(), Uint128::new(1233)); + assert_eq!(super::BALANCES.load(&store, &Addr::unchecked("addr2")).unwrap(), Uint128::new(1235)); + assert_eq!(super::BALANCES.load(&store, &Addr::unchecked("addr3")).unwrap(), Uint128::new(4455)); + } + } + +} \ No newline at end of file