diff --git a/CHANGELOG.md b/CHANGELOG.md index c6ad25e2..b59d38c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## Unreleased changes * migrate to GRPC queries ([#157](https://github.com/provenance-io/provwasm/issues/157)) +* add `MetadataAddress` type for encoding/decoding metadata + addresses ([#161](https://github.com/provenance-io/provwasm/pull/161)) ## Releases diff --git a/RELEASE_CHANGELOG.md b/RELEASE_CHANGELOG.md index 334b0dd6..64a8718f 100644 --- a/RELEASE_CHANGELOG.md +++ b/RELEASE_CHANGELOG.md @@ -1,3 +1,5 @@ ### [v2.4.0](https://github.com/provenance-io/provwasm/tree/v2.4.0) * migrate to GRPC queries ([#157](https://github.com/provenance-io/provwasm/issues/157)) +* add `MetadataAddress` type for encoding/decoding metadata + addresses ([#161](https://github.com/provenance-io/provwasm/pull/161)) \ No newline at end of file diff --git a/contracts/marker/src/types.rs b/contracts/marker/src/types.rs index 1cebe8f1..da710b2a 100644 --- a/contracts/marker/src/types.rs +++ b/contracts/marker/src/types.rs @@ -11,7 +11,7 @@ pub struct Marker { pub struct MarkAccount { pub base_account: Option, pub manager: String, - pub demon: String, + pub denom: String, pub supply: String, } @@ -20,7 +20,7 @@ impl From for MarkAccount { MarkAccount { base_account: value.base_account.unwrap().address.into(), manager: value.manager, - demon: value.denom.to_string(), + denom: value.denom.to_string(), supply: value.supply.to_string(), } } diff --git a/contracts/name/src/msg.rs b/contracts/name/src/msg.rs index 94eed8f9..b620d209 100644 --- a/contracts/name/src/msg.rs +++ b/contracts/name/src/msg.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; -use provwasm_std::types::provenance::name::v1::{QueryResolveResponse, QueryReverseLookupResponse}; +use provwasm_std::types::provenance::name::v1::QueryReverseLookupResponse; #[cw_serde] pub struct InitMsg { @@ -23,21 +23,6 @@ pub enum QueryMsg { Params {}, } -#[cw_serde] -pub struct ResolveResponse { - address: String, - restricted: bool, -} - -impl From for ResolveResponse { - fn from(value: QueryResolveResponse) -> Self { - ResolveResponse { - address: value.address, - restricted: value.restricted, - } - } -} - #[cw_serde] pub struct LookupResponse { pub name: Vec, diff --git a/packages/provwasm-std/Cargo.toml b/packages/provwasm-std/Cargo.toml index 88b5a9fc..51e3767b 100644 --- a/packages/provwasm-std/Cargo.toml +++ b/packages/provwasm-std/Cargo.toml @@ -10,9 +10,12 @@ keywords = ["provenance", "blockchain", "smart-contracts", "defi", "finance"] categories = ["api-bindings", "cryptography::cryptocurrencies", "wasm"] [dependencies] +base64 = "0.22.1" +bech32 = { version = "0.11.0", default-features = false, features = ["alloc"] } chrono = { version = "0.4.24", default-features = false } cosmwasm-std = { workspace = true } cosmwasm-schema = { workspace = true } +hex = { version = "0.4.3", default-features = false } prost = { workspace = true, default-features = false, features = ["prost-derive"] } prost-types = { workspace = true, default-features = false } provwasm-common = { workspace = true } @@ -20,5 +23,7 @@ provwasm-proc-macro = { workspace = true } schemars = { workspace = true } serde = { workspace = true, default-features = false, features = ["derive"] } serde-cw-value = "0.7.0" -base64 = "0.22.1" +sha2 = { version = "0.10.8", default-features = false } strum_macros = "0.26.4" +thiserror = { workspace = true } +uuid = { version = "1.10.0", default-features = false } \ No newline at end of file diff --git a/packages/provwasm-std/src/lib.rs b/packages/provwasm-std/src/lib.rs index 0a9794f8..04cbc517 100644 --- a/packages/provwasm-std/src/lib.rs +++ b/packages/provwasm-std/src/lib.rs @@ -1,7 +1,7 @@ /// The version (commit hash) of the Cosmos SDK used when generating this library. pub const PROVENANCE_VERSION: &str = include_str!("types/PROVENANCE_COMMIT"); -// mod serde; +pub mod metadata_address; pub mod shim; #[allow( deprecated, diff --git a/packages/provwasm-std/src/metadata_address.rs b/packages/provwasm-std/src/metadata_address.rs new file mode 100644 index 00000000..4f2e6974 --- /dev/null +++ b/packages/provwasm-std/src/metadata_address.rs @@ -0,0 +1,323 @@ +use crate::metadata_address::KeyType::{ + ContractSpecification, Record, RecordSpecification, Scope, ScopeSpecification, Session, +}; +use bech32::{Bech32, Hrp}; +use cosmwasm_std::StdError; +use sha2::{Digest, Sha256}; +use uuid::Uuid; + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum KeyType { + Scope = 0x00, + Session = 0x01, + Record = 0x02, + ContractSpecification = 0x03, + ScopeSpecification = 0x04, + RecordSpecification = 0x05, +} + +impl KeyType { + pub fn to_str(&self) -> &str { + match self { + Scope => "scope", + Session => "session", + Record => "record", + ContractSpecification => "contractspec", + ScopeSpecification => "scopespec", + RecordSpecification => "recspec", + } + } +} + +/// Represents a Provenance `MetadataAddress` type +#[derive(Clone, Debug, PartialEq)] +pub struct MetadataAddress { + pub bech32: String, + pub bytes: Vec, + pub key_type: KeyType, +} + +impl MetadataAddress { + /// Create a Contract Specification Metadata Address from a `Uuid` + /// + /// e.g. `contractspec1qw07zlu62ms5zk9g4azsdq9vnesqy4dtgm` + pub fn contract_specification( + contract_specification_uuid: Uuid, + ) -> Result { + let key_type_byte = ContractSpecification as u8; + let bytes = [key_type_byte] + .iter() + .cloned() + .chain(Self::hex_encode_uuid(contract_specification_uuid)) + .collect::>(); + + let addr = Self::encode_bech32(ContractSpecification, &bytes)?; + + Ok(MetadataAddress { + bech32: addr, + bytes, + key_type: ContractSpecification, + }) + } + + /// Create a Scope Specification Metadata Address from a `Uuid` + /// + /// e.g. `scopespec1qj07zlu62ms5zk9g4azsdq9vnesqxcv7hd` + pub fn scope_specification( + scope_specification_uuid: Uuid, + ) -> Result { + let key_type_byte = ScopeSpecification as u8; + let bytes = [key_type_byte] + .iter() + .cloned() + .chain(Self::hex_encode_uuid(scope_specification_uuid)) + .collect::>(); + + let addr = Self::encode_bech32(ScopeSpecification, &bytes)?; + + Ok(MetadataAddress { + bech32: addr, + bytes, + key_type: ScopeSpecification, + }) + } + + /// Create a Scope Metadata Address from a `Uuid` + /// + /// e.g. `scope1qz07zlu62ms5zk9g4azsdq9vnesqg74ssc` + pub fn scope(scope_uuid: Uuid) -> Result { + let key_type_byte = Scope as u8; + let bytes = [key_type_byte] + .iter() + .cloned() + .chain(Self::hex_encode_uuid(scope_uuid)) + .collect::>(); + + let addr = Self::encode_bech32(Scope, &bytes)?; + + Ok(MetadataAddress { + bech32: addr, + bytes, + key_type: Scope, + }) + } + + /// Create a Record Metadata Address from a `Uuid` and Record name + /// + /// e.g. `record1q207zlu62ms5zk9g4azsdq9vneswsu3m8wtqu0zfqu9edf8r7l4jugz3hcl` + pub fn record(scope_uuid: Uuid, record_name: String) -> Result { + let key_type_byte = Record as u8; + let bytes = [key_type_byte] + .iter() + .cloned() + .chain(Self::hex_encode_uuid(scope_uuid)) + .chain(Self::hash_bytes(record_name)) + .collect::>(); + + let addr = Self::encode_bech32(Record, &bytes)?; + + Ok(MetadataAddress { + bech32: addr, + bytes, + key_type: Record, + }) + } + + /// Create a Record Specification Metadata Address from a Contract Specification `Uuid` and Record Specification name + /// + /// e.g. `recspec1qk07zlu62ms5zk9g4azsdq9vneswsu3m8wtqu0zfqu9edf8r7l4juadl3c0` + pub fn record_specification( + contract_specification_uuid: Uuid, + record_specification_name: String, + ) -> Result { + let key_type_byte = RecordSpecification as u8; + let bytes = [key_type_byte] + .iter() + .cloned() + .chain(Self::hex_encode_uuid(contract_specification_uuid)) + .chain(Self::hash_bytes(record_specification_name)) + .collect::>(); + + let addr = Self::encode_bech32(RecordSpecification, &bytes)?; + + Ok(MetadataAddress { + bech32: addr, + bytes, + key_type: RecordSpecification, + }) + } + + /// Create a Session Metadata Address from Scope and Session `Uuid` + /// + /// e.g. `session1qx07zlu62ms5zk9g4azsdq9vnesflctlnftwzs2c4zh52p5q4j0xqrysdme` + pub fn session(scope_uuid: Uuid, session_uuid: Uuid) -> Result { + let key_type_byte = Session as u8; + let bytes = [key_type_byte] + .iter() + .cloned() + .chain(Self::hex_encode_uuid(scope_uuid)) + .chain(Self::hex_encode_uuid(session_uuid)) + .collect::>(); + + let addr = Self::encode_bech32(Session, &bytes)?; + + Ok(MetadataAddress { + bech32: addr, + bytes, + key_type: Session, + }) + } + + pub fn to_bytes(&self) -> Vec { + self.bech32.as_bytes().to_vec() + } + + fn hex_encode_uuid(uuid: Uuid) -> Vec { + hex::decode(uuid.simple().encode_lower(&mut Uuid::encode_buffer())).unwrap() + } + + fn encode_bech32(key_type: KeyType, bytes: &[u8]) -> Result { + let hrp = + Hrp::parse(key_type.to_str()).map_err(|e| StdError::parse_err("Hrp", e.to_string()))?; + let encoded = bech32::encode::(hrp, bytes) + .map_err(|e| StdError::generic_err(e.to_string()))?; + + Ok(encoded) + } + + pub fn hash_bytes(data: String) -> Vec { + let hash = Sha256::digest(data.trim().to_lowercase().as_bytes()); + hash[0..16].to_vec() + } +} + +#[cfg(test)] +pub mod test { + use std::str::FromStr; + + use uuid::Uuid; + + use crate::metadata_address::{KeyType, MetadataAddress}; + + #[test] + pub fn new_contract_spec() { + let meta_addr = MetadataAddress::contract_specification( + Uuid::from_str("9fe17f9a-56e1-4158-a8af-450680ac9e60").unwrap(), + ) + .unwrap(); + + assert_eq!( + meta_addr, + MetadataAddress { + bech32: "contractspec1qw07zlu62ms5zk9g4azsdq9vnesqy4dtgm".to_string(), + bytes: vec![ + 3, 159, 225, 127, 154, 86, 225, 65, 88, 168, 175, 69, 6, 128, 172, 158, 96 + ], + key_type: KeyType::ContractSpecification, + } + ); + } + + #[test] + pub fn new_record() { + let meta_addr = MetadataAddress::record( + Uuid::from_str("9fe17f9a-56e1-4158-a8af-450680ac9e60").unwrap(), + "nft_record_spec_name".to_string(), + ) + .unwrap(); + + assert_eq!( + meta_addr, + MetadataAddress { + bech32: "record1q207zlu62ms5zk9g4azsdq9vneswsu3m8wtqu0zfqu9edf8r7l4jugz3hcl" + .to_string(), + bytes: vec![ + 2, 159, 225, 127, 154, 86, 225, 65, 88, 168, 175, 69, 6, 128, 172, 158, 96, + 232, 114, 59, 59, 150, 14, 60, 73, 7, 11, 150, 164, 227, 247, 235, 46 + ], + key_type: KeyType::Record, + } + ); + } + + #[test] + pub fn new_record_spec() { + let meta_addr = MetadataAddress::record_specification( + Uuid::from_str("9fe17f9a-56e1-4158-a8af-450680ac9e60").unwrap(), + "nft_record_spec_name".to_string(), + ) + .unwrap(); + + assert_eq!( + meta_addr, + MetadataAddress { + bech32: "recspec1qk07zlu62ms5zk9g4azsdq9vneswsu3m8wtqu0zfqu9edf8r7l4juadl3c0" + .to_string(), + bytes: vec![ + 5, 159, 225, 127, 154, 86, 225, 65, 88, 168, 175, 69, 6, 128, 172, 158, 96, + 232, 114, 59, 59, 150, 14, 60, 73, 7, 11, 150, 164, 227, 247, 235, 46 + ], + key_type: KeyType::RecordSpecification, + } + ); + } + + #[test] + pub fn new_scope() { + let meta_addr = + MetadataAddress::scope(Uuid::from_str("9fe17f9a-56e1-4158-a8af-450680ac9e60").unwrap()) + .unwrap(); + + assert_eq!( + meta_addr, + MetadataAddress { + bech32: "scope1qz07zlu62ms5zk9g4azsdq9vnesqg74ssc".to_string(), + bytes: vec![ + 0, 159, 225, 127, 154, 86, 225, 65, 88, 168, 175, 69, 6, 128, 172, 158, 96 + ], + key_type: KeyType::Scope, + } + ); + } + + #[test] + pub fn new_scope_spec() { + let meta_addr = MetadataAddress::scope_specification( + Uuid::from_str("9fe17f9a-56e1-4158-a8af-450680ac9e60").unwrap(), + ) + .unwrap(); + + assert_eq!( + meta_addr, + MetadataAddress { + bech32: "scopespec1qj07zlu62ms5zk9g4azsdq9vnesqxcv7hd".to_string(), + bytes: vec![ + 4, 159, 225, 127, 154, 86, 225, 65, 88, 168, 175, 69, 6, 128, 172, 158, 96 + ], + key_type: KeyType::ScopeSpecification, + } + ); + } + + #[test] + pub fn new_session() { + let meta_addr = MetadataAddress::session( + Uuid::from_str("9fe17f9a-56e1-4158-a8af-450680ac9e60").unwrap(), + Uuid::from_str("9fe17f9a-56e1-4158-a8af-450680ac9e60").unwrap(), + ) + .unwrap(); + + assert_eq!( + meta_addr, + MetadataAddress { + bech32: "session1qx07zlu62ms5zk9g4azsdq9vnesflctlnftwzs2c4zh52p5q4j0xqrysdme" + .to_string(), + bytes: vec![ + 1, 159, 225, 127, 154, 86, 225, 65, 88, 168, 175, 69, 6, 128, 172, 158, 96, + 159, 225, 127, 154, 86, 225, 65, 88, 168, 175, 69, 6, 128, 172, 158, 96 + ], + key_type: KeyType::Session, + } + ); + } +}