Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cw22 add supported interface #844

Open
wants to merge 24 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ workflows:
- package_cw3
- package_cw4
- package_cw20
- package_cw22
- lint
- wasm-build
- coverage
Expand Down Expand Up @@ -419,6 +420,31 @@ jobs:
- target
key: cargocache-v2-cw20:1.64.0-{{ checksum "~/project/Cargo.lock" }}

package_cw22:
docker:
- image: rust:1.64.0
working_directory: ~/project/packages/cw22
steps:
- checkout:
path: ~/project
- run:
name: Version information
command: rustc --version; cargo --version; rustup --version; rustup target list --installed
- restore_cache:
keys:
- cargocache-v2-cw22:1.64.0-{{ checksum "~/project/Cargo.lock" }}
- run:
name: Build library for native target
command: cargo build --locked
- run:
name: Run unit tests
command: cargo test --locked
- save_cache:
paths:
- /usr/local/cargo/registry
- target
key: cargocache-v2-cw22:1.64.0-{{ checksum "~/project/Cargo.lock" }}

package_cw1155:
docker:
- image: rust:1.64.0
Expand Down
12 changes: 12 additions & 0 deletions Cargo.lock

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

3 changes: 3 additions & 0 deletions packages/cw22/.cargo/config
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[alias]
wasm = "build --release --target wasm32-unknown-unknown"
wasm-debug = "build --target wasm32-unknown-unknown"
17 changes: 17 additions & 0 deletions packages/cw22/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "cw22"
version = "0.1.0"
authors = ["ThienLK1@aura-nw"]
edition = "2021"
description = "Definition and types for the CosmWasm-22 interface"
license = "Apache-2.0"
repository = "https://github.com/CosmWasm/cw-plus"
homepage = "https://cosmwasm.com"

[dependencies]
cosmwasm-schema = "1.1.0"
cosmwasm-std = { version = "1.1.0", default-features = false }
cw-storage-plus = "1.0.1"
schemars = "0.8.1"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
semver = "1.0.14"
53 changes: 53 additions & 0 deletions packages/cw22/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# CW22 Spec: Contract Info

The standard used to declare which interface contract implements. This standard is inspired by the EIP-165 from
Ethereum.

For more information on this specification, please check out the
[README](https://github.com/CosmWasm/cw-plus/blob/main/packages/cw22/README.md).

### Data structures

**Required**

All CW22-compliant contracts must store the following data:

* key: `contract_supported_interface`
* data: Json-serialized `ContractSupportedInterface`

```rust
pub struct ContractSupportedInterface<'a> {
/// supported_interface is the name of an interface that the contract supports.
/// This is inspired by the EIP-165 from Ethereum.
/// Interface names should follow a common standard such as <Registry Domain>:<Crate Name> in Rust crate registry.
/// e.g. "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 implements the interface.
pub supported_interface: Cow<'a, str>,
/// Version on release tags of the interface package following [SemVer](https://semver.org/) guideline.
/// e.g. "0.16.0"
pub version: Cow<'a, str>,
}
```

Below is an example used in cw20 contract, where we declare to implement cw20 interface with version 0.16.0 at
instantiate:

```rust
use cw22::{set_contract_supported_interface, ContractSupportedInterface};

pub fn instantiate(
mut deps: DepsMut,
_env: Env,
_info: MessageInfo,
msg: InstantiateMsg,
) -> Result<Response, ContractError> {
///...
let supported_interface = ContractSupportedInterface {
supported_interface: "crates.io:cw20".into(),
version: "0.16.0".into(),
};
set_contract_supported_interface(deps.storage, &[supported_interface])?;
///...
}
```
171 changes: 171 additions & 0 deletions packages/cw22/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
//! CW22 defines a way for a contract to declare which interfaces do the contract implement
//! This standard is inspired by the EIP-165 from Ethereum. Originally it was proposed to
//! be merged into CW2: Contract Info, then it is splitted to a separated cargo to keep CW2
//! being backward compatible.

//! Each supported interface contains a string value pointing to the corresponding cargo package
//! and a specific release of the package. There is also a function to check whether the contract
//! support a specific version of an interface or not.

//! The version string for each interface follows Semantic Versioning standard. More info is in:
//! https://docs.rs/semver/latest/semver/
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{StdError, StdResult, Storage};
use cw_storage_plus::Map;
use semver::{Version, VersionReq};
use std::borrow::Cow;

pub const SUPPORTED_INTERFACES: Map<&str, String> = Map::new("supported_interfaces");

#[cw_serde]
pub struct ContractSupportedInterface<'a> {
/// supported_interface is the name of an interface that the contract support.
/// This is inspired by the EIP-165 from Ethereum.
/// Interface names should follow a common standard such as <Registry Domain>:<Crate Name> in Rust crate registry.
/// e.g. "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: Cow<'a, str>,
/// semantic version on release tags of the interface package following SemVer guideline.
/// e.g. "0.16.0"
pub version: Cow<'a, str>,
}

/// set_contract_supported_interface should be used in instantiate to store the original version
/// of supported interfaces. It should also be used after every migration.
pub fn set_contract_supported_interface(
store: &mut dyn Storage,
supported_interfaces: &[ContractSupportedInterface],
) -> StdResult<()> {
for item in supported_interfaces {
let ver = Version::parse(&item.version);
match ver {
Ok(_) => {
SUPPORTED_INTERFACES.save(
store,
&item.supported_interface,
&item.version.to_string(),
)?;
}
Err(_) => {
return Err(StdError::generic_err("Version's format is invalid"));
}
}
}
Ok(())
}

/// query_supported_interface_version show the version of an interface supported by the contract
pub fn query_supported_interface_version(
store: &dyn Storage,
interface: &str,
) -> StdResult<Option<String>> {
let version = SUPPORTED_INTERFACES.may_load(store, interface)?;
Ok(version)
}

pub fn minimum_version(version: &str, required: &str) -> bool {
if let Ok(ver) = Version::parse(version) {
if let Ok(req) = VersionReq::parse(format!(">={}", required).as_str()) {
return req.matches(&ver);
}
}
false
}

/// query_supported_interface show if contract supports an interface with version following SemVer query
/// query example">=1.2.3, <1.8.0"
pub fn require_version(version: &str, request: &str) -> bool {
if let Ok(ver) = Version::parse(version) {
if let Ok(req) = VersionReq::parse(request) {
return req.matches(&ver);
}
}
false
}

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

#[test]
fn get_and_set_work() {
let mut store = MockStorage::new();

let interface2 = "crates.io:cw2";
let interface22 = "crates.io:cw22";
let interface721 = "crates.io:cw721";
let contract_interface2 = ContractSupportedInterface {
supported_interface: Cow::Borrowed(interface2),
version: Cow::from("0.16.0"),
};
let contract_interface22 = ContractSupportedInterface {
supported_interface: Cow::Borrowed(interface22),
version: "0.1.0".into(),
};
let contract_interface721 = ContractSupportedInterface {
supported_interface: Cow::Borrowed(interface22),
version: Cow::from("v0.1.0"),
};

// set supported_interface error
let supported_interface = &[contract_interface721];

let rs_error =
set_contract_supported_interface(&mut store, supported_interface).unwrap_err();
let expected = StdError::generic_err("Version's format is invalid");
assert_eq!(expected, rs_error);

// set supported_interface
let supported_interface = &[contract_interface2, contract_interface22];

set_contract_supported_interface(&mut store, supported_interface).unwrap();
// get version of not supported interface
let loaded = query_supported_interface_version(&store, interface721).unwrap();
assert_eq!(None, loaded);

// get version of supported interface
let loaded = query_supported_interface_version(&store, interface2).unwrap();
let expected = String::from("0.16.0");
assert_eq!(Some(expected), loaded);
}

#[test]
fn test_require_version() {
let version_req = ">=0.1.0";
let result = require_version("0.16.0", version_req);
assert!(result);

let version_req = ">=0.16.0";
let result = require_version("0.1.0", version_req);
assert!(!result);

let version_req = ">=1.2.3, <1.8.0";
let result = require_version("0.16.0", version_req);
assert!(!result);

let version_req = ">=0.2.3";
let result = require_version("v0.16.0", version_req);
assert!(!result);

let version_req = "!=0.2.3";
let result = require_version("0.16.0", version_req);
assert!(!result);
}

#[test]
fn test_minimum_version() {
let result = minimum_version("0.16.0", "0.2.3");
assert!(result);

let result = minimum_version("0.2.0", "0.2.3");
assert!(!result);

let result = minimum_version("v0.16.0", "0.2.3");
assert!(!result);

let result = minimum_version("0.16.0", "v0.2.3");
assert!(!result);
}
}