diff --git a/src/deferred_data/src/app.rs b/src/deferred_data/src/app.rs index 75d783a..9edbc48 100644 --- a/src/deferred_data/src/app.rs +++ b/src/deferred_data/src/app.rs @@ -172,12 +172,7 @@ impl DeferredData { ))?; // get caller access level - let access_level = if contract - .agency - .as_ref() - .map(|agency| agency.owner == caller()) - .unwrap_or_default() - { + let access_level = if contract.agency == caller() { RestrictionLevel::Agent } else if let Some(signature) = signature { Inspect::inspect_signature(&contract.id, signature.signature, signature.message)? @@ -202,12 +197,7 @@ impl DeferredData { let mut redacted_properties = Vec::with_capacity(contract.restricted_properties.len()); // get caller access level - let access_level = if contract - .agency - .as_ref() - .map(|agency| agency.owner == caller) - .unwrap_or_default() - { + let access_level = if contract.agency == caller { Some(RestrictionLevel::Agent) } else if let Some(signature) = signature { Inspect::inspect_signature(&contract.id, signature.signature, signature.message).ok() @@ -291,7 +281,7 @@ mod test { init(); let contract = with_mock_contract(1, 100, |contract| { - contract.agency.as_mut().unwrap().owner = caller(); + contract.agency = caller(); }); DeferredData::create_contract(contract.clone()).expect("Failed to create contract"); @@ -333,7 +323,7 @@ mod test { init(); let contract = with_mock_contract(1, 100, |contract| { - contract.agency.as_mut().unwrap().owner = caller(); + contract.agency = caller(); }); DeferredData::create_contract(contract.clone()).expect("Failed to create contract"); diff --git a/src/deferred_data/src/app/test_utils.rs b/src/deferred_data/src/app/test_utils.rs index 9e1272b..37682f7 100644 --- a/src/deferred_data/src/app/test_utils.rs +++ b/src/deferred_data/src/app/test_utils.rs @@ -29,7 +29,7 @@ pub fn mock_contract(id: u64, installments: u64) -> Contract { }, )], documents: vec![], - agency: Some(mock_agency()), + agency: mock_agency().owner, expiration: "2078-01-01".to_string(), closed: false, } diff --git a/src/deferred_data/src/http.rs b/src/deferred_data/src/http.rs index 0f745c3..297e752 100644 --- a/src/deferred_data/src/http.rs +++ b/src/deferred_data/src/http.rs @@ -252,9 +252,8 @@ mod test { address: H160::from_hex_str("0x253553366da8546fc250f225fe3d25d0c782303b").unwrap(), quota: 100, }]; - contract.agency.as_mut().unwrap().owner = - Principal::from_text("v5vof-zqaaa-aaaal-ai5cq-cai") - .expect("Failed to create principal"); + contract.agency = Principal::from_text("v5vof-zqaaa-aaaal-ai5cq-cai") + .expect("Failed to create principal"); // insert properties contract.properties = vec![ diff --git a/src/deferred_data/src/http/contract_filter.rs b/src/deferred_data/src/http/contract_filter.rs index 2000c0f..b90df5f 100644 --- a/src/deferred_data/src/http/contract_filter.rs +++ b/src/deferred_data/src/http/contract_filter.rs @@ -72,19 +72,13 @@ impl ContractFilter { .properties .iter() .find(|(k, _)| k == name) - .map_or(false, |(_, v)| { - v.to_string().to_lowercase().contains(&value.to_lowercase()) - }), + .is_some_and(|(_, v)| v.to_string().to_lowercase().contains(&value.to_lowercase())), ContractFilter::Seller(addr) => contract .sellers .iter() .any(|seller| seller.address == *addr), ContractFilter::Buyer(addr) => contract.buyers.iter().any(|buyer| buyer == addr), - ContractFilter::Agent(agent) => contract - .agency - .as_ref() - .map(|agency| agency.owner == *agent) - .unwrap_or_default(), + ContractFilter::Agent(agent) => contract.agency == *agent, ContractFilter::MinPrice(min_price) => contract.value >= *min_price, ContractFilter::MaxPrice(max_price) => contract.value <= *max_price, ContractFilter::Position { diff --git a/src/deferred_minter/src/app.rs b/src/deferred_minter/src/app.rs index fbf787e..364bae7 100644 --- a/src/deferred_minter/src/app.rs +++ b/src/deferred_minter/src/app.rs @@ -162,11 +162,7 @@ impl DeferredMinter { if RolesManager::is_agent(caller()) { log::debug!("caller is an agent"); let contract = Self::deferred_data().get_contract(&contract_id).await?; - if contract - .agency - .map(|agency| agency.owner != caller()) - .unwrap_or(true) - { + if contract.agency != caller() { log::debug!("caller is not the agency for the contract"); ic_cdk::trap("Unauthorized"); } @@ -310,9 +306,6 @@ impl DeferredMinter { /// Create a contract from the registration data fn contract_from_registration(contract_id: ID, data: ContractRegistration) -> Contract { - // get agency from caller - let agency = Agents::get_agency_by_wallet(caller()); - Contract { id: contract_id, r#type: data.r#type, @@ -325,7 +318,7 @@ impl DeferredMinter { properties: data.properties, restricted_properties: data.restricted_properties, documents: vec![], - agency, + agency: caller(), expiration: data.expiration, closed: false, } diff --git a/src/deferred_minter/src/app/data_client.rs b/src/deferred_minter/src/app/data_client.rs index f2e16f5..72c4417 100644 --- a/src/deferred_minter/src/app/data_client.rs +++ b/src/deferred_minter/src/app/data_client.rs @@ -1,6 +1,6 @@ use candid::Principal; use did::deferred::{ - Agency, Contract, ContractError, DeferredDataResult, DeferredMinterError, DeferredMinterResult, + Contract, ContractError, DeferredDataResult, DeferredMinterError, DeferredMinterResult, GenericValue, Seller, }; use did::{H160, ID}; @@ -41,24 +41,7 @@ impl DeferredDataClient { )], restricted_properties: vec![], documents: vec![], - agency: Some(Agency { - name: "Dummy Real estate".to_string(), - lat: None, - lng: None, - address: "Via Delle Botteghe Scure".to_string(), - city: "Rome".to_string(), - region: "Lazio".to_string(), - zip_code: "00100".to_string(), - country: "Italy".to_string(), - continent: did::deferred::Continent::Europe, - email: "email".to_string(), - website: "website".to_string(), - mobile: "mobile".to_string(), - vat: "vat".to_string(), - agent: "agent".to_string(), - logo: None, - owner: caller(), - }), + agency: caller(), expiration: "2078-01-01".to_string(), closed: false, }); diff --git a/src/deferred_minter/src/app/test_utils.rs b/src/deferred_minter/src/app/test_utils.rs index 9fffc61..0b9e5bb 100644 --- a/src/deferred_minter/src/app/test_utils.rs +++ b/src/deferred_minter/src/app/test_utils.rs @@ -27,7 +27,7 @@ pub fn mock_contract(id: u64, installments: u64) -> Contract { }, )], documents: vec![], - agency: Some(mock_agency()), + agency: mock_agency().owner, expiration: "2078-01-01".to_string(), closed: false, } diff --git a/src/deferred_minter/src/http/agents.rs b/src/deferred_minter/src/http/agents.rs index 8531802..8bb46a1 100644 --- a/src/deferred_minter/src/http/agents.rs +++ b/src/deferred_minter/src/http/agents.rs @@ -54,9 +54,7 @@ impl AgencyFilter { match self { AgencyFilter::Always => true, AgencyFilter::HasProperty { name, value } => Self::agency_property(name, agency) - .map_or(false, |v| { - v.to_string().to_lowercase().contains(&value.to_lowercase()) - }), + .is_some_and(|v| v.to_string().to_lowercase().contains(&value.to_lowercase())), AgencyFilter::Position { latitude, longitude, diff --git a/src/did/src/deferred.rs b/src/did/src/deferred.rs index 395b35e..f9b894a 100644 --- a/src/did/src/deferred.rs +++ b/src/did/src/deferred.rs @@ -1,16 +1,19 @@ //! Types associated to the "Deferred" canister +mod agency; mod contract; mod data; mod minter; +mod real_estate; pub type DeferredMinterResult = Result; pub type DeferredDataResult = Result; +pub use self::agency::{Agency, AgencyId, Continent}; pub use self::contract::{ - Agency, Continent, Contract, ContractDocument, ContractDocumentData, ContractDocuments, - ContractProperties, ContractRegistration, ContractType, GenericValue, - RestrictedContractProperties, RestrictedProperty, RestrictionLevel, Seller, ID, + Contract, ContractDocument, ContractDocumentData, ContractDocuments, ContractProperties, + ContractRegistration, ContractType, GenericValue, RestrictedContractProperties, + RestrictedProperty, RestrictionLevel, Seller, ID, }; pub use self::data::{ ConfigurationError as DataConfigurationError, ContractError as DataContractError, @@ -20,94 +23,3 @@ pub use self::minter::{ CloseContractError, ConfigurationError, ContractError, DeferredMinterError, DeferredMinterInitData, EcdsaError, EcdsaKey, Role, Roles, }; - -#[cfg(test)] -mod test { - - use candid::{Decode, Encode, Principal}; - use ic_stable_structures::Storable as _; - use pretty_assertions::assert_eq; - - use super::*; - use crate::{H160, ID}; - - #[test] - fn test_should_encode_contract() { - let contract = Contract { - id: ID::from(1_u64), - r#type: ContractType::Sell, - sellers: vec![ - Seller { - address: H160::from_hex_str("0xE46A267b65Ed8CBAeBA9AdC3171063179b642E7A") - .unwrap(), - quota: 51, - }, - Seller { - address: H160::from_hex_str("0xE46A267b65Ed8CBAeBA9AdC3171063179b642E7A") - .unwrap(), - quota: 49, - }, - ], - buyers: vec![ - H160::from_hex_str("0xE46A267b65Ed8CBAeBA9AdC3171063179b642E7A").unwrap(), - H160::from_hex_str("0xE46A267b65Ed8CBAeBA9AdC3171063179b642E7A").unwrap(), - ], - installments: 2, - value: 250_000, - deposit: 50_000, - currency: "EUR".to_string(), - documents: vec![], - properties: vec![( - "Rome".to_string(), - GenericValue::TextContent("Rome".to_string()), - )], - restricted_properties: vec![( - "Secret".to_string(), - RestrictedProperty { - access_list: vec![RestrictionLevel::Agent], - value: GenericValue::TextContent("Secret".to_string()), - }, - )], - agency: Some(Agency { - name: "Agency".to_string(), - address: "Address".to_string(), - city: "City".to_string(), - region: "Region".to_string(), - zip_code: "Zip".to_string(), - country: "Country".to_string(), - continent: Continent::Europe, - lat: None, - lng: None, - email: "Email".to_string(), - website: "Website".to_string(), - mobile: "Mobile".to_string(), - vat: "VAT".to_string(), - agent: "Agent".to_string(), - logo: None, - owner: Principal::anonymous(), - }), - expiration: "2040-01-01".to_string(), - closed: false, - }; - let data = Encode!(&contract).unwrap(); - let decoded_contract = Decode!(&data, Contract).unwrap(); - - assert_eq!(contract.id, decoded_contract.id); - assert_eq!(contract.sellers, decoded_contract.sellers); - assert_eq!(contract.buyers, decoded_contract.buyers); - assert_eq!(contract.properties, decoded_contract.properties); - assert_eq!(contract.value, decoded_contract.value); - assert_eq!(contract.currency, decoded_contract.currency); - assert_eq!(contract.installments, decoded_contract.installments); - assert_eq!(contract.agency, decoded_contract.agency); - } - - #[test] - fn test_should_encode_role() { - let role: Roles = vec![Role::Agent, Role::Custodian].into(); - - let data = role.to_bytes(); - let decoded_role = Roles::from_bytes(data); - assert_eq!(role, decoded_role); - } -} diff --git a/src/did/src/deferred/contract/agency.rs b/src/did/src/deferred/agency.rs similarity index 70% rename from src/did/src/deferred/contract/agency.rs rename to src/did/src/deferred/agency.rs index 1c6c646..6d0c5d8 100644 --- a/src/did/src/deferred/contract/agency.rs +++ b/src/did/src/deferred/agency.rs @@ -5,6 +5,9 @@ use ic_stable_structures::storable::Bound; use ic_stable_structures::Storable; use serde::{Deserialize, Serialize}; +/// Unique identifier for an agency +pub type AgencyId = Principal; + /// A sell contract for a building #[derive(Clone, Debug, CandidType, Deserialize, Serialize, PartialEq)] pub struct Agency { @@ -86,3 +89,37 @@ impl Storable for Agency { Decode!(&bytes, Self).unwrap() } } + +#[cfg(test)] +mod test { + + use pretty_assertions::assert_eq; + + use super::*; + + #[test] + fn test_should_encode_agency() { + let agency = Agency { + name: "Agency".to_string(), + address: "Address".to_string(), + city: "City".to_string(), + region: "Region".to_string(), + zip_code: "Zip".to_string(), + country: "Country".to_string(), + continent: Continent::Europe, + lat: None, + lng: None, + email: "Email".to_string(), + website: "Website".to_string(), + mobile: "Mobile".to_string(), + vat: "VAT".to_string(), + agent: "Agent".to_string(), + logo: None, + owner: Principal::anonymous(), + }; + let data = Encode!(&agency).unwrap(); + let decoded = Decode!(&data, Agency).unwrap(); + + assert_eq!(agency, decoded); + } +} diff --git a/src/did/src/deferred/contract.rs b/src/did/src/deferred/contract.rs index 7a31b1d..abb327d 100644 --- a/src/did/src/deferred/contract.rs +++ b/src/did/src/deferred/contract.rs @@ -6,11 +6,10 @@ use time::Date; pub use crate::ID; -mod agency; mod generic_value; -pub use self::agency::{Agency, Continent}; pub use self::generic_value::GenericValue; +use super::agency::AgencyId; use super::{ContractError, DeferredMinterError, DeferredMinterResult}; use crate::H160; @@ -39,8 +38,8 @@ pub struct Contract { pub restricted_properties: RestrictedContractProperties, /// Documents associated to the contract pub documents: ContractDocuments, - /// Agency data - pub agency: Option, + /// Agency id + pub agency: AgencyId, /// Contract expiration date YYYY-MM-DD pub expiration: String, /// If the contract is closed @@ -188,3 +187,66 @@ impl Default for ContractRegistration { } } } + +#[cfg(test)] +mod test { + + use candid::Principal; + use pretty_assertions::assert_eq; + + use super::*; + + #[test] + fn test_should_encode_contract() { + let contract = Contract { + id: ID::from(1_u64), + r#type: ContractType::Sell, + sellers: vec![ + Seller { + address: H160::from_hex_str("0xE46A267b65Ed8CBAeBA9AdC3171063179b642E7A") + .unwrap(), + quota: 51, + }, + Seller { + address: H160::from_hex_str("0xE46A267b65Ed8CBAeBA9AdC3171063179b642E7A") + .unwrap(), + quota: 49, + }, + ], + buyers: vec![ + H160::from_hex_str("0xE46A267b65Ed8CBAeBA9AdC3171063179b642E7A").unwrap(), + H160::from_hex_str("0xE46A267b65Ed8CBAeBA9AdC3171063179b642E7A").unwrap(), + ], + installments: 2, + value: 250_000, + deposit: 50_000, + currency: "EUR".to_string(), + documents: vec![], + properties: vec![( + "Rome".to_string(), + GenericValue::TextContent("Rome".to_string()), + )], + restricted_properties: vec![( + "Secret".to_string(), + RestrictedProperty { + access_list: vec![RestrictionLevel::Agent], + value: GenericValue::TextContent("Secret".to_string()), + }, + )], + agency: Principal::management_canister(), + expiration: "2040-01-01".to_string(), + closed: false, + }; + let data = Encode!(&contract).unwrap(); + let decoded_contract = Decode!(&data, Contract).unwrap(); + + assert_eq!(contract.id, decoded_contract.id); + assert_eq!(contract.sellers, decoded_contract.sellers); + assert_eq!(contract.buyers, decoded_contract.buyers); + assert_eq!(contract.properties, decoded_contract.properties); + assert_eq!(contract.value, decoded_contract.value); + assert_eq!(contract.currency, decoded_contract.currency); + assert_eq!(contract.installments, decoded_contract.installments); + assert_eq!(contract.agency, decoded_contract.agency); + } +} diff --git a/src/did/src/deferred/minter.rs b/src/did/src/deferred/minter.rs index 3ecc265..a506226 100644 --- a/src/did/src/deferred/minter.rs +++ b/src/did/src/deferred/minter.rs @@ -113,3 +113,21 @@ impl Storable for Roles { Decode!(&bytes, Vec).unwrap().into() } } + +#[cfg(test)] +mod test { + + use ic_stable_structures::Storable as _; + use pretty_assertions::assert_eq; + + use super::*; + + #[test] + fn test_should_encode_role() { + let role: Roles = vec![Role::Agent, Role::Custodian].into(); + + let data = role.to_bytes(); + let decoded_role = Roles::from_bytes(data); + assert_eq!(role, decoded_role); + } +} diff --git a/src/did/src/deferred/real_estate.rs b/src/did/src/deferred/real_estate.rs new file mode 100644 index 0000000..5aa3f2d --- /dev/null +++ b/src/did/src/deferred/real_estate.rs @@ -0,0 +1,128 @@ +use candid::{CandidType, Decode, Deserialize, Encode}; +use ic_stable_structures::storable::Bound; +use ic_stable_structures::Storable; +use serde::Serialize; + +use super::{AgencyId, Continent}; +use crate::ID; + +/// Data for a real estate +#[derive(Clone, Debug, CandidType, Deserialize, Serialize, PartialEq)] +pub struct RealEstate { + /// Unique identifier for the real estate + pub id: ID, + /// agency + pub agency: AgencyId, + /// name + pub name: String, + /// description + pub description: String, + /// image URL + pub image: Option, + /// address + pub address: Option, + /// country + pub country: Option, + /// continent + pub continent: Option, + /// region + pub region: Option, + /// city + pub city: Option, + /// zone + pub zone: Option, + /// postal code + pub zip_code: Option, + /// latitude + pub latitude: Option, + /// longitude + pub longitude: Option, + /// square meters + pub square_meters: Option, + /// number of rooms + pub rooms: Option, + /// number of bathrooms + pub bathrooms: Option, + /// number of bedrooms + pub bedrooms: Option, + /// floors + pub floors: Option, + /// year of construction + pub year_of_construction: Option, + /// garden + pub garden: Option, + /// balconies + pub balconies: Option, + /// pool + pub pool: Option, + /// garage + pub garage: Option, + /// parking + pub parking: Option, + /// elevator + pub elevator: Option, + /// energy class + pub energy_class: Option, + /// youtube url + pub youtube: Option, +} + +impl Storable for RealEstate { + const BOUND: Bound = Bound::Unbounded; + + fn to_bytes(&self) -> std::borrow::Cow<[u8]> { + Encode!(&self).unwrap().into() + } + + fn from_bytes(bytes: std::borrow::Cow<[u8]>) -> Self { + Decode!(&bytes, Self).unwrap() + } +} + +#[cfg(test)] +mod test { + + use candid::Principal; + use pretty_assertions::assert_eq; + + use super::*; + + #[test] + fn test_should_encode_and_decode_real_estate() { + let real_estate = RealEstate { + id: 2_u64.into(), + agency: Principal::management_canister(), + name: "name".to_string(), + description: "description".to_string(), + image: Some("image".to_string()), + address: Some("address".to_string()), + country: Some("country".to_string()), + continent: Some(Continent::Europe), + region: Some("region".to_string()), + city: Some("city".to_string()), + zone: Some("zone".to_string()), + zip_code: Some("zip_code".to_string()), + latitude: Some(1.0), + longitude: Some(2.0), + square_meters: Some(100), + rooms: Some(3), + bathrooms: Some(2), + bedrooms: Some(1), + floors: Some(1), + year_of_construction: Some(2021), + garden: Some(true), + balconies: Some(1), + pool: Some(true), + garage: Some(true), + parking: Some(true), + elevator: Some(true), + energy_class: Some("A".to_string()), + youtube: Some("youtube".to_string()), + }; + + let data = Encode!(&real_estate).unwrap(); + let decoded = Decode!(&data, RealEstate).unwrap(); + + assert_eq!(real_estate, decoded); + } +} diff --git a/src/repr/src/main.rs b/src/repr/src/main.rs index a04ea22..d636075 100644 --- a/src/repr/src/main.rs +++ b/src/repr/src/main.rs @@ -106,7 +106,7 @@ fn repr_contract() -> anyhow::Result<()> { expiration: "1970-01-01".to_string(), properties, restricted_properties, - agency: Some(agency), + agency: agency.owner, id: 1u64.into(), documents, closed: false,