diff --git a/integration-tests/src/client/deferred.rs b/integration-tests/src/client/deferred.rs index 66ec06c..b95158f 100644 --- a/integration-tests/src/client/deferred.rs +++ b/integration-tests/src/client/deferred.rs @@ -1,5 +1,5 @@ use candid::{Encode, Nat, Principal}; -use did::deferred::{Contract, ContractRegistration, DeferredResult, TokenInfo}; +use did::deferred::{Agency, Contract, ContractRegistration, DeferredResult, TokenInfo}; use did::ID; use dip721::{GenericValue, NftError, TokenIdentifier, TokenMetadata}; @@ -21,12 +21,16 @@ impl<'a> DeferredClient<'a> { Self { env } } - pub fn register_contract(&self, data: ContractRegistration) -> DeferredResult { + pub fn register_contract( + &self, + caller: Principal, + data: ContractRegistration, + ) -> DeferredResult { let contract_id: DeferredResult = self .env .update( self.env.deferred_id, - admin(), + caller, "register_contract", Encode!(&data).unwrap(), ) @@ -207,4 +211,27 @@ impl<'a> DeferredClient<'a> { ) .unwrap() } + + pub fn admin_register_agency(&self, wallet: Principal, agency: Agency) { + let _: () = self + .env + .update( + self.env.deferred_id, + admin(), + "admin_register_agency", + Encode!(&wallet, &agency).unwrap(), + ) + .unwrap(); + } + + pub fn remove_agency(&self, wallet: Principal) -> DeferredResult<()> { + self.env + .update( + self.env.deferred_id, + wallet, + "remove_agency", + Encode!(&wallet).unwrap(), + ) + .unwrap() + } } diff --git a/integration-tests/tests/inspect/deferred.rs b/integration-tests/tests/inspect/deferred.rs index cee2ce2..8226814 100644 --- a/integration-tests/tests/inspect/deferred.rs +++ b/integration-tests/tests/inspect/deferred.rs @@ -95,7 +95,9 @@ fn test_should_inspect_update_contract_property() { }; // call register - let contract_id = client.register_contract(registration_data).unwrap(); + let contract_id = client + .register_contract(admin(), registration_data) + .unwrap(); assert!(env .update::>( env.deferred_id, @@ -154,7 +156,9 @@ fn test_should_inspect_update_contract_property_is_not_authorized() { }; // call register - let contract_id = client.register_contract(registration_data).unwrap(); + let contract_id = client + .register_contract(admin(), registration_data) + .unwrap(); assert!(env .update::>( env.deferred_id, @@ -199,7 +203,9 @@ fn test_should_inspect_update_contract_property_bad_key() { }; // call register - let contract_id = client.register_contract(registration_data).unwrap(); + let contract_id = client + .register_contract(admin(), registration_data) + .unwrap(); assert!(env .update::>( env.deferred_id, @@ -238,7 +244,9 @@ fn test_should_inspect_update_contract_buyers() { }; // call register - let contract_id = client.register_contract(registration_data).unwrap(); + let contract_id = client + .register_contract(admin(), registration_data) + .unwrap(); assert!(env .update::>( env.deferred_id, @@ -288,7 +296,9 @@ fn test_should_inspect_update_contract_buyers_not_seller() { }; // call register - let contract_id = client.register_contract(registration_data).unwrap(); + let contract_id = client + .register_contract(admin(), registration_data) + .unwrap(); assert!(env .update::>( env.deferred_id, @@ -444,7 +454,9 @@ fn test_should_inspect_burn() { )], }; - let contract_id = client.register_contract(registration_data).unwrap(); + let contract_id = client + .register_contract(admin(), registration_data) + .unwrap(); assert!(client.admin_sign_contract(contract_id.clone()).is_ok()); // transfer token to buyer diff --git a/integration-tests/tests/use_case/buy_marketplace_nft.rs b/integration-tests/tests/use_case/buy_marketplace_nft.rs index 3df1364..bed5451 100644 --- a/integration-tests/tests/use_case/buy_marketplace_nft.rs +++ b/integration-tests/tests/use_case/buy_marketplace_nft.rs @@ -1,6 +1,6 @@ use did::deferred::{ContractRegistration, ContractType, GenericValue, Seller, ID}; use icrc::icrc1::account::Account; -use integration_tests::actor::{alice, alice_account, bob, charlie, charlie_account}; +use integration_tests::actor::{admin, alice, alice_account, bob, charlie, charlie_account}; use integration_tests::client::{DeferredClient, IcrcLedgerClient, MarketplaceClient}; use integration_tests::TestEnv; use pretty_assertions::{assert_eq, assert_ne}; @@ -130,7 +130,7 @@ fn setup_contract_marketplace(env: &TestEnv) -> ID { }; // call register let contract_id = deferred_client - .register_contract(registration_data) + .register_contract(admin(), registration_data) .unwrap(); assert_eq!(contract_id, 0_u64); diff --git a/integration-tests/tests/use_case/increment_contract_value.rs b/integration-tests/tests/use_case/increment_contract_value.rs index 6369369..626561d 100644 --- a/integration-tests/tests/use_case/increment_contract_value.rs +++ b/integration-tests/tests/use_case/increment_contract_value.rs @@ -1,5 +1,5 @@ use did::deferred::{ContractRegistration, ContractType, GenericValue, Seller}; -use integration_tests::actor::alice; +use integration_tests::actor::{admin, alice}; use integration_tests::client::DeferredClient; use integration_tests::TestEnv; use pretty_assertions::assert_eq; @@ -28,7 +28,7 @@ fn test_as_seller_i_can_set_the_contract_buyers() { // call register let contract_id = deferred_client - .register_contract(registration_data) + .register_contract(admin(), registration_data) .unwrap(); // sign contract diff --git a/integration-tests/tests/use_case/mod.rs b/integration-tests/tests/use_case/mod.rs index 16cf0e5..2ef258e 100644 --- a/integration-tests/tests/use_case/mod.rs +++ b/integration-tests/tests/use_case/mod.rs @@ -1,6 +1,7 @@ mod buy_marketplace_nft; mod icrc2_spend; mod increment_contract_value; +mod register_agency; mod register_contract_buyers; mod register_sell_contract; mod reserve_reward_pool; diff --git a/integration-tests/tests/use_case/register_agency.rs b/integration-tests/tests/use_case/register_agency.rs new file mode 100644 index 0000000..e046ea3 --- /dev/null +++ b/integration-tests/tests/use_case/register_agency.rs @@ -0,0 +1,68 @@ +use candid::Nat; +use did::deferred::{Agency, ContractRegistration, ContractType, GenericValue, Seller}; +use integration_tests::actor::{alice, bob}; +use integration_tests::client::DeferredClient; +use integration_tests::TestEnv; +use pretty_assertions::assert_eq; + +#[test] +#[serial_test::serial] +fn test_should_register_agency_and_be_able_to_create_contract() { + let env = TestEnv::init(); + let deferred_client = DeferredClient::from(&env); + + let registration_data = ContractRegistration { + r#type: ContractType::Sell, + sellers: vec![Seller { + principal: alice(), + quota: 100, + }], + buyers: vec![], + value: 400_000, + currency: "EUR".to_string(), + installments: 400_000 / 100, + properties: vec![( + "contract:address".to_string(), + GenericValue::TextContent("via roma 10".to_string()), + )], + }; + + // give bob an agency + deferred_client.admin_register_agency( + bob(), + Agency { + name: "Bob's agency".to_string(), + 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, + }, + ); + + // call register + let contract_id = deferred_client + .register_contract(bob(), registration_data.clone()) + .unwrap(); + assert_eq!(contract_id, 0_u64); + + // check unsigned contract and signed contracts + let unsigned_contracts = deferred_client.admin_get_unsigned_contracts(); + assert_eq!(unsigned_contracts, vec![contract_id.clone()]); + let signed_contract = deferred_client.get_signed_contracts(); + assert!(signed_contract.is_empty()); + + // sign contract + let res = deferred_client.admin_sign_contract(Nat::from(0_u64)); + assert!(res.is_ok()); + + // agency could remove himself + assert!(deferred_client.remove_agency(bob()).is_ok()); +} diff --git a/integration-tests/tests/use_case/register_contract_buyers.rs b/integration-tests/tests/use_case/register_contract_buyers.rs index aefb2b0..68b69af 100644 --- a/integration-tests/tests/use_case/register_contract_buyers.rs +++ b/integration-tests/tests/use_case/register_contract_buyers.rs @@ -1,5 +1,5 @@ use did::deferred::{ContractRegistration, ContractType, GenericValue, Seller}; -use integration_tests::actor::{alice, bob}; +use integration_tests::actor::{admin, alice, bob}; use integration_tests::client::DeferredClient; use integration_tests::TestEnv; use pretty_assertions::assert_eq; @@ -28,7 +28,7 @@ fn test_as_seller_i_can_set_the_contract_buyers() { // call register let contract_id = deferred_client - .register_contract(registration_data) + .register_contract(admin(), registration_data) .unwrap(); // sign contract diff --git a/integration-tests/tests/use_case/register_sell_contract.rs b/integration-tests/tests/use_case/register_sell_contract.rs index f85f32b..d558658 100644 --- a/integration-tests/tests/use_case/register_sell_contract.rs +++ b/integration-tests/tests/use_case/register_sell_contract.rs @@ -1,6 +1,6 @@ use candid::Nat; -use did::deferred::{ContractRegistration, ContractType, GenericValue, Seller}; -use integration_tests::actor::{alice, bob}; +use did::deferred::{Agency, ContractRegistration, ContractType, GenericValue, Seller}; +use integration_tests::actor::{admin, alice, bob}; use integration_tests::client::DeferredClient; use integration_tests::TestEnv; use pretty_assertions::assert_eq; @@ -33,9 +33,27 @@ fn test_as_seller_i_can_register_a_sell_contract() { )], }; + // register agency for admin + let agency = Agency { + name: "Admin's agency".to_string(), + 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, + }; + deferred_client.admin_register_agency(admin(), agency.clone()); + // call register let contract_id = deferred_client - .register_contract(registration_data) + .register_contract(admin(), registration_data) .unwrap(); assert_eq!(contract_id, 0_u64); @@ -49,6 +67,10 @@ fn test_as_seller_i_can_register_a_sell_contract() { let res = deferred_client.admin_sign_contract(Nat::from(0_u64)); assert!(res.is_ok()); + // get contract + let contract = deferred_client.get_contract(&contract_id).unwrap(); + assert_eq!(contract.agency.unwrap(), agency); + // check unsigned contract and signed contracts let unsigned_contracts = deferred_client.admin_get_unsigned_contracts(); assert!(unsigned_contracts.is_empty()); diff --git a/integration-tests/tests/use_case/reserve_reward_pool.rs b/integration-tests/tests/use_case/reserve_reward_pool.rs index db707e1..3295d6d 100644 --- a/integration-tests/tests/use_case/reserve_reward_pool.rs +++ b/integration-tests/tests/use_case/reserve_reward_pool.rs @@ -2,7 +2,7 @@ use candid::Nat; use did::deferred::{ContractRegistration, ContractType, Seller}; use dip721::GenericValue; use icrc::icrc1::account::Account; -use integration_tests::actor::alice; +use integration_tests::actor::{admin, alice}; use integration_tests::client::{DeferredClient, EkokeClient}; use integration_tests::{ekoke_to_picoekoke, TestEnv}; @@ -33,7 +33,7 @@ fn test_should_reserve_a_reward_pool_on_ekoke() { // call register let contract_id = deferred_client - .register_contract(registration_data) + .register_contract(admin(), registration_data) .unwrap(); assert_eq!(contract_id, 0_u64); diff --git a/integration-tests/tests/use_case/update_contract_property.rs b/integration-tests/tests/use_case/update_contract_property.rs index b2e3ff5..c99dd0b 100644 --- a/integration-tests/tests/use_case/update_contract_property.rs +++ b/integration-tests/tests/use_case/update_contract_property.rs @@ -1,5 +1,5 @@ use did::deferred::{ContractRegistration, ContractType, GenericValue, Seller}; -use integration_tests::actor::{alice, bob}; +use integration_tests::actor::{admin, alice, bob}; use integration_tests::client::DeferredClient; use integration_tests::TestEnv; use pretty_assertions::assert_eq; @@ -40,7 +40,7 @@ fn test_should_update_contract_property() { // call register let contract_id = deferred_client - .register_contract(registration_data) + .register_contract(admin(), registration_data) .unwrap(); let res = deferred_client.admin_sign_contract(contract_id.clone()); diff --git a/src/declarations/deferred/deferred.did.d.ts b/src/declarations/deferred/deferred.did.d.ts index 7082db6..3450c5a 100644 --- a/src/declarations/deferred/deferred.did.d.ts +++ b/src/declarations/deferred/deferred.did.d.ts @@ -2,6 +2,21 @@ import type { Principal } from '@dfinity/principal'; import type { ActorMethod } from '@dfinity/agent'; import type { IDL } from '@dfinity/candid'; +export interface Agency { + 'vat' : string, + 'region' : string, + 'zip_code' : string, + 'country' : string, + 'agent' : string, + 'city' : string, + 'logo' : [] | [string], + 'name' : string, + 'continent' : Continent, + 'email' : string, + 'website' : string, + 'address' : string, + 'mobile' : string, +} export type AllowanceError = { 'AllowanceNotFound' : null } | { 'BadSpender' : null } | { 'AllowanceChanged' : null } | @@ -25,11 +40,19 @@ export type ConfigurationError = { 'AdminsCantBeEmpty' : null } | { 'AnonymousAdmin' : null }; export type ConfigurationError_1 = { 'CustodialsCantBeEmpty' : null } | { 'AnonymousCustodial' : null }; +export type Continent = { 'Africa' : null } | + { 'Antarctica' : null } | + { 'Asia' : null } | + { 'Europe' : null } | + { 'SouthAmerica' : null } | + { 'Oceania' : null } | + { 'NorthAmerica' : null }; export interface Contract { 'id' : bigint, 'value' : bigint, 'type' : ContractType, 'is_signed' : boolean, + 'agency' : [] | [Agency], 'properties' : Array<[string, GenericValue]>, 'sellers' : Array, 'tokens' : Array, @@ -252,6 +275,7 @@ export type Vec = Array< >; export interface _SERVICE { 'admin_get_unsigned_contracts' : ActorMethod<[], Array>, + 'admin_register_agency' : ActorMethod<[Principal, Agency], undefined>, 'admin_remove_role' : ActorMethod<[Principal, Role], Result>, 'admin_set_ekoke_canister' : ActorMethod<[Principal], undefined>, 'admin_set_marketplace_canister' : ActorMethod<[Principal], undefined>, @@ -262,6 +286,7 @@ export interface _SERVICE { 'burn' : ActorMethod<[bigint], Result_1>, 'custodians' : ActorMethod<[], Array>, 'cycles' : ActorMethod<[], bigint>, + 'get_agencies' : ActorMethod<[], Array>, 'get_contract' : ActorMethod<[bigint], [] | [Contract]>, 'get_signed_contracts' : ActorMethod<[], Array>, 'get_token' : ActorMethod<[bigint], [] | [TokenInfo]>, @@ -280,6 +305,7 @@ export interface _SERVICE { 'owner_token_identifiers' : ActorMethod<[Principal], Result_4>, 'owner_token_metadata' : ActorMethod<[Principal], Result_5>, 'register_contract' : ActorMethod<[ContractRegistration], Result_6>, + 'remove_agency' : ActorMethod<[Principal], Result>, 'seller_increment_contract_value' : ActorMethod< [bigint, bigint, bigint], Result diff --git a/src/declarations/deferred/deferred.did.js b/src/declarations/deferred/deferred.did.js index 644ea7e..bf5de1b 100644 --- a/src/declarations/deferred/deferred.did.js +++ b/src/declarations/deferred/deferred.did.js @@ -5,6 +5,30 @@ export const idlFactory = ({ IDL }) => { 'custodians' : IDL.Vec(IDL.Principal), 'marketplace_canister' : IDL.Principal, }); + const Continent = IDL.Variant({ + 'Africa' : IDL.Null, + 'Antarctica' : IDL.Null, + 'Asia' : IDL.Null, + 'Europe' : IDL.Null, + 'SouthAmerica' : IDL.Null, + 'Oceania' : IDL.Null, + 'NorthAmerica' : IDL.Null, + }); + const Agency = IDL.Record({ + 'vat' : IDL.Text, + 'region' : IDL.Text, + 'zip_code' : IDL.Text, + 'country' : IDL.Text, + 'agent' : IDL.Text, + 'city' : IDL.Text, + 'logo' : IDL.Opt(IDL.Text), + 'name' : IDL.Text, + 'continent' : Continent, + 'email' : IDL.Text, + 'website' : IDL.Text, + 'address' : IDL.Text, + 'mobile' : IDL.Text, + }); const Role = IDL.Variant({ 'Custodian' : IDL.Null, 'Agent' : IDL.Null }); const NftError = IDL.Variant({ 'UnauthorizedOperator' : IDL.Null, @@ -199,6 +223,7 @@ export const idlFactory = ({ IDL }) => { 'value' : IDL.Nat64, 'type' : ContractType, 'is_signed' : IDL.Bool, + 'agency' : IDL.Opt(Agency), 'properties' : IDL.Vec(IDL.Tuple(IDL.Text, GenericValue)), 'sellers' : IDL.Vec(Seller), 'tokens' : IDL.Vec(IDL.Nat), @@ -294,6 +319,7 @@ export const idlFactory = ({ IDL }) => { [IDL.Vec(IDL.Nat)], ['query'], ), + 'admin_register_agency' : IDL.Func([IDL.Principal, Agency], [], []), 'admin_remove_role' : IDL.Func([IDL.Principal, Role], [Result], []), 'admin_set_ekoke_canister' : IDL.Func([IDL.Principal], [], []), 'admin_set_marketplace_canister' : IDL.Func([IDL.Principal], [], []), @@ -304,6 +330,7 @@ export const idlFactory = ({ IDL }) => { 'burn' : IDL.Func([IDL.Nat], [Result_1], []), 'custodians' : IDL.Func([], [IDL.Vec(IDL.Principal)], ['query']), 'cycles' : IDL.Func([], [IDL.Nat], ['query']), + 'get_agencies' : IDL.Func([], [IDL.Vec(Agency)], ['query']), 'get_contract' : IDL.Func([IDL.Nat], [IDL.Opt(Contract)], ['query']), 'get_signed_contracts' : IDL.Func([], [IDL.Vec(IDL.Nat)], ['query']), 'get_token' : IDL.Func([IDL.Nat], [IDL.Opt(TokenInfo)], ['query']), @@ -339,6 +366,7 @@ export const idlFactory = ({ IDL }) => { ), 'owner_token_metadata' : IDL.Func([IDL.Principal], [Result_5], ['query']), 'register_contract' : IDL.Func([ContractRegistration], [Result_6], []), + 'remove_agency' : IDL.Func([IDL.Principal], [Result], []), 'seller_increment_contract_value' : IDL.Func( [IDL.Nat, IDL.Nat64, IDL.Nat64], [Result], diff --git a/src/deferred/deferred.did b/src/deferred/deferred.did index 47084df..1a2a8cc 100644 --- a/src/deferred/deferred.did +++ b/src/deferred/deferred.did @@ -1,3 +1,18 @@ +type Agency = record { + vat : text; + region : text; + zip_code : text; + country : text; + agent : text; + city : text; + logo : opt text; + name : text; + continent : Continent; + email : text; + website : text; + address : text; + mobile : text; +}; type AllowanceError = variant { AllowanceNotFound; BadSpender; @@ -23,11 +38,21 @@ type ConfigurationError_1 = variant { CustodialsCantBeEmpty; AnonymousCustodial; }; +type Continent = variant { + Africa; + Antarctica; + Asia; + Europe; + SouthAmerica; + Oceania; + NorthAmerica; +}; type Contract = record { id : nat; value : nat64; "type" : ContractType; is_signed : bool; + agency : opt Agency; properties : vec record { text; GenericValue }; sellers : vec Seller; tokens : vec nat; @@ -249,6 +274,7 @@ type Vec = vec record { }; service : (DeferredInitData) -> { admin_get_unsigned_contracts : () -> (vec nat) query; + admin_register_agency : (principal, Agency) -> (); admin_remove_role : (principal, Role) -> (Result); admin_set_ekoke_canister : (principal) -> (); admin_set_marketplace_canister : (principal) -> (); @@ -259,6 +285,7 @@ service : (DeferredInitData) -> { burn : (nat) -> (Result_1); custodians : () -> (vec principal) query; cycles : () -> (nat) query; + get_agencies : () -> (vec Agency) query; get_contract : (nat) -> (opt Contract) query; get_signed_contracts : () -> (vec nat) query; get_token : (nat) -> (opt TokenInfo) query; @@ -274,6 +301,7 @@ service : (DeferredInitData) -> { owner_token_identifiers : (principal) -> (Result_4) query; owner_token_metadata : (principal) -> (Result_5) query; register_contract : (ContractRegistration) -> (Result_6); + remove_agency : (principal) -> (Result); seller_increment_contract_value : (nat, nat64, nat64) -> (Result); set_approval_for_all : (principal, bool) -> (Result_1); set_custodians : (vec principal) -> (); diff --git a/src/deferred/src/app.rs b/src/deferred/src/app.rs index 9a6cebf..0748eb7 100644 --- a/src/deferred/src/app.rs +++ b/src/deferred/src/app.rs @@ -15,7 +15,7 @@ use async_trait::async_trait; use candid::{Nat, Principal}; use configuration::Configuration; use did::deferred::{ - Contract, ContractRegistration, DeferredError, DeferredInitData, DeferredResult, Role, + Agency, Contract, ContractRegistration, DeferredError, DeferredInitData, DeferredResult, Role, TokenError, TokenInfo, }; use did::ID; @@ -27,7 +27,7 @@ use dip721::{ pub use self::inspect::Inspect; use self::minter::Minter; use self::roles::RolesManager; -use self::storage::{ContractStorage, TxHistory}; +use self::storage::{Agents, ContractStorage, TxHistory}; use crate::utils::caller; #[derive(Default)] @@ -69,6 +69,24 @@ impl Deferred { ContractStorage::get_signed_contracts() } + /// get agencies + pub fn get_agencies() -> Vec { + Agents::get_agencies() + } + + /// Remove agency by wallet. + /// + /// Only a custodian can call this method or the caller must be the owner of the agency + pub fn remove_agency(wallet: Principal) -> DeferredResult<()> { + if !Inspect::inspect_remove_agency(caller()) { + ic_cdk::trap("Unauthorized"); + } + + Agents::remove_agency(wallet); + // remove role + RolesManager::remove_role(wallet, Role::Agent) + } + /// get unsigned contracts pub fn admin_get_unsigned_contracts() -> Vec { if !Inspect::inspect_is_custodian(caller()) { @@ -134,6 +152,7 @@ impl Deferred { sellers: data.sellers, tokens: vec![], value: data.value, + agency: Agents::get_agency_by_wallet(caller()), }; // register contract @@ -197,6 +216,19 @@ impl Deferred { } } + /// Insert agency into the storage + pub fn admin_register_agency(wallet: Principal, agency: Agency) { + if !Inspect::inspect_is_custodian(caller()) { + ic_cdk::trap("Unauthorized"); + } + + Agents::insert_agency(wallet, agency); + // give role to the agent + if !RolesManager::is_custodian(wallet) { + RolesManager::give_role(wallet, Role::Agent); + } + } + /// Give role to the provied principal pub fn admin_set_role(principal: Principal, role: Role) { if !Inspect::inspect_is_custodian(caller()) { @@ -516,6 +548,7 @@ mod test { use did::deferred::Seller; use pretty_assertions::assert_eq; + use self::test_utils::{bob, mock_agency}; use super::test_utils::store_mock_contract; use super::*; use crate::app::test_utils::{mock_token, store_mock_contract_with}; @@ -940,6 +973,19 @@ mod test { assert_eq!(Deferred::total_transactions(), Nat::from(1_u64)); } + #[test] + fn test_should_register_agency() { + init_canister(); + let wallet = bob(); + let agency = mock_agency(); + + Deferred::admin_register_agency(wallet, agency.clone()); + // check if has role + assert!(RolesManager::is_agent(wallet)); + // check if is in the storage + assert_eq!(Agents::get_agency_by_wallet(wallet), Some(agency)); + } + fn init_canister() { Deferred::init(DeferredInitData { custodians: vec![caller()], diff --git a/src/deferred/src/app/inspect.rs b/src/deferred/src/app/inspect.rs index 53169c5..e18b392 100644 --- a/src/deferred/src/app/inspect.rs +++ b/src/deferred/src/app/inspect.rs @@ -8,7 +8,7 @@ use did::ID; use dip721::NftError; use super::roles::RolesManager; -use super::storage::ContractStorage; +use super::storage::{Agents, ContractStorage}; pub struct Inspect; @@ -185,6 +185,11 @@ impl Inspect { Ok(contract) } + + /// Inspect whether caller is custodian or owner of the agency + pub fn inspect_remove_agency(caller: Principal) -> bool { + RolesManager::is_custodian(caller) || Agents::get_agency_by_wallet(caller).is_some() + } } #[cfg(test)] @@ -194,7 +199,7 @@ mod test { use pretty_assertions::assert_eq; use super::*; - use crate::app::test_utils::{self, alice}; + use crate::app::test_utils::{self, alice, bob}; use crate::utils::caller; #[test] @@ -607,4 +612,18 @@ mod test { ) .is_ok()); } + + #[test] + fn test_should_inspect_whether_to_remove_agency() { + let caller = crate::utils::caller(); + assert!(RolesManager::set_custodians(vec![caller]).is_ok()); + + // register agency + Agents::insert_agency(bob(), test_utils::mock_agency()); + assert!(Inspect::inspect_remove_agency(caller)); + assert!(Inspect::inspect_remove_agency(bob())); + assert!(!Inspect::inspect_remove_agency( + Principal::management_canister() + )); + } } diff --git a/src/deferred/src/app/memory.rs b/src/deferred/src/app/memory.rs index bc8f68a..27e31e6 100644 --- a/src/deferred/src/app/memory.rs +++ b/src/deferred/src/app/memory.rs @@ -4,6 +4,7 @@ use ic_stable_structures::DefaultMemoryImpl; pub const TOKENS_MEMORY_ID: MemoryId = MemoryId::new(10); pub const CONTRACTS_MEMORY_ID: MemoryId = MemoryId::new(11); pub const TRANSACTIONS_MEMORY_ID: MemoryId = MemoryId::new(12); +pub const AGENCIES_MEMORY_ID: MemoryId = MemoryId::new(13); pub const LOGO_MEMORY_ID: MemoryId = MemoryId::new(20); pub const NAME_MEMORY_ID: MemoryId = MemoryId::new(21); diff --git a/src/deferred/src/app/storage.rs b/src/deferred/src/app/storage.rs index fcf0cfe..61f62cf 100644 --- a/src/deferred/src/app/storage.rs +++ b/src/deferred/src/app/storage.rs @@ -1,22 +1,31 @@ use std::cell::RefCell; -use did::deferred::{Contract, DeferredError, DeferredResult, StorableTxEvent, Token, TokenError}; -use did::{StorableNat, ID}; +use did::deferred::{ + Agency, Contract, DeferredError, DeferredResult, StorableTxEvent, Token, TokenError, +}; +use did::{StorableNat, StorablePrincipal, ID}; use dip721::TokenIdentifier; use ic_stable_structures::memory_manager::VirtualMemory; use ic_stable_structures::{BTreeMap, DefaultMemoryImpl}; use crate::app::memory::{ - CONTRACTS_MEMORY_ID, MEMORY_MANAGER, TOKENS_MEMORY_ID, TRANSACTIONS_MEMORY_ID, + AGENCIES_MEMORY_ID, CONTRACTS_MEMORY_ID, MEMORY_MANAGER, TOKENS_MEMORY_ID, + TRANSACTIONS_MEMORY_ID, }; +mod agents; mod contracts; mod tx_history; +pub use agents::Agents; pub use contracts::ContractStorage; pub use tx_history::TxHistory; thread_local! { + /// Agencies storage (1 wallet has 1 agency) + static AGENCIES: RefCell>> = + RefCell::new(BTreeMap::new(MEMORY_MANAGER.with(|mm| mm.get(AGENCIES_MEMORY_ID)))); + /// ContractStorage storage (1 contract has many tokens) static CONTRACTS: RefCell>> = RefCell::new(BTreeMap::new(MEMORY_MANAGER.with(|mm| mm.get(CONTRACTS_MEMORY_ID)))); diff --git a/src/deferred/src/app/storage/agents.rs b/src/deferred/src/app/storage/agents.rs new file mode 100644 index 0000000..3e3d0cd --- /dev/null +++ b/src/deferred/src/app/storage/agents.rs @@ -0,0 +1,64 @@ +use candid::Principal; +use did::deferred::Agency; +use did::StorablePrincipal; + +use super::AGENCIES; + +pub struct Agents; + +impl Agents { + pub fn insert_agency(wallet: Principal, agency: Agency) { + AGENCIES.with_borrow_mut(|agencies| { + agencies.insert(wallet.into(), agency); + }) + } + + pub fn get_agency_by_wallet(wallet: Principal) -> Option { + AGENCIES.with_borrow(|agencies| agencies.get(&StorablePrincipal::from(wallet)).clone()) + } + + /// Get all agencies + pub fn get_agencies() -> Vec { + AGENCIES.with_borrow(|agencies| agencies.iter().map(|(_, agency)| agency.clone()).collect()) + } + + /// Remove agency by wallet + pub fn remove_agency(wallet: Principal) { + AGENCIES.with_borrow_mut(|agencies| { + agencies.remove(&StorablePrincipal::from(wallet)); + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::app::test_utils::{alice, mock_agency}; + + #[test] + fn test_should_store_and_retrieve_agency() { + let wallet = alice(); + Agents::insert_agency(wallet, mock_agency()); + + assert!( + Agents::get_agency_by_wallet(wallet).is_some(), + "Agency should be stored" + ); + assert!( + Agents::get_agency_by_wallet(Principal::anonymous()).is_none(), + "Agency should not be stored" + ) + } + + #[test] + fn test_should_remove_agency() { + let wallet = alice(); + Agents::insert_agency(wallet, mock_agency()); + Agents::remove_agency(wallet); + + assert!( + Agents::get_agency_by_wallet(wallet).is_none(), + "Agency should be removed" + ); + } +} diff --git a/src/deferred/src/app/storage/contracts.rs b/src/deferred/src/app/storage/contracts.rs index 55ab8a7..5487015 100644 --- a/src/deferred/src/app/storage/contracts.rs +++ b/src/deferred/src/app/storage/contracts.rs @@ -775,6 +775,7 @@ mod test { "contract:city".to_string(), dip721::GenericValue::TextContent("Rome".to_string()), )], + agency: None, }; assert!(ContractStorage::insert_contract(contract.clone(),).is_ok()); diff --git a/src/deferred/src/app/test_utils.rs b/src/deferred/src/app/test_utils.rs index fefcab8..9aa6250 100644 --- a/src/deferred/src/app/test_utils.rs +++ b/src/deferred/src/app/test_utils.rs @@ -1,5 +1,5 @@ use candid::Principal; -use did::deferred::{Contract, Seller, Token}; +use did::deferred::{Agency, Contract, Seller, Token}; use did::ID; use dip721::TokenIdentifier; @@ -45,6 +45,25 @@ pub fn mock_contract(id: u64, installments: u64) -> Contract { "contract:city".to_string(), dip721::GenericValue::TextContent("Rome".to_string()), )], + agency: Some(mock_agency()), + } +} + +pub fn mock_agency() -> Agency { + Agency { + name: "Dummy Real estate".to_string(), + 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, } } @@ -100,3 +119,7 @@ where pub fn alice() -> Principal { Principal::from_text("be2us-64aaa-aaaaa-qaabq-cai").unwrap() } + +pub fn bob() -> Principal { + Principal::from_text("bs5l3-6b3zu-dpqyj-p2x4a-jyg4k-goneb-afof2-y5d62-skt67-3756q-dqe").unwrap() +} diff --git a/src/deferred/src/lib.rs b/src/deferred/src/lib.rs index 819a329..8a5ff5b 100644 --- a/src/deferred/src/lib.rs +++ b/src/deferred/src/lib.rs @@ -6,7 +6,7 @@ use candid::{candid_method, Nat, Principal}; use did::deferred::{ - Contract, ContractRegistration, DeferredInitData, DeferredResult, Role, TokenInfo, + Agency, Contract, ContractRegistration, DeferredInitData, DeferredResult, Role, TokenInfo, }; use did::ID; use dip721::{Dip721 as _, GenericValue, TokenIdentifier}; @@ -66,6 +66,18 @@ pub fn get_contract(id: ID) -> Option { Deferred::get_contract(&id) } +#[query] +#[candid_method(query)] +pub fn get_agencies() -> Vec { + Deferred::get_agencies() +} + +#[update] +#[candid_method(update)] +pub fn remove_agency(wallet: Principal) -> DeferredResult<()> { + Deferred::remove_agency(wallet) +} + #[query] #[candid_method(query)] pub fn get_signed_contracts() -> Vec { @@ -118,6 +130,12 @@ pub fn admin_remove_role(principal: Principal, role: Role) -> DeferredResult<()> Deferred::admin_remove_role(principal, role) } +#[update] +#[candid_method(update)] +pub fn admin_register_agency(wallet: Principal, agency: Agency) { + Deferred::admin_register_agency(wallet, agency) +} + // DIP721 #[query] diff --git a/src/did/src/deferred.rs b/src/did/src/deferred.rs index b4a5908..cf53848 100644 --- a/src/did/src/deferred.rs +++ b/src/did/src/deferred.rs @@ -8,8 +8,8 @@ pub type DeferredResult = Result; pub use self::canister::{DeferredInitData, Role, Roles, StorableTxEvent}; pub use self::contract::{ - Contract, ContractProperties, ContractRegistration, ContractType, GenericValue, Seller, Token, - TokenIdentifier, TokenInfo, ID, + Agency, Continent, Contract, ContractProperties, ContractRegistration, ContractType, + GenericValue, Seller, Token, TokenIdentifier, TokenInfo, ID, }; pub use self::error::{ConfigurationError, DeferredError, TokenError}; @@ -93,6 +93,21 @@ mod test { "Rome".to_string(), GenericValue::TextContent("Rome".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, + email: "Email".to_string(), + website: "Website".to_string(), + mobile: "Mobile".to_string(), + vat: "VAT".to_string(), + agent: "Agent".to_string(), + logo: None, + }), }; let data = Encode!(&contract).unwrap(); let decoded_contract = Decode!(&data, Contract).unwrap(); @@ -107,6 +122,7 @@ mod test { assert_eq!(contract.currency, decoded_contract.currency); assert_eq!(contract.installments, decoded_contract.installments); assert_eq!(contract.is_signed, decoded_contract.is_signed); + assert_eq!(contract.agency, decoded_contract.agency); } #[test] diff --git a/src/did/src/deferred/contract.rs b/src/did/src/deferred/contract.rs index da19101..ed2bc07 100644 --- a/src/did/src/deferred/contract.rs +++ b/src/did/src/deferred/contract.rs @@ -5,9 +5,11 @@ use ic_stable_structures::Storable; pub use crate::ID; +mod agency; mod info; mod token; +pub use agency::{Agency, Continent}; pub use info::TokenInfo; pub use token::Token; @@ -36,6 +38,8 @@ pub struct Contract { pub currency: String, /// Data associated to the contract pub properties: ContractProperties, + /// Agency data + pub agency: Option, } impl Contract { diff --git a/src/did/src/deferred/contract/agency.rs b/src/did/src/deferred/contract/agency.rs new file mode 100644 index 0000000..bf41fa9 --- /dev/null +++ b/src/did/src/deferred/contract/agency.rs @@ -0,0 +1,45 @@ +use candid::{CandidType, Decode, Encode}; +use ic_stable_structures::storable::Bound; +use ic_stable_structures::Storable; +use serde::Deserialize; + +/// A sell contract for a building +#[derive(Clone, Debug, CandidType, Deserialize, PartialEq)] +pub struct Agency { + pub name: String, + pub address: String, + pub city: String, + pub region: String, + pub zip_code: String, + pub country: String, + pub continent: Continent, + pub email: String, + pub website: String, + pub mobile: String, + pub vat: String, + pub agent: String, + pub logo: Option, +} + +#[derive(Clone, Debug, CandidType, Deserialize, Copy, PartialEq, Eq)] +pub enum Continent { + Africa, + Antarctica, + Asia, + Europe, + NorthAmerica, + Oceania, + SouthAmerica, +} + +impl Storable for Agency { + 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() + } +} diff --git a/src/marketplace/src/client/deferred.rs b/src/marketplace/src/client/deferred.rs index 34c3f3d..3b92e1d 100644 --- a/src/marketplace/src/client/deferred.rs +++ b/src/marketplace/src/client/deferred.rs @@ -75,6 +75,7 @@ impl DeferredClient { value: 400_000, currency: "EUR".to_string(), properties: vec![], + agency: None, }, })) }