diff --git a/land_registry/src/interface.cairo b/land_registry/src/interface.cairo index ce01f584..1c7e7805 100644 --- a/land_registry/src/interface.cairo +++ b/land_registry/src/interface.cairo @@ -8,8 +8,8 @@ pub struct Land { area: u256, land_use: LandUse, status: LandStatus, - inspector: Option, last_transaction_timestamp: u64, + inspector: ContractAddress, } #[derive(Drop, Debug, Copy, Serde, Clone, starknet::Store, PartialEq)] @@ -43,8 +43,6 @@ pub trait ILandRegistry { fn register_land( ref self: TContractState, location: Location, area: u256, land_use: LandUse, ) -> u256; - fn set_land_inspector(ref self: TContractState, land_id: u256, inspector: ContractAddress); - fn get_land_inspector(self: @TContractState, land_id: u256) -> Option; fn transfer_land(ref self: TContractState, land_id: u256, new_owner: ContractAddress); fn get_land(self: @TContractState, land_id: u256) -> Land; fn get_land_count(self: @TContractState) -> u256; @@ -53,12 +51,15 @@ pub trait ILandRegistry { fn approve_land(ref self: TContractState, land_id: u256); fn reject_land(ref self: TContractState, land_id: u256); fn is_inspector(self: @TContractState, inspector: ContractAddress) -> bool; - fn add_inspector(ref self: TContractState, inspector: ContractAddress); - fn remove_inspector(ref self: TContractState, inspector: ContractAddress); + // fn add_inspector(ref self: TContractState, inspector: ContractAddress); + // fn remove_inspector(ref self: TContractState, inspector: ContractAddress); fn is_land_approved(self: @TContractState, land_id: u256) -> bool; fn get_pending_approvals(self: @TContractState) -> Array; fn get_land_transaction_history( self: @TContractState, land_id: u256 ) -> Array<(ContractAddress, u64)>; fn get_land_status(self: @TContractState, land_id: u256) -> LandStatus; + + fn set_land_inspector(ref self: TContractState, land_id: u256, inspector: ContractAddress); + fn get_land_inspector(self: @TContractState, land_id: u256) -> ContractAddress; } diff --git a/land_registry/src/land_register.cairo b/land_registry/src/land_register.cairo index 874dbdb4..115476c4 100644 --- a/land_registry/src/land_register.cairo +++ b/land_registry/src/land_register.cairo @@ -13,7 +13,8 @@ pub mod LandRegistryContract { lands: Map::, owner_lands: Map::<(ContractAddress, u256), u256>, owner_land_count: Map::, - land_inspectors: Map::, + land_inspectors: Map::, + lands_assigned_to_inspector: Map::, approved_lands: Map::, land_count: u256, nft_contract: ContractAddress, @@ -29,7 +30,7 @@ pub mod LandRegistryContract { LandTransferred: LandTransferred, LandVerified: LandVerified, LandUpdated: LandUpdated, - InspectorAssigned: InspectorAssigned, + LandInspectorSet: LandInspectorSet, } #[derive(Drop, starknet::Event)] @@ -60,8 +61,8 @@ pub mod LandRegistryContract { area: u256 } - #[derive(Drop, starknet::Event)] - struct InspectorAssigned { + #[derive(Drop, Copy, starknet::Event)] + struct LandInspectorSet { land_id: u256, inspector: ContractAddress, } @@ -87,7 +88,7 @@ pub mod LandRegistryContract { area: area, land_use: land_use, status: LandStatus::Pending, - inspector: Option::None, + inspector: 0.try_into().unwrap(), last_transaction_timestamp: timestamp, }; @@ -198,7 +199,7 @@ pub mod LandRegistryContract { } fn approve_land(ref self: ContractState, land_id: u256) { - assert(InternalFunctions::only_inspector(@self), 'Only inspector can approve'); + assert(InternalFunctions::only_inspector(@self, land_id), 'Only inspector can approve'); self.approved_lands.write(land_id, true); // Mint NFT @@ -214,7 +215,11 @@ pub mod LandRegistryContract { } fn reject_land(ref self: ContractState, land_id: u256) { - assert(InternalFunctions::only_inspector(@self), 'Only inspector can reject'); + assert( + InternalFunctions::only_inspector(@self, land_id) + | InternalFunctions::only_owner(@self, land_id), + 'Only inspector/owner can reject' + ); let mut land = self.lands.read(land_id); assert(land.status == LandStatus::Pending, 'Land must be in Pending status'); land.status = LandStatus::Rejected; @@ -224,15 +229,13 @@ pub mod LandRegistryContract { } fn is_inspector(self: @ContractState, inspector: ContractAddress) -> bool { - self.land_inspectors.read(inspector) - } + let count = self.lands_assigned_to_inspector.read(inspector); - fn add_inspector(ref self: ContractState, inspector: ContractAddress) { - self.land_inspectors.write(inspector, true); - } + if count > 0 { + return true; + } - fn remove_inspector(ref self: ContractState, inspector: ContractAddress) { - self.land_inspectors.write(inspector, false); + return false; } @@ -276,26 +279,22 @@ pub mod LandRegistryContract { } fn set_land_inspector(ref self: ContractState, land_id: u256, inspector: ContractAddress) { - // Ensure inspector is approved - assert(self.land_inspectors.read(inspector), 'Inspector not approved'); + assert( + InternalFunctions::only_owner(@self, land_id), 'Only owner can set an inspector' + ); + let prev_land_count = self.lands_assigned_to_inspector.read(inspector); + self.land_inspectors.write(land_id, inspector); + self.lands_assigned_to_inspector.write(inspector, prev_land_count + 1); - // Ensure land exists - let land = self.lands.read(land_id); - assert(land.status == LandStatus::Pending, 'Land must be pending'); + let prev_land = self.lands.read(land_id); - // Assign inspector - self.land_inspector_assignments.write(land_id, inspector); + self.lands.write(land_id, Land { inspector, ..prev_land }); - // Emit event - self.emit(InspectorAssigned { land_id: land_id, inspector: inspector }); + self.emit(LandInspectorSet { land_id, inspector }); } - fn get_land_inspector(self: @ContractState, land_id: u256) -> Option { - if self.land_inspector_assignments.read(land_id).is_zero() { - Option::None - } else { - Option::Some(self.land_inspector_assignments.read(land_id)) - } + fn get_land_inspector(self: @ContractState, land_id: u256) -> ContractAddress { + self.land_inspectors.read(land_id) } } @@ -306,9 +305,11 @@ pub mod LandRegistryContract { land.owner == get_caller_address() } - fn only_inspector(self: @ContractState) -> bool { + fn only_inspector(self: @ContractState, land_id: u256) -> bool { let caller = get_caller_address(); - self.land_inspectors.read(caller) + let inspector = self.land_inspectors.read(land_id); + + inspector == caller } } } diff --git a/land_registry/tests/test_land_register.cairo b/land_registry/tests/test_land_register.cairo index d46b6b75..56b1650a 100644 --- a/land_registry/tests/test_land_register.cairo +++ b/land_registry/tests/test_land_register.cairo @@ -66,7 +66,7 @@ fn test_can_register_land() { LandStatus::Pending => {}, _ => panic!("Must be Pending"), } - assert(registered_land.inspector.is_none(), 'Should have no inspector'); + assert(registered_land.inspector == 0.try_into().unwrap(), 'Should have no inspector'); } #[test] @@ -247,6 +247,34 @@ fn test_can_get_land_transaction_history() { stop_cheat_block_timestamp(contract_address); } +#[test] +fn test_set_inspector() { + let contract_address = deploy("LandRegistryContract"); + + // Get an instance of the deployed Counter contract + let land_register_dispatcher = ILandRegistryDispatcher { contract_address }; + + // Set up test data + let owner_address = starknet::contract_address_const::<0x123>(); + let inspector_address = starknet::contract_address_const::<0x456>(); + let location: Location = Location { latitude: 1, longitude: 2 }; + let area: u256 = 1000; + let land_use = LandUse::Residential; + + // Start cheating the caller address + start_cheat_caller_address(contract_address, owner_address); + + // Register the land + let land_id = land_register_dispatcher.register_land(location, area, land_use); + + // Get the registered land + land_register_dispatcher.set_land_inspector(land_id, inspector_address); + let registered_land = land_register_dispatcher.get_land(land_id); + + // Assert land details are correct + assert(registered_land.inspector == inspector_address, 'Wrong inspector'); +} + #[test] fn test_can_approve_land() { let contract_address = deploy("LandRegistryContract"); @@ -266,11 +294,13 @@ fn test_can_approve_land() { let land_id = land_register_dispatcher.register_land(location, area, land_use); stop_cheat_caller_address(contract_address); - // Add inspector - start_cheat_caller_address(contract_address, inspector_address); - land_register_dispatcher.add_inspector(inspector_address); + // Set inspector as owner address + start_cheat_caller_address(contract_address, owner_address); + land_register_dispatcher.set_land_inspector(land_id, inspector_address); + stop_cheat_caller_address(contract_address); // Approve land as inspector + start_cheat_caller_address(contract_address, inspector_address); let land_before = land_register_dispatcher.get_land(land_id); assert_eq!(land_before.status, LandStatus::Pending, "Should be pending before approval"); @@ -301,11 +331,13 @@ fn test_can_reject_land() { let land_id = land_register_dispatcher.register_land(location, area, land_use); stop_cheat_caller_address(contract_address); - // Add inspector - start_cheat_caller_address(contract_address, inspector_address); - land_register_dispatcher.add_inspector(inspector_address); + // Set inspector as owner + start_cheat_caller_address(contract_address, owner_address); + land_register_dispatcher.set_land_inspector(land_id, inspector_address); + stop_cheat_caller_address(contract_address); // Reject land as inspector + start_cheat_caller_address(contract_address, inspector_address); let land_before = land_register_dispatcher.get_land(land_id); assert_eq!(land_before.status, LandStatus::Pending, "Should be pending before reject"); @@ -316,3 +348,4 @@ fn test_can_reject_land() { assert_eq!(land_after.status, LandStatus::Rejected, "Should be rejected after"); stop_cheat_caller_address(contract_address); } +