Skip to content

Commit

Permalink
add MetadataAddress util for encode/decode
Browse files Browse the repository at this point in the history
  • Loading branch information
kwtalley committed Sep 26, 2024
1 parent 0d5efd2 commit 8254897
Show file tree
Hide file tree
Showing 3 changed files with 298 additions and 2 deletions.
7 changes: 6 additions & 1 deletion packages/provwasm-std/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,20 @@ 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 }
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 }
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 }
2 changes: 1 addition & 1 deletion packages/provwasm-std/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
291 changes: 291 additions & 0 deletions packages/provwasm-std/src/metadata_address.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
use crate::metadata_address::KeyType::{
ContractSpecification, Record, RecordSpecification, Scope, ScopeSpecification, Session,
};
use bech32::primitives::hrp;
use bech32::{Bech32, Hrp};
use cosmwasm_std::StdError;
use sha2::{Digest, Sha256};
use thiserror::Error;
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",
}
}
}

#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum MetadataAddressError {
#[error("Failed to parse HRP: {0}")]
ParseError(#[from] hrp::Error),

#[error("Failed to encode Bech32: {0}")]
EncodeError(#[from] bech32::EncodeError),
}

#[derive(Clone, Debug, PartialEq)]
pub struct MetadataAddress {
pub bech32: String,
pub bytes: Vec<u8>,
pub key_type: KeyType,
}

impl MetadataAddress {
pub fn contract_specification(
contract_specification_uuid: Uuid,
) -> Result<MetadataAddress, StdError> {
let key_type_byte = ContractSpecification as u8;
let bytes = [key_type_byte]
.iter()
.cloned()
.chain(Self::hex_encode_uuid(contract_specification_uuid))
.collect::<Vec<u8>>();

let addr = Self::encode_bech32(ContractSpecification, &bytes).unwrap();

Ok(MetadataAddress {
bech32: addr,
bytes,
key_type: ContractSpecification,
})
}

pub fn scope_specification(
scope_specification_uuid: Uuid,
) -> Result<MetadataAddress, StdError> {
let key_type_byte = ScopeSpecification as u8;
let bytes = [key_type_byte]
.iter()
.cloned()
.chain(Self::hex_encode_uuid(scope_specification_uuid))
.collect::<Vec<u8>>();

let addr = Self::encode_bech32(ScopeSpecification, &bytes).unwrap();

Ok(MetadataAddress {
bech32: addr,
bytes,
key_type: ScopeSpecification,
})
}

pub fn scope(scope_uuid: Uuid) -> Result<MetadataAddress, StdError> {
let key_type_byte = Scope as u8;
let bytes = [key_type_byte]
.iter()
.cloned()
.chain(Self::hex_encode_uuid(scope_uuid))
.collect::<Vec<u8>>();

let addr = Self::encode_bech32(Scope, &bytes).unwrap();

Ok(MetadataAddress {
bech32: addr,
bytes,
key_type: Scope,
})
}

pub fn record(scope_uuid: Uuid, record_name: String) -> Result<MetadataAddress, StdError> {
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::<Vec<u8>>();

let addr = Self::encode_bech32(Record, &bytes).unwrap();

Ok(MetadataAddress {
bech32: addr,
bytes,
key_type: Record,
})
}

pub fn record_specification(
contract_specification_uuid: Uuid,
record_specification_name: String,
) -> Result<MetadataAddress, StdError> {
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::<Vec<u8>>();

let addr = Self::encode_bech32(RecordSpecification, &bytes).unwrap();

Ok(MetadataAddress {
bech32: addr,
bytes,
key_type: RecordSpecification,
})
}

pub fn session(scope_uuid: Uuid, session_uuid: Uuid) -> Result<MetadataAddress, StdError> {
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::<Vec<u8>>();

let addr = Self::encode_bech32(Session, &bytes).unwrap();

Ok(MetadataAddress {
bech32: addr,
bytes,
key_type: Session,
})
}

pub fn to_bytes(&self) -> Vec<u8> {
self.bech32.as_bytes().to_vec()
}

fn hex_encode_uuid(uuid: Uuid) -> Vec<u8> {
hex::decode(uuid.simple().encode_lower(&mut Uuid::encode_buffer())).unwrap()
}

fn encode_bech32(key_type: KeyType, bytes: &[u8]) -> Result<String, MetadataAddressError> {
let hrp = Hrp::parse(key_type.to_str()).map_err(MetadataAddressError::ParseError)?;
let encoded =
bech32::encode::<Bech32>(hrp, bytes).map_err(MetadataAddressError::EncodeError)?;
Ok(encoded)
}

pub fn hash_bytes(data: String) -> Vec<u8> {
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_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_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_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_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,
}
);
}
}

0 comments on commit 8254897

Please sign in to comment.