diff --git a/packages/cw2-supported-interface/.cargo/config b/packages/cw2-supported-interface/.cargo/config new file mode 100644 index 000000000..e82e5693f --- /dev/null +++ b/packages/cw2-supported-interface/.cargo/config @@ -0,0 +1,3 @@ +[alias] +wasm = "build --release --target wasm32-unknown-unknown" +wasm-debug = "build --target wasm32-unknown-unknown" diff --git a/packages/cw2-supported-interface/Cargo.toml b/packages/cw2-supported-interface/Cargo.toml new file mode 100644 index 000000000..25c737941 --- /dev/null +++ b/packages/cw2-supported-interface/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "cw2-supported-interface" +version = "1.0.0" +authors = ["ThienLK1@aura-nw"] +edition = "2021" +description = "Definition and types for the CosmWasm-2 interface" +license = "Apache-2.0" +repository = "https://github.com/CosmWasm/cw-plus" +homepage = "https://cosmwasm.com" + +[dependencies] +cosmwasm-schema = "1.0.0" +cosmwasm-std = { version = "1.0.0", default-features = false } +cw-storage-plus = "0.16.0" +schemars = "0.8.1" +serde = { version = "1.0.0", default-features = false, features = ["derive"] } diff --git a/packages/cw2-supported-interface/README.md b/packages/cw2-supported-interface/README.md new file mode 100644 index 000000000..b80c599a2 --- /dev/null +++ b/packages/cw2-supported-interface/README.md @@ -0,0 +1,58 @@ +# CW2 Spec: Contract Info + +Most of the CW* specs are focused on the *public interfaces* +of the contract. The APIs used for `ExecuteMsg` or `QueryMsg`. +However, when we wish to migrate or inspect smart contract info, +we need some form of smart contract information embedded on state. + +This is where CW2 comes in. It specifies a special Item to +be stored on disk by all contracts on `instantiate`. + +`ContractInfo` must be stored under the `"contract_info"` key which translates +to `"636F6E74726163745F696E666F"` in hex format. +Since the state is well defined, we do not need to support any "smart queries". +We do provide a helper to construct a "raw query" to read the ContractInfo +of any CW2-compliant contract. + +You can query using: +```shell +wasmd query wasm contract-state raw [contract_addr] 636F6E74726163745F696E666F --node $RPC +``` + +When the `migrate` function is called, then the new contract +can read that data andsee if this is an expected contract we can +migrate from. And also contain extra version information if we +support multiple migrate paths. + +### Data structures + +**Required** + +All CW2-compliant contracts must store the following data: + +* key: `contract_info` +* data: Json-serialized `ContractVersion` + +```rust +pub struct ContractVersion { + /// contract is a globally unique identifier for the contract. + /// it should build off standard namespacing for whichever language it is in, + /// and prefix it with the registry we use. + /// For rust we prefix with `crates.io:`, to give us eg. `crates.io:cw20-base` + pub contract: String, + /// version is any string that this implementation knows. It may be simple counter "1", "2". + /// or semantic version on release tags "v0.7.0", or some custom feature flag list. + /// the only code that needs to understand the version parsing is code that knows how to + /// migrate from the given contract (and is tied to it's implementation somehow) + pub version: String, +} +``` + +Thus, an serialized example may looks like: + +```json +{ + "contract": "crates.io:cw20-base", + "version": "v0.1.0" +} +``` diff --git a/packages/cw2-supported-interface/src/lib.rs b/packages/cw2-supported-interface/src/lib.rs new file mode 100644 index 000000000..a4e95aecc --- /dev/null +++ b/packages/cw2-supported-interface/src/lib.rs @@ -0,0 +1,136 @@ +/*! +Most of the CW* specs are focused on the *public interfaces* +of the contract. The APIs used for `ExecuteMsg` or `QueryMsg`. +However, when we wish to migrate or inspect smart contract info, +we need some form of smart contract information embedded on state. + +This is where CW2 comes in. It specifies a special Item to +be stored on disk by all contracts on `instantiate`. + +`ContractInfo` must be stored under the `"contract_info"` key which translates +to `"636F6E74726163745F696E666F"` in hex format. +Since the state is well defined, we do not need to support any "smart queries". +We do provide a helper to construct a "raw query" to read the ContractInfo +of any CW2-compliant contract. + +For more information on this specification, please check out the +[README](https://github.com/CosmWasm/cw-plus/blob/main/packages/cw2/README.md). +*/ + +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{CustomQuery, QuerierWrapper, QueryRequest, StdResult, Storage, WasmQuery}; +use cw_storage_plus::Item; + +pub const CONTRACT: Item = Item::new("contract_info"); + +#[cw_serde] +pub struct ContractVersion { + /// contract is the crate name of the implementing contract, eg. `crate:cw20-base` + /// we will use other prefixes for other languages, and their standard global namespacing + pub contract: String, + /// version is any string that this implementation knows. It may be simple counter "1", "2". + /// or semantic version on release tags "v0.7.0", or some custom feature flag list. + /// the only code that needs to understand the version parsing is code that knows how to + /// migrate from the given contract (and is tied to it's implementation somehow) + pub version: String, + /// supported_interface is an optional parameter returning a vector of string represents interfaces + /// that the contract support The string value is the interface crate names in Rust crate Registry. + /// This parameter is inspired by the EIP-165 from Ethereum. + /// Each string value should follow a common standard such as : + /// e.g ["crates.io:cw721","crates.io:cw2"] + /// NOTE: this is just a hint for the caller to adapt on how to interact with this contract. + /// There is no guarantee that the contract actually implement these interfaces. + pub supported_interface: Option>, +} + +/// get_contract_version can be use in migrate to read the previous version of this contract +pub fn get_contract_version(store: &dyn Storage) -> StdResult { + CONTRACT.load(store) +} + +/// set_contract_version should be used in instantiate to store the original version, and after a successful +/// migrate to update it +pub fn set_contract_version, U: Into>( + store: &mut dyn Storage, + name: T, + version: U, + supported_interface: Option>, +) -> StdResult<()> { + let val = ContractVersion { + contract: name.into(), + version: version.into(), + supported_interface: supported_interface.into(), + }; + CONTRACT.save(store, &val) +} + +/// This will make a raw_query to another contract to determine the current version it +/// claims to be. This should not be trusted, but could be used as a quick filter +/// if the other contract exists and claims to be a cw20-base contract for example. +/// (Note: you usually want to require *interfaces* not *implementations* of the +/// contracts you compose with, so be careful of overuse) +pub fn query_contract_info( + querier: &QuerierWrapper, + contract_addr: T, +) -> StdResult +where + T: Into, + CQ: CustomQuery, +{ + let req = QueryRequest::Wasm(WasmQuery::Raw { + contract_addr: contract_addr.into(), + key: CONTRACT.as_slice().into(), + }); + querier.query(&req) +} + +#[cfg(test)] +mod tests { + use super::*; + use cosmwasm_std::testing::MockStorage; + use std::vec::Vec; + + #[test] + fn get_and_set_work() { + let mut store = MockStorage::new(); + + // error if not set + assert!(get_contract_version(&store).is_err()); + + // set and get + let contract_name = "crate:cw20-base"; + let contract_version = "0.2.0"; + set_contract_version(&mut store, contract_name, contract_version, None).unwrap(); + + let loaded = get_contract_version(&store).unwrap(); + let expected = ContractVersion { + contract: contract_name.to_string(), + version: contract_version.to_string(), + supported_interface: None, + }; + assert_eq!(expected, loaded); + + // set and get with supported_interface + let contract_name = "crate:cw20-base"; + let contract_version = "0.2.0"; + let supported_interface = Some(Vec::from([ + "crates.io:cw2".to_string(), + "crates.io:cw721".to_string(), + ])); + set_contract_version( + &mut store, + contract_name, + contract_version, + supported_interface.clone(), + ) + .unwrap(); + + let loaded = get_contract_version(&store).unwrap(); + let expected = ContractVersion { + contract: contract_name.to_string(), + version: contract_version.to_string(), + supported_interface: supported_interface.clone(), + }; + assert_eq!(expected, loaded); + } +}