diff --git a/Cargo.lock b/Cargo.lock index 51312b5b..d666073d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -812,6 +812,7 @@ dependencies = [ "okp4-logic-bindings", "okp4-objectarium", "okp4-objectarium-client", + "okp4-wasm", "schemars", "serde", "thiserror", @@ -824,6 +825,7 @@ version = "4.1.0" dependencies = [ "cosmwasm-std", "form_urlencoded", + "okp4-wasm", "schemars", "serde", "serde-json-wasm 1.0.1", @@ -861,8 +863,8 @@ name = "okp4-objectarium-client" version = "4.1.0" dependencies = [ "cosmwasm-std", - "okp4-logic-bindings", "okp4-objectarium", + "okp4-wasm", "schemars", "serde", ] @@ -881,6 +883,19 @@ dependencies = [ "thiserror", ] +[[package]] +name = "okp4-wasm" +version = "4.1.0" +dependencies = [ + "cosmwasm-std", + "form_urlencoded", + "schemars", + "serde", + "serde-json-wasm 1.0.1", + "thiserror", + "url", +] + [[package]] name = "once_cell" version = "1.18.0" diff --git a/Cargo.toml b/Cargo.toml index e2b41d13..0276d7f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ okp4-objectarium = { path = "contracts/okp4-objectarium", features = [ ] } okp4-objectarium-client = { path = "packages/okp4-objectarium-client" } okp4-rdf = { path = "packages/okp4-rdf" } +okp4-wasm = { path = "packages/okp4-wasm" } rdf-types = "0.18.2" rio_api = "0.8.4" rio_turtle = "0.8.4" diff --git a/contracts/okp4-law-stone/Cargo.toml b/contracts/okp4-law-stone/Cargo.toml index f8cf038c..f3e1a173 100644 --- a/contracts/okp4-law-stone/Cargo.toml +++ b/contracts/okp4-law-stone/Cargo.toml @@ -38,6 +38,7 @@ itertools = "0.12.1" okp4-logic-bindings.workspace = true okp4-objectarium-client.workspace = true okp4-objectarium.workspace = true +okp4-wasm.workspace = true schemars.workspace = true serde.workspace = true thiserror.workspace = true diff --git a/contracts/okp4-law-stone/src/contract.rs b/contracts/okp4-law-stone/src/contract.rs index bb9b4eba..caa9a0a4 100644 --- a/contracts/okp4-law-stone/src/contract.rs +++ b/contracts/okp4-law-stone/src/contract.rs @@ -1,8 +1,8 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, StdError, StdResult, - SubMsg, WasmMsg, + to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, StdResult, SubMsg, + WasmMsg, }; use cw2::set_contract_version; use okp4_logic_bindings::LogicCustomQuery; @@ -77,7 +77,7 @@ pub mod execute { .query_wasm_contract_info(env.contract.address)? .admin { - Some(admin_addr) if admin_addr != info.sender => Err(ContractError::Unauthorized {}), + Some(admin_addr) if admin_addr != info.sender => Err(ContractError::Unauthorized), _ => Ok(()), }?; @@ -119,9 +119,9 @@ pub mod execute { } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps<'_, LogicCustomQuery>, _env: Env, msg: QueryMsg) -> StdResult { +pub fn query(deps: Deps<'_, LogicCustomQuery>, env: Env, msg: QueryMsg) -> StdResult { match msg { - QueryMsg::Ask { query } => to_json_binary(&query::ask(deps, query)?), + QueryMsg::Ask { query } => to_json_binary(&query::ask(deps, env, query)?), QueryMsg::Program => to_json_binary(&query::program(deps)?), QueryMsg::ProgramCode => to_json_binary(&query::program_code(deps)?), } @@ -133,7 +133,9 @@ pub mod query { use crate::msg::ProgramResponse; use crate::state::PROGRAM; use cosmwasm_std::QueryRequest; - use okp4_logic_bindings::AskResponse; + use okp4_logic_bindings::{Answer, AskResponse}; + + const ERR_STONE_BROKEN: &str = "system_error(broken_law_stone)"; pub fn program(deps: Deps<'_, LogicCustomQuery>) -> StdResult { let program = PROGRAM.load(deps.storage)?.into(); @@ -152,10 +154,21 @@ pub mod query { ) } - pub fn ask(deps: Deps<'_, LogicCustomQuery>, query: String) -> StdResult { + pub fn ask( + deps: Deps<'_, LogicCustomQuery>, + env: Env, + query: String, + ) -> StdResult { let stone = PROGRAM.load(deps.storage)?; if stone.broken { - return Err(StdError::generic_err("Law is broken")); + return Ok(AskResponse { + height: env.block.height, + answer: Some(Answer::from_error(format!( + "error({},root)", + ERR_STONE_BROKEN + ))), + ..Default::default() + }); } let req: QueryRequest = build_ask_query(stone.law, query)?.into(); @@ -163,11 +176,11 @@ pub mod query { } pub fn build_ask_query(program: ObjectRef, query: String) -> StdResult { - let program_uri = object_ref_to_uri(program)?.to_string(); + let program_uri = object_ref_to_uri(program)?; Ok(LogicCustomQuery::Ask { - program: String::new(), - query: ["consult('", program_uri.as_str(), "'), ", query.as_str()].join(""), + program: format!(":- consult('{}').", program_uri), + query, }) } } @@ -180,7 +193,7 @@ pub fn reply( ) -> Result { match msg.id { STORE_PROGRAM_REPLY_ID => reply::store_program_reply(deps, env, msg), - _ => Err(StdError::generic_err("Not implemented").into()), + _ => Err(ContractError::UnknownReplyID), } } @@ -188,6 +201,7 @@ pub mod reply { use super::*; use crate::helper::{ask_response_to_objects, get_reply_event_attribute, object_ref_to_uri}; use crate::state::{LawStone, DEPENDENCIES, PROGRAM}; + use cw_utils::ParseReplyError; pub fn store_program_reply( deps: DepsMut<'_, LogicCustomQuery>, @@ -198,14 +212,14 @@ pub mod reply { msg.result .into_result() - .map_err(|_| { - ContractError::InvalidReplyMsg(StdError::generic_err("no message in reply")) - }) + .map_err(ParseReplyError::SubMsgFailure) + .map_err(Into::into) .and_then(|e| { - get_reply_event_attribute(e.events, "id".to_string()).ok_or_else(|| { - ContractError::InvalidReplyMsg(StdError::generic_err( - "reply event doesn't contains object id", - )) + get_reply_event_attribute(&e.events, "id").ok_or_else(|| { + ParseReplyError::SubMsgFailure( + "reply event doesn't contains object id".to_string(), + ) + .into() }) }) .map(|obj_id| LawStone { @@ -263,19 +277,22 @@ mod tests { use crate::msg::ProgramResponse; use crate::state::{LawStone, DEPENDENCIES, PROGRAM}; use cosmwasm_std::testing::{ - mock_dependencies, mock_env, mock_info, MockQuerierCustomHandlerResult, + mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, + MockQuerierCustomHandlerResult, MockStorage, }; use cosmwasm_std::{ from_json, to_json_binary, ContractInfoResponse, ContractResult, CosmosMsg, Event, Order, - SubMsgResponse, SubMsgResult, SystemError, SystemResult, WasmQuery, + OwnedDeps, SubMsgResponse, SubMsgResult, SystemError, SystemResult, WasmQuery, }; + use cw_utils::ParseReplyError::SubMsgFailure; use okp4_logic_bindings::testing::mock::mock_dependencies_with_logic_handler; - use okp4_logic_bindings::uri::CosmwasmUri; use okp4_logic_bindings::{ Answer, AskResponse, LogicCustomQuery, Result as LogicResult, Substitution, }; use okp4_objectarium::msg::PageInfo; + use okp4_wasm::uri::CosmwasmUri; use std::collections::VecDeque; + use std::marker::PhantomData; fn custom_logic_handler_with_dependencies( dependencies: Vec, @@ -441,6 +458,7 @@ mod tests { } fn custom_logic_handler_with_query( + env: &Env, query: String, program: ObjectRef, request: &LogicCustomQuery, @@ -456,7 +474,7 @@ mod tests { query: queryy, } if *queryy == exp_query && *program == exp_program => SystemResult::Ok( to_json_binary(&AskResponse { - height: 1, + height: env.block.height, gas_used: 1000, answer: Some(Answer { has_more: false, @@ -493,8 +511,23 @@ mod tests { "okp41ffzp0xmjhwkltuxcvccl0z9tyfuu7txp5ke0tpkcjpzuq9fcj3pqrteqt3" .to_string(), }, - Some("Foo"), // Variable result - None, // Expected error + Some(AskResponse { + height: 12345, + gas_used: 1000, + answer: Some(Answer { + variables: vec!["Foo".to_string()], + results: vec![okp4_logic_bindings::Result { + substitutions: vec![Substitution { + variable: "Foo".to_string(), + expression: "bar".to_string(), + }], + ..Default::default() + }], + ..Default::default() + }), + ..Default::default() + }), + None, // Expected error ), ( true, // broken @@ -506,8 +539,18 @@ mod tests { "okp41ffzp0xmjhwkltuxcvccl0z9tyfuu7txp5ke0tpkcjpzuq9fcj3pqrteqt3" .to_string(), }, - None, // Variable result - Some(StdError::generic_err("Law is broken")), // Expected error + Some(AskResponse { + height: 12345, + answer: Some(Answer { + results: vec![okp4_logic_bindings::Result { + error: Some("error(system_error(broken_law_stone),root)".to_string()), + ..Default::default() + }], + ..Default::default() + }), + ..Default::default() + }), + None, // Expected error ), ]; @@ -517,9 +560,12 @@ mod tests { case.2.object_id.to_string(), case.2.storage_address.to_string(), )); + let env = mock_env(); + let env_4_closure = env.clone(); let mut deps = mock_dependencies_with_logic_handler(move |request| { let (query, o, s) = p.as_ref(); custom_logic_handler_with_query( + &env_4_closure, query.to_string(), ObjectRef { object_id: o.to_string(), @@ -539,7 +585,7 @@ mod tests { ) .unwrap(); - let res = query(deps.as_ref(), mock_env(), QueryMsg::Ask { query: case.1 }); + let res = query(deps.as_ref(), env, QueryMsg::Ask { query: case.1 }); match res { Ok(result) => { @@ -547,12 +593,7 @@ mod tests { assert!(case.3.is_some()); assert!(result.answer.is_some()); - assert!(result - .answer - .unwrap() - .variables - .contains(&case.3.unwrap().to_string())); - + assert_eq!(result, case.3.unwrap()); assert!(case.4.is_none(), "query doesn't return error") } Err(e) => { @@ -719,6 +760,56 @@ mod tests { } } + #[test] + fn program_reply_errors() { + let object_id = "okp41dclchlcttf2uektxyryg0c6yau63eml5q9uq03myg44ml8cxpxnqavca4s"; + let cases = vec![ + ( + Reply { + id: 404, + result: SubMsgResult::Ok(SubMsgResponse { + events: vec![Event::new("e".to_string()) + .add_attribute("id".to_string(), object_id.to_string())], + data: None, + }), + }, + Err(ContractError::UnknownReplyID), + ), + ( + Reply { + id: 1, + result: SubMsgResult::Ok(SubMsgResponse { + events: vec![Event::new("e".to_string())], + data: None, + }), + }, + Err(ContractError::ParseReplyError(SubMsgFailure( + "reply event doesn't contains object id".to_string(), + ))), + ), + ]; + + for case in cases { + let mut deps = OwnedDeps { + storage: MockStorage::default(), + api: MockApi::default(), + querier: MockQuerier::default(), + custom_query_type: PhantomData, + }; + + INSTANTIATE_CONTEXT + .save( + deps.as_mut().storage, + &"okp41dclchlcttf2uektxyryg0c6yau63eml5q9uq03myg44ml8cxpxnqavca4s".to_string(), + ) + .unwrap(); + + let response = reply(deps.as_mut(), mock_env(), case.0); + + assert_eq!(response, case.1); + } + } + #[test] fn build_source_files_query() { let result = reply::build_source_files_query(ObjectRef { @@ -754,8 +845,8 @@ mod tests { match result { Ok(LogicCustomQuery::Ask { program, query }) => { - assert_eq!(program, ""); - assert_eq!(query, "consult('cosmwasm:okp4-objectarium:okp41ffzp0xmjhwkltuxcvccl0z9tyfuu7txp5ke0tpkcjpzuq9fcj3pqrteqt3?query=%7B%22object_data%22%3A%7B%22id%22%3A%221cc6de7672c97db145a3940df2264140ea893c6688fa5ca55b73cb8b68e0574d%22%7D%7D'), test(X).") + assert_eq!(program, ":- consult('cosmwasm:okp4-objectarium:okp41ffzp0xmjhwkltuxcvccl0z9tyfuu7txp5ke0tpkcjpzuq9fcj3pqrteqt3?query=%7B%22object_data%22%3A%7B%22id%22%3A%221cc6de7672c97db145a3940df2264140ea893c6688fa5ca55b73cb8b68e0574d%22%7D%7D')."); + assert_eq!(query, "test(X).") } _ => panic!("Expected Ok(LogicCustomQuery)."), } @@ -906,18 +997,8 @@ mod tests { #[test] fn break_stone_admin() { let cases = vec![ - ( - "not-admin", - true, - false, - Some(ContractError::Unauthorized {}), - ), - ( - "not-admin", - true, - true, - Some(ContractError::Unauthorized {}), - ), + ("not-admin", true, false, Some(ContractError::Unauthorized)), + ("not-admin", true, true, Some(ContractError::Unauthorized)), ("admin", true, false, None), ("anyone", false, false, None), ]; diff --git a/contracts/okp4-law-stone/src/error.rs b/contracts/okp4-law-stone/src/error.rs index aaef5bd6..2db0a7db 100644 --- a/contracts/okp4-law-stone/src/error.rs +++ b/contracts/okp4-law-stone/src/error.rs @@ -1,6 +1,7 @@ use cosmwasm_std::StdError; use cw_utils::ParseReplyError; -use okp4_logic_bindings::error::{CosmwasmUriError, TermParseError}; +use okp4_logic_bindings::error::TermParseError; +use okp4_wasm::error::CosmwasmUriError; use thiserror::Error; #[derive(Error, Debug, PartialEq)] @@ -9,10 +10,10 @@ pub enum ContractError { Std(#[from] StdError), #[error("{0}")] - Parse(#[from] ParseReplyError), + ParseReplyError(#[from] ParseReplyError), - #[error("Invalid reply message: {0}")] - InvalidReplyMsg(StdError), + #[error("An unknown reply ID was received.")] + UnknownReplyID, #[error("Cannot parse cosmwasm uri: {0}")] ParseCosmwasmUri(CosmwasmUriError), @@ -21,7 +22,7 @@ pub enum ContractError { LogicAskResponse(LogicAskResponseError), #[error("Only the contract admin can perform this operation.")] - Unauthorized {}, + Unauthorized, } #[derive(Error, Debug, PartialEq, Eq)] diff --git a/contracts/okp4-law-stone/src/helper.rs b/contracts/okp4-law-stone/src/helper.rs index 05e2edcb..1404fd5e 100644 --- a/contracts/okp4-law-stone/src/helper.rs +++ b/contracts/okp4-law-stone/src/helper.rs @@ -2,10 +2,10 @@ use crate::error::LogicAskResponseError; use crate::ContractError; use cosmwasm_std::{Event, StdError, StdResult}; use itertools::Itertools; -use okp4_logic_bindings::error::CosmwasmUriError; -use okp4_logic_bindings::uri::CosmwasmUri; use okp4_logic_bindings::{AskResponse, TermValue}; use okp4_objectarium_client::ObjectRef; +use okp4_wasm::error::CosmwasmUriError; +use okp4_wasm::uri::CosmwasmUri; use std::any::type_name; pub fn object_ref_to_uri(object: ObjectRef) -> StdResult { @@ -14,13 +14,12 @@ pub fn object_ref_to_uri(object: ObjectRef) -> StdResult { }) } -pub fn get_reply_event_attribute(events: Vec, key: String) -> Option { - return events +pub fn get_reply_event_attribute(events: &[Event], key: &str) -> Option { + events .iter() - .flat_map(|e| e.attributes.clone()) - .filter(|a| a.key == key) - .map(|a| a.value) - .next(); + .flat_map(|e| e.attributes.iter()) + .find(|a| a.key == key) + .map(|a| a.value.clone()) } fn term_as_vec(term: TermValue) -> Result, ContractError> { diff --git a/contracts/okp4-law-stone/src/msg.rs b/contracts/okp4-law-stone/src/msg.rs index ddba3a6f..8ecd074f 100644 --- a/contracts/okp4-law-stone/src/msg.rs +++ b/contracts/okp4-law-stone/src/msg.rs @@ -30,17 +30,27 @@ pub enum ExecuteMsg { #[derive(QueryResponses)] pub enum QueryMsg { /// # Ask - /// If not broken, ask the logic module the provided query with the law program loaded. + /// Submits a Prolog query string to the `Logic` module, evaluating it against the + /// law program associated with this contract. + /// + /// If the law stone is broken the query returns a response with the error `error(system_error(broken_law_stone),root)` + /// set in the `answer` field. #[returns(AskResponse)] Ask { query: String }, /// # Program - /// If not broken, returns the law program location information. + /// Retrieves the location metadata of the law program bound to this contract. + /// + /// This includes the contract address of the `objectarium` and the program object ID, + /// where the law program's code can be accessed. #[returns(ProgramResponse)] Program, /// # ProgramCode - /// ProgramCode returns the law program code. + /// Fetches the raw code of the law program tied to this contract. + /// + /// If the law stone is broken, the query may fail if the program is no longer available in the + /// `Objectarium`. #[returns(Binary)] ProgramCode, } diff --git a/contracts/okp4-objectarium/src/contract.rs b/contracts/okp4-objectarium/src/contract.rs index 9a6a4d91..52ce551f 100644 --- a/contracts/okp4-objectarium/src/contract.rs +++ b/contracts/okp4-objectarium/src/contract.rs @@ -161,7 +161,7 @@ pub mod execute { Ok(Response::new() .add_attribute("action", "store_object") - .add_attribute("id", object.id.clone())) + .add_attribute("id", object.id.to_string())) } pub fn pin_object( @@ -274,8 +274,8 @@ pub mod execute { } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> Result { - Ok(match msg { +pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> StdResult { + match msg { QueryMsg::Bucket {} => to_json_binary(&query::bucket(deps)?), QueryMsg::Object { id } => to_json_binary(&query::object(deps, id)?), QueryMsg::ObjectData { id } => to_json_binary(&query::data(deps, id)?), @@ -287,7 +287,7 @@ pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> Result { to_json_binary(&query::object_pins(deps, id, after, first)?) } - }?) + } } pub mod query { @@ -298,9 +298,9 @@ pub mod query { BucketResponse, Cursor, ObjectPinsResponse, ObjectResponse, ObjectsResponse, PageInfo, }; use crate::pagination::{PaginationHandler, QueryPage}; - use cosmwasm_std::{Addr, Order}; + use cosmwasm_std::{Addr, Order, StdError}; - pub fn bucket(deps: Deps<'_>) -> Result { + pub fn bucket(deps: Deps<'_>) -> StdResult { let bucket = BUCKET.load(deps.storage)?; Ok(BucketResponse { @@ -311,18 +311,21 @@ pub mod query { }) } - pub fn object(deps: Deps<'_>, object_id: ObjectId) -> Result { + pub fn object(deps: Deps<'_>, object_id: ObjectId) -> StdResult { let id: Hash = object_id.try_into()?; let object = objects().load(deps.storage, id)?; Ok((&object).into()) } - pub fn data(deps: Deps<'_>, object_id: ObjectId) -> Result { + pub fn data(deps: Deps<'_>, object_id: ObjectId) -> StdResult { let id: Hash = object_id.try_into()?; let compression = objects().load(deps.storage, id.clone())?.compression; let data = DATA.load(deps.storage, id)?; - let decompressed_data = compression.decompress(&data)?; - Ok(Binary::from(decompressed_data)) + + compression + .decompress(&data) + .map_err(|e| StdError::serialize_err(format!("{:?}", compression), e)) + .map(Binary::from) } pub fn fetch_objects( @@ -416,6 +419,8 @@ impl From for crypto::HashAlgorithm { #[cfg(test)] mod tests { use super::*; + use crate::compress; + use crate::crypto::Hash; use crate::error::BucketError; use crate::msg::{ BucketConfig, BucketLimitsBuilder, BucketResponse, CompressionAlgorithm, HashAlgorithm, @@ -424,7 +429,7 @@ mod tests { use base64::{engine::general_purpose, Engine as _}; use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; use cosmwasm_std::StdError::NotFound; - use cosmwasm_std::{from_json, Attribute, Order, StdError, Uint128}; + use cosmwasm_std::{from_json, Addr, Attribute, Order, StdError, Uint128}; use std::any::type_name; fn decode_hex(hex: &str) -> Vec { @@ -1123,7 +1128,7 @@ mod tests { .err() .unwrap() { - ContractError::Std(NotFound { .. }) => (), + NotFound { .. } => (), _ => panic!("assertion failed"), } @@ -1213,7 +1218,7 @@ mod tests { .err() .unwrap() { - ContractError::Std(NotFound { .. }) => (), + NotFound { .. } => (), _ => panic!("assertion failed"), } @@ -1232,6 +1237,41 @@ mod tests { } } + #[test] + fn object_data_error() { + let mut deps = mock_dependencies(); + let id: Hash = vec![1, 2, 3].into(); + let data = &vec![255, 255, 0]; + + let object = &Object { + id: id.clone(), + owner: Addr::unchecked("john"), + size: 42u8.into(), + pin_count: Uint128::one(), + compression: compress::CompressionAlgorithm::Lzma, + compressed_size: Uint128::from(data.len() as u128), + }; + + objects() + .save(deps.as_mut().storage, object.id.clone(), object) + .expect("no error when storing object"); + let data_path = DATA.key(id.clone()); + data_path + .save(deps.as_mut().storage, &data) + .expect("no error when storing data"); + + let msg = QueryMsg::ObjectData { id: id.to_string() }; + + let result = query(deps.as_ref(), mock_env(), msg); + assert_eq!( + result, + Err(StdError::serialize_err( + "Lzma", + "lzma error: LZMA header invalid properties: 255 must be < 225" + )) + ); + } + #[test] fn pin_object() { struct TC { @@ -2093,9 +2133,9 @@ mod tests { after: None, first: None, }, - ContractError::Std(StdError::not_found(not_found_object_info::( + StdError::not_found(not_found_object_info::( "abafa4428bdc8c34dae28bbc17303a62175f274edf59757b3e9898215a428a56", - ))), + )), ), ( QueryMsg::ObjectPins { @@ -2103,10 +2143,10 @@ mod tests { after: None, first: None, }, - ContractError::Std(StdError::parse_err( + StdError::parse_err( type_name::>(), "invalid Base16 encoding".to_string(), - )), + ), ), ]; diff --git a/contracts/okp4-objectarium/src/crypto.rs b/contracts/okp4-objectarium/src/crypto.rs index edcc9c20..a0c19b92 100644 --- a/contracts/okp4-objectarium/src/crypto.rs +++ b/contracts/okp4-objectarium/src/crypto.rs @@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize}; use sha2; use sha2::Digest; use std::any::type_name; +use std::fmt; /// HashAlgorithm is the type of the hash algorithm. pub enum HashAlgorithm { @@ -34,7 +35,7 @@ impl HashAlgorithm { } } -/// Hash represent a Object hash as binary value. +/// Hash represent a Object hash as binary value. #[derive( Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, JsonSchema, )] @@ -83,9 +84,11 @@ impl TryFrom for Hash { } } -impl From for String { - fn from(hash: Hash) -> Self { - base16ct::lower::encode_string(hash.0.as_slice()) +// Allows for a (user-friendly) string representation of Hash as a lower Base16 (hex) encoding. +impl fmt::Display for Hash { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let hex_string = base16ct::lower::encode_string(&self.0); + write!(f, "{}", hex_string) } } diff --git a/contracts/okp4-objectarium/src/state.rs b/contracts/okp4-objectarium/src/state.rs index 7fb4ce44..7b1cf41b 100644 --- a/contracts/okp4-objectarium/src/state.rs +++ b/contracts/okp4-objectarium/src/state.rs @@ -275,7 +275,7 @@ pub struct Object { impl From<&Object> for ObjectResponse { fn from(object: &Object) -> Self { ObjectResponse { - id: object.id.clone().into(), + id: object.id.to_string(), size: object.size, owner: object.owner.clone().into(), is_pinned: object.pin_count > Uint128::zero(), diff --git a/docs/okp4-cognitarium.md b/docs/okp4-cognitarium.md index 6802877a..8c379c55 100644 --- a/docs/okp4-cognitarium.md +++ b/docs/okp4-cognitarium.md @@ -876,4 +876,4 @@ Represents a condition in a [WhereClause]. --- -_Rendered by [Fadroma](https://fadroma.tech) ([@fadroma/schema 1.1.0](https://www.npmjs.com/package/@fadroma/schema)) from `okp4-cognitarium.json` (`a04a40216c76a302`)_ +_Rendered by [Fadroma](https://fadroma.tech) ([@fadroma/schema 1.1.0](https://www.npmjs.com/package/@fadroma/schema)) from `okp4-cognitarium.json` (`6f72bb04e2230e19`)_ diff --git a/docs/okp4-dataverse.md b/docs/okp4-dataverse.md index 5baedc66..c5c197ce 100644 --- a/docs/okp4-dataverse.md +++ b/docs/okp4-dataverse.md @@ -238,5 +238,5 @@ let b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ``` --- -*Rendered by [Fadroma](https://fadroma.tech) ([@fadroma/schema 1.1.0](https://www.npmjs.com/package/@fadroma/schema)) from `okp4-dataverse.json` (`6c4e48ca82d04a6a`)* +*Rendered by [Fadroma](https://fadroma.tech) ([@fadroma/schema 1.1.0](https://www.npmjs.com/package/@fadroma/schema)) from `okp4-dataverse.json` (`b906ba32d6e0720c`)* ```` diff --git a/docs/okp4-law-stone.md b/docs/okp4-law-stone.md index 0e38fd49..10d46c10 100644 --- a/docs/okp4-law-stone.md +++ b/docs/okp4-law-stone.md @@ -41,7 +41,9 @@ Query messages ### QueryMsg::Ask -If not broken, ask the logic module the provided query with the law program loaded. +Submits a Prolog query string to the `Logic` module, evaluating it against the law program associated with this contract. + +If the law stone is broken the query returns a response with the error `error(system_error(broken_law_stone),root)` set in the `answer` field. | parameter | description | | ----------- | -------------------------- | @@ -50,7 +52,9 @@ If not broken, ask the logic module the provided query with the law program load ### QueryMsg::Program -If not broken, returns the law program location information. +Retrieves the location metadata of the law program bound to this contract. + +This includes the contract address of the `objectarium` and the program object ID, where the law program's code can be accessed. | literal | | ----------- | @@ -58,7 +62,9 @@ If not broken, returns the law program location information. ### QueryMsg::ProgramCode -ProgramCode returns the law program code. +Fetches the raw code of the law program tied to this contract. + +If the law stone is broken, the query may fail if the program is no longer available in the `Objectarium`. | literal | | ---------------- | @@ -128,4 +134,4 @@ A string containing Base64-encoded data. --- -_Rendered by [Fadroma](https://fadroma.tech) ([@fadroma/schema 1.1.0](https://www.npmjs.com/package/@fadroma/schema)) from `okp4-law-stone.json` (`a95a2760652729c5`)_ +_Rendered by [Fadroma](https://fadroma.tech) ([@fadroma/schema 1.1.0](https://www.npmjs.com/package/@fadroma/schema)) from `okp4-law-stone.json` (`f92ef76322c09083`)_ diff --git a/docs/okp4-objectarium.md b/docs/okp4-objectarium.md index d8310590..83fc5971 100644 --- a/docs/okp4-objectarium.md +++ b/docs/okp4-objectarium.md @@ -511,4 +511,4 @@ A string containing a 128-bit integer in decimal representation. --- -_Rendered by [Fadroma](https://fadroma.tech) ([@fadroma/schema 1.1.0](https://www.npmjs.com/package/@fadroma/schema)) from `okp4-objectarium.json` (`483acdc660c72c5f`)_ +_Rendered by [Fadroma](https://fadroma.tech) ([@fadroma/schema 1.1.0](https://www.npmjs.com/package/@fadroma/schema)) from `okp4-objectarium.json` (`b4d8508c7abc145b`)_ diff --git a/packages/okp4-logic-bindings/Cargo.toml b/packages/okp4-logic-bindings/Cargo.toml index df1d5bf6..8afcf428 100644 --- a/packages/okp4-logic-bindings/Cargo.toml +++ b/packages/okp4-logic-bindings/Cargo.toml @@ -7,6 +7,7 @@ version = "4.1.0" [dependencies] cosmwasm-std.workspace = true form_urlencoded = "1.2.1" +okp4-wasm.workspace = true schemars.workspace = true serde-json-wasm.workspace = true serde.workspace = true diff --git a/packages/okp4-logic-bindings/src/error.rs b/packages/okp4-logic-bindings/src/error.rs index 85cfffb0..0160b590 100644 --- a/packages/okp4-logic-bindings/src/error.rs +++ b/packages/okp4-logic-bindings/src/error.rs @@ -1,21 +1,5 @@ use std::string::FromUtf8Error; use thiserror::Error; -use url::ParseError; - -#[derive(Error, Debug, PartialEq, Eq)] -pub enum CosmwasmUriError { - #[error("{0}")] - ParseURI(#[from] ParseError), - - #[error("{0}")] - ParseQuery(String), - - #[error("{0}")] - SerializeQuery(String), - - #[error("Malformed URI: {0}")] - Malformed(String), -} #[derive(Error, Debug, PartialEq, Eq)] pub enum TermParseError { diff --git a/packages/okp4-logic-bindings/src/lib.rs b/packages/okp4-logic-bindings/src/lib.rs index 28117b26..1f58e6bc 100644 --- a/packages/okp4-logic-bindings/src/lib.rs +++ b/packages/okp4-logic-bindings/src/lib.rs @@ -1,7 +1,6 @@ pub mod error; mod query; mod term_parser; -pub mod uri; pub use query::{Answer, AskResponse, LogicCustomQuery, Result, Substitution}; pub use term_parser::TermValue; diff --git a/packages/okp4-logic-bindings/src/query.rs b/packages/okp4-logic-bindings/src/query.rs index 5a3c1f86..f4ea9d4a 100644 --- a/packages/okp4-logic-bindings/src/query.rs +++ b/packages/okp4-logic-bindings/src/query.rs @@ -29,6 +29,20 @@ pub struct Answer { pub results: Vec, } +impl Answer { + /// Create a new Answer with an error message. + pub fn from_error(error: String) -> Self { + Self { + has_more: false, + variables: vec![], + results: vec![Result { + error: Some(error), + substitutions: vec![], + }], + } + } +} + #[derive(Serialize, Deserialize, Default, Clone, PartialEq, Eq, JsonSchema, Debug)] #[serde(rename_all = "snake_case")] pub struct Result { diff --git a/packages/okp4-objectarium-client/Cargo.toml b/packages/okp4-objectarium-client/Cargo.toml index e556e295..16259041 100644 --- a/packages/okp4-objectarium-client/Cargo.toml +++ b/packages/okp4-objectarium-client/Cargo.toml @@ -6,7 +6,7 @@ version = "4.1.0" [dependencies] cosmwasm-std.workspace = true -okp4-logic-bindings.workspace = true okp4-objectarium.workspace = true +okp4-wasm.workspace = true schemars.workspace = true serde.workspace = true diff --git a/packages/okp4-objectarium-client/src/object.rs b/packages/okp4-objectarium-client/src/object.rs index 4ca87d30..73e1128e 100644 --- a/packages/okp4-objectarium-client/src/object.rs +++ b/packages/okp4-objectarium-client/src/object.rs @@ -1,8 +1,8 @@ use cosmwasm_std::{to_json_binary, Coin, StdResult, WasmMsg}; -use okp4_logic_bindings::error::CosmwasmUriError; -use okp4_logic_bindings::uri::CosmwasmUri; use okp4_objectarium::msg::QueryMsg::ObjectData; use okp4_objectarium::msg::{ExecuteMsg, QueryMsg}; +use okp4_wasm::error::CosmwasmUriError; +use okp4_wasm::uri::CosmwasmUri; use serde::{Deserialize, Serialize}; const CONTRACT_NAME: &str = "okp4-objectarium"; diff --git a/packages/okp4-wasm/Cargo.toml b/packages/okp4-wasm/Cargo.toml new file mode 100644 index 00000000..938178cc --- /dev/null +++ b/packages/okp4-wasm/Cargo.toml @@ -0,0 +1,14 @@ +[package] +authors = ["OKP4"] +edition = "2021" +name = "okp4-wasm" +version = "4.1.0" + +[dependencies] +cosmwasm-std.workspace = true +form_urlencoded = "1.2.1" +schemars.workspace = true +serde-json-wasm.workspace = true +serde.workspace = true +thiserror.workspace = true +url = "2.5.0" diff --git a/packages/okp4-wasm/Makefile.toml b/packages/okp4-wasm/Makefile.toml new file mode 100644 index 00000000..2d9b5d19 --- /dev/null +++ b/packages/okp4-wasm/Makefile.toml @@ -0,0 +1 @@ +[tasks.schema] diff --git a/packages/okp4-wasm/README.md b/packages/okp4-wasm/README.md new file mode 100644 index 00000000..7cb11c7a --- /dev/null +++ b/packages/okp4-wasm/README.md @@ -0,0 +1,3 @@ +# RDF + +Package that holds useful components to manage with `RDF` data, typically reading / writing. diff --git a/packages/okp4-wasm/src/error.rs b/packages/okp4-wasm/src/error.rs new file mode 100644 index 00000000..01cce3a9 --- /dev/null +++ b/packages/okp4-wasm/src/error.rs @@ -0,0 +1,17 @@ +use thiserror::Error; +use url::ParseError; + +#[derive(Error, Debug, PartialEq, Eq)] +pub enum CosmwasmUriError { + #[error("{0}")] + ParseURI(#[from] ParseError), + + #[error("{0}")] + ParseQuery(String), + + #[error("{0}")] + SerializeQuery(String), + + #[error("Malformed URI: {0}")] + Malformed(String), +} diff --git a/packages/okp4-wasm/src/lib.rs b/packages/okp4-wasm/src/lib.rs new file mode 100644 index 00000000..e533d91d --- /dev/null +++ b/packages/okp4-wasm/src/lib.rs @@ -0,0 +1,2 @@ +pub mod error; +pub mod uri; diff --git a/packages/okp4-logic-bindings/src/uri.rs b/packages/okp4-wasm/src/uri.rs similarity index 93% rename from packages/okp4-logic-bindings/src/uri.rs rename to packages/okp4-wasm/src/uri.rs index 694308ec..aa507bb0 100644 --- a/packages/okp4-logic-bindings/src/uri.rs +++ b/packages/okp4-wasm/src/uri.rs @@ -1,13 +1,18 @@ use crate::error::CosmwasmUriError; use serde::{de, ser}; use std::collections::HashMap; +use std::fmt::Display; use url::Url; const COSMWASM_SCHEME: &str = "cosmwasm"; const COSMWASM_QUERY_PARAM: &str = "query"; -/// Represents a file system URI used to load files from the logic module dedicated to the resolution -/// of data coming from a CosmWasm smart contract query. The URI having the form: +/// A CosmWasm URI identifies a resource on a blockchain by referencing a specific instantiated +/// smart contract. It includes the contract's address and uses query parameters to encode the message +/// intended for the contract. The resource identified by the URI is the response provided by the +/// smart contract following this query. +/// +/// Its general form is as follows: /// /// `cosmwasm:{contract_name}:{contract_address}?query={contract_query}` /// @@ -96,10 +101,10 @@ impl TryFrom for CosmwasmUri { } } -impl ToString for CosmwasmUri { - fn to_string(&self) -> String { +impl Display for CosmwasmUri { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let encoded_query = self.clone().encode_query(); - match self.contract_name.clone() { + let str = match self.contract_name.clone() { Some(name) => [ COSMWASM_SCHEME, ":", @@ -118,7 +123,8 @@ impl ToString for CosmwasmUri { encoded_query.as_str(), ] .join(""), - } + }; + write!(f, "{}", str) } }