From 92b0f270089518dc71f9210d3887eff784d09306 Mon Sep 17 00:00:00 2001 From: Therock Ani Date: Sun, 26 Oct 2025 00:10:22 +0100 Subject: [PATCH 1/3] updated --- src/MedFiEscrow.sol | 54 +++---- src/MedFiRecords.sol | 138 ++++++++-------- src/MedFiRegistry.sol | 50 +++--- src/interface/Interfaces.sol | 31 ++-- test/MedFiEdgeCaseTest.t.sol | 229 +++++++++++++------------- test/MedFiIntegrationTest.t.sol | 279 ++++++++++++++++---------------- test/MedFiTest.t.sol | 204 ++++++++++++----------- test/mock/MockUSDC.sol | 63 ++++---- 8 files changed, 515 insertions(+), 533 deletions(-) diff --git a/src/MedFiEscrow.sol b/src/MedFiEscrow.sol index 109c544..3e4f088 100644 --- a/src/MedFiEscrow.sol +++ b/src/MedFiEscrow.sol @@ -1,10 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; + import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; -import { IMedFiRegistry } from "./interface/Interfaces.sol"; +import {IMedFiRegistry} from "./interface/Interfaces.sol"; /** * @title MedFiEscrow @@ -12,7 +13,7 @@ import { IMedFiRegistry } from "./interface/Interfaces.sol"; * @notice Enhanced escrow contract for managing medical bookings with USDC payments * @dev Uses ReentrancyGuard, SafeERC20, and enhanced registry checks */ -contract MedFiEscrow is ReentrancyGuard, Ownable{ +contract MedFiEscrow is ReentrancyGuard, Ownable { using SafeERC20 for IERC20; // Custom Errors @@ -71,7 +72,6 @@ contract MedFiEscrow is ReentrancyGuard, Ownable{ event RefundProcessed(uint256 indexed bookingId, address indexed patient, uint256 amount); event FeesWithdrawn(address indexed owner, uint256 amount); - /** * @notice Initializes the contract with the registry address, USDC address, and fee percentage * @param _registry The address of the MedFiRegistry contract @@ -81,7 +81,7 @@ contract MedFiEscrow is ReentrancyGuard, Ownable{ constructor(address _registry, address _usdc, uint256 _feePercent) Ownable(msg.sender) { if (_feePercent > 100) revert InvalidFeePercent(); if (_usdc == address(0)) revert InvalidUSDCAddress(); - + registry = IMedFiRegistry(_registry); usdc = IERC20(_usdc); feePercent = _feePercent; @@ -112,7 +112,7 @@ contract MedFiEscrow is ReentrancyGuard, Ownable{ function bookService(address _provider, uint256 _amount) external { // Enhanced verification - check if provider can perform activities if (!registry.canPerformActivities(_provider)) revert ProviderCannotPerformActivities(); - + // Additional security checks if (registry.isBlacklisted(_provider)) revert ProviderBlacklisted(); if (_amount == 0) revert ZeroBookingAmount(); @@ -138,7 +138,7 @@ contract MedFiEscrow is ReentrancyGuard, Ownable{ emit Booked(bookingCount, msg.sender, _provider, amountAfterFee); - // Transfer USDC from patient to this contract + // Transfer USDC from patient to this contract usdc.safeTransferFrom(msg.sender, address(this), _amount); } @@ -148,7 +148,7 @@ contract MedFiEscrow is ReentrancyGuard, Ownable{ */ function confirmService(uint256 _bookingID) external nonReentrant { if (_bookingID == 0 || _bookingID > bookingCount) revert BookingNotFound(); - + Booking storage booking = bookings[_bookingID]; if (booking.patient != msg.sender) revert OnlyPatient(); if (!booking.isPaid) revert BookingNotPaid(); @@ -158,8 +158,8 @@ contract MedFiEscrow is ReentrancyGuard, Ownable{ if (!registry.canPerformActivities(booking.provider)) revert ProviderCannotPerformActivities(); booking.confirmed = true; // Mark the booking as confirmed - // booking.isPaid = false; // Mark as processed - + // booking.isPaid = false; // Mark as processed + // Update provider statistics providerConfirmedBookings[booking.provider] += 1; providerTotalEarnings[booking.provider] += booking.amount; @@ -167,7 +167,7 @@ contract MedFiEscrow is ReentrancyGuard, Ownable{ emit PaymentConfirmed(_bookingID, booking.patient, booking.provider); emit PaymentReleased(_bookingID, booking.provider, booking.amount); - // Transfer USDC to provider + // Transfer USDC to provider usdc.safeTransfer(booking.provider, booking.amount); } @@ -177,7 +177,7 @@ contract MedFiEscrow is ReentrancyGuard, Ownable{ */ function refundPatient(uint256 _bookingID) external nonReentrant { if (_bookingID == 0 || _bookingID > bookingCount) revert BookingNotFound(); - + Booking storage booking = bookings[_bookingID]; if (booking.provider != msg.sender) revert OnlyProvider(); if (!booking.isPaid) revert BookingNotPaid(); @@ -187,7 +187,7 @@ contract MedFiEscrow is ReentrancyGuard, Ownable{ emit RefundProcessed(_bookingID, booking.patient, booking.amount); - // Transfer USDC back to patient + // Transfer USDC back to patient usdc.safeTransfer(booking.patient, booking.amount); } @@ -197,7 +197,7 @@ contract MedFiEscrow is ReentrancyGuard, Ownable{ */ function emergencyRefund(uint256 _bookingID) external nonReentrant onlyOwner { if (_bookingID == 0 || _bookingID > bookingCount) revert BookingNotFound(); - + Booking storage booking = bookings[_bookingID]; if (!booking.isPaid) revert BookingNotPaid(); @@ -250,16 +250,15 @@ contract MedFiEscrow is ReentrancyGuard, Ownable{ * @return totalEarnings Total earnings from confirmed bookings (in USDC) * @return canReceiveBookings Whether provider can currently receive bookings */ - function getProviderStats(address _provider) external view returns ( - uint256 confirmedBookings, - uint256 totalEarnings, - bool canReceiveBookings - ) { + function getProviderStats(address _provider) + external + view + returns (uint256 confirmedBookings, uint256 totalEarnings, bool canReceiveBookings) + { confirmedBookings = providerConfirmedBookings[_provider]; totalEarnings = providerTotalEarnings[_provider]; - canReceiveBookings = registry.canPerformActivities(_provider) && - !registry.isBlacklisted(_provider); - + canReceiveBookings = registry.canPerformActivities(_provider) && !registry.isBlacklisted(_provider); + return (confirmedBookings, totalEarnings, canReceiveBookings); } @@ -270,12 +269,11 @@ contract MedFiEscrow is ReentrancyGuard, Ownable{ * @return currentFeePercent Current fee percentage * @return usdcBalance Current USDC balance in contract */ - function getContractStats() external view returns ( - uint256 totalBookings, - uint256 totalFeesCollected, - uint256 currentFeePercent, - uint256 usdcBalance - ) { + function getContractStats() + external + view + returns (uint256 totalBookings, uint256 totalFeesCollected, uint256 currentFeePercent, uint256 usdcBalance) + { return (bookingCount, collectedFees, feePercent, usdc.balanceOf(address(this))); } @@ -286,4 +284,4 @@ contract MedFiEscrow is ReentrancyGuard, Ownable{ function getUSDCAddress() external view returns (address) { return address(usdc); } -} \ No newline at end of file +} diff --git a/src/MedFiRecords.sol b/src/MedFiRecords.sol index 7ec4a7f..0583127 100644 --- a/src/MedFiRecords.sol +++ b/src/MedFiRecords.sol @@ -1,11 +1,11 @@ - // SPDX-License-Identifier: MIT + // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; + import {IMedFiRegistry, IMedFiEscrow} from "./interface/Interfaces.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; - /** * @title MedFiRecords * @author Therock Ani @@ -14,7 +14,7 @@ import "@openzeppelin/contracts/access/Ownable.sol"; */ contract MedFiRecords is Ownable { using SafeERC20 for IERC20; - + // Custom Errors error CannotPerformActivities(); error EmptyRecordHash(); @@ -78,7 +78,9 @@ contract MedFiRecords is Ownable { mapping(address => uint256) public professionalRecordCount; // Events - event MedicalRecordAdded(address indexed patient, address indexed professional, uint256 indexed recordId, uint256 timestamp); + event MedicalRecordAdded( + address indexed patient, address indexed professional, uint256 indexed recordId, uint256 timestamp + ); event PatientRecordAdded(address indexed patient, uint256 indexed recordId, uint256 timestamp); event HealthWorkerRated(address indexed rater, address indexed healthWorker, uint8 rating); event AccessGranted(address indexed patient, address indexed professional, uint256 timestamp); @@ -96,8 +98,6 @@ contract MedFiRecords is Ownable { bool addedByProfessional; // True if added by professional, false if by patient } - - // Modifier to check if professional has patient access modifier hasPatientAccess(address _patient) { if (msg.sender != _patient && !patientAccessGrants[_patient][msg.sender]) { @@ -147,7 +147,7 @@ contract MedFiRecords is Ownable { * @param _newRecipient The new fee recipient address */ function updateFeeRecipient(address _newRecipient) external onlyOwner { - if(_newRecipient == address(0)) revert InvalidRecipient(); + if (_newRecipient == address(0)) revert InvalidRecipient(); address oldRecipient = feeRecipient; feeRecipient = _newRecipient; emit FeeRecipientUpdated(oldRecipient, _newRecipient); @@ -159,9 +159,8 @@ contract MedFiRecords is Ownable { */ function _checkAndRenewSubscription(address _patient) internal { // Check if subscription is active - if (!hasActiveSubscription[_patient] || - block.timestamp >= lastPaymentTimestamp[_patient] + SUBSCRIPTION_PERIOD) { - + if (!hasActiveSubscription[_patient] || block.timestamp >= lastPaymentTimestamp[_patient] + SUBSCRIPTION_PERIOD) + { // Subscription expired or never paid, collect fee uint256 balance = usdcToken.balanceOf(_patient); if (balance < STORAGE_FEE) { @@ -207,7 +206,7 @@ contract MedFiRecords is Ownable { // Calculate new expiry time uint256 currentExpiry = lastPaymentTimestamp[msg.sender] + SUBSCRIPTION_PERIOD; uint256 newExpiry; - + // If subscription is still active, add 30 days to remaining time if (hasActiveSubscription[msg.sender] && block.timestamp < currentExpiry) { // Add 30 days to current expiry @@ -266,8 +265,8 @@ contract MedFiRecords is Ownable { * @param _patient The patient's address * @return Array of professional addresses with access */ - function getAuthorizedProfessionals(address _patient) external pure returns (address[] memory) { - // This is a view function for convenience, but in practice, + function getAuthorizedProfessionals(address _patient) external pure returns (address[] memory) { + // This is a view function for convenience, but in practice, // front-end should track granted access events for efficiency revert("Use events to track authorized professionals for gas efficiency"); } @@ -278,9 +277,9 @@ contract MedFiRecords is Ownable { * @param _recordHash The hash of the medical record (stored off-chain) * @dev Requires patient to have active monthly subscription */ - function storeRecordByCareProvider(address _patient, string memory _recordHash) - external - hasPatientAccess(_patient) + function storeRecordByCareProvider(address _patient, string memory _recordHash) + external + hasPatientAccess(_patient) { // Enhanced verification - checks verified, affiliated, active, and not blacklisted if (!registry.canPerformActivities(msg.sender)) revert CannotPerformActivities(); @@ -293,7 +292,7 @@ contract MedFiRecords is Ownable { _checkAndRenewSubscription(_patient); uint256 recordId = recordCounter++; - + // Store the record in gas-efficient mapping records[recordId] = MedicalRecord({ patient: _patient, @@ -352,11 +351,11 @@ contract MedFiRecords is Ownable { * @return Array of medical records for the patient * @dev Does NOT require fee for professionals (they already have access) */ - function getPatientMedicalRecords(address _patient) - external - view - hasPatientAccess(_patient) - returns (MedicalRecord[] memory) + function getPatientMedicalRecords(address _patient) + external + view + hasPatientAccess(_patient) + returns (MedicalRecord[] memory) { return _getMedicalRecordsForPatient(_patient); } @@ -369,13 +368,13 @@ contract MedFiRecords is Ownable { function _getMedicalRecordsForPatient(address _patient) internal view returns (MedicalRecord[] memory) { uint256[] memory recordIds = patientRecordIds[_patient]; uint256 recordCount = recordIds.length; - + MedicalRecord[] memory patientRecords = new MedicalRecord[](recordCount); - + for (uint256 i = 0; i < recordCount; i++) { patientRecords[i] = records[recordIds[i]]; } - + return patientRecords; } @@ -386,13 +385,13 @@ contract MedFiRecords is Ownable { */ function getMedicalRecord(uint256 _recordId) external view returns (MedicalRecord memory) { MedicalRecord memory record = records[_recordId]; - if(record.patient == address(0)) revert RecordDoesNotExist(); - + if (record.patient == address(0)) revert RecordDoesNotExist(); + // Check authorization if (msg.sender != record.patient && !patientAccessGrants[record.patient][msg.sender]) { revert AccessNotGranted(); } - + return record; } @@ -406,7 +405,7 @@ contract MedFiRecords is Ownable { if (msg.sender != _patient && !patientAccessGrants[_patient][msg.sender]) { revert AccessNotGranted(); } - + return patientRecordIds[_patient].length; } @@ -418,10 +417,10 @@ contract MedFiRecords is Ownable { function rateHealthWorker(address _healthWorker, uint8 _rating) external { if (_rating == 0 || _rating > 5) revert InvalidRating(); if (hasRated[msg.sender][_healthWorker]) revert AlreadyRated(); - + // Enhanced check - health worker must be able to perform activities if (!registry.canPerformActivities(_healthWorker)) revert CannotPerformActivities(); - + // Check against confirmed bookings if (escrow.getConfirmedBookingsCount(_healthWorker) <= healthWorkerRatingsCount[_healthWorker]) { revert ExceedsConfirmedBookings(); @@ -455,16 +454,15 @@ contract MedFiRecords is Ownable { * @return totalRatings The total number of ratings * @return canBeRated Whether the worker can currently receive ratings */ - function getHealthWorkerRatingDetails(address _healthWorker) external view returns ( - uint256 averageRating, - uint256 totalRatings, - bool canBeRated - ) { + function getHealthWorkerRatingDetails(address _healthWorker) + external + view + returns (uint256 averageRating, uint256 totalRatings, bool canBeRated) + { totalRatings = healthWorkerRatingsCount[_healthWorker]; averageRating = totalRatings == 0 ? 0 : totalRatingScore[_healthWorker] / totalRatings; - canBeRated = registry.canPerformActivities(_healthWorker) && - !registry.isBlacklisted(_healthWorker); - + canBeRated = registry.canPerformActivities(_healthWorker) && !registry.isBlacklisted(_healthWorker); + return (averageRating, totalRatings, canBeRated); } @@ -474,15 +472,12 @@ contract MedFiRecords is Ownable { * @return recordCount Number of records created by this professional * @return isActive Whether the professional can currently perform activities */ - function getProfessionalStats(address _professional) external view returns ( - uint256 recordCount, - bool isActive - ) { + function getProfessionalStats(address _professional) external view returns (uint256 recordCount, bool isActive) { recordCount = professionalRecordCount[_professional]; isActive = registry.canPerformActivities(_professional); - - // (, , , , , profession, , , , , ) = registry.getProfessional(_professional); - + + // (, , , , , profession, , , , , ) = registry.getProfessional(_professional); + return (recordCount, isActive); } @@ -503,19 +498,17 @@ contract MedFiRecords is Ownable { * @return expiryTime When the current subscription expires (0 if never paid) * @return daysRemaining Number of days remaining in subscription */ - function checkPatientSubscriptionStatus(address _patient) external view returns ( - bool hasBalance, - bool hasAllowance, - bool isActive, - uint256 expiryTime, - uint256 daysRemaining - ) { + function checkPatientSubscriptionStatus(address _patient) + external + view + returns (bool hasBalance, bool hasAllowance, bool isActive, uint256 expiryTime, uint256 daysRemaining) + { hasBalance = usdcToken.balanceOf(_patient) >= STORAGE_FEE; hasAllowance = usdcToken.allowance(_patient, address(this)) >= STORAGE_FEE; - + if (hasActiveSubscription[_patient]) { expiryTime = lastPaymentTimestamp[_patient] + SUBSCRIPTION_PERIOD; - + if (block.timestamp < expiryTime) { isActive = true; uint256 secondsRemaining = expiryTime - block.timestamp; @@ -533,7 +526,7 @@ contract MedFiRecords is Ownable { expiryTime = 0; daysRemaining = 0; } - + return (hasBalance, hasAllowance, isActive, expiryTime, daysRemaining); } @@ -558,21 +551,21 @@ contract MedFiRecords is Ownable { if (!hasActiveSubscription[_patient]) { return 0; } - + uint256 expiryTime = lastPaymentTimestamp[_patient] + SUBSCRIPTION_PERIOD; - + if (block.timestamp >= expiryTime) { return 0; // Expired } - + uint256 secondsRemaining = expiryTime - block.timestamp; daysRemaining = secondsRemaining / 1 days; - + // If there are remaining seconds but less than a full day, show as 1 day if (secondsRemaining % 1 days > 0) { daysRemaining += 1; } - + return daysRemaining; } @@ -583,25 +576,25 @@ contract MedFiRecords is Ownable { * @return hoursRemaining Number of hours left (after full days) * @return totalSecondsRemaining Total seconds remaining */ - function getDetailedTimeRemaining(address _patient) external view returns ( - uint256 daysRemaining, - uint256 hoursRemaining, - uint256 totalSecondsRemaining - ) { + function getDetailedTimeRemaining(address _patient) + external + view + returns (uint256 daysRemaining, uint256 hoursRemaining, uint256 totalSecondsRemaining) + { if (!hasActiveSubscription[_patient]) { return (0, 0, 0); } - + uint256 expiryTime = lastPaymentTimestamp[_patient] + SUBSCRIPTION_PERIOD; - + if (block.timestamp >= expiryTime) { return (0, 0, 0); // Expired } - + totalSecondsRemaining = expiryTime - block.timestamp; daysRemaining = totalSecondsRemaining / 1 days; hoursRemaining = (totalSecondsRemaining % 1 days) / 1 hours; - + return (daysRemaining, hoursRemaining, totalSecondsRemaining); } @@ -611,7 +604,6 @@ contract MedFiRecords is Ownable { * @return Whether the subscription is active */ function isSubscriptionActive(address _patient) external view returns (bool) { - return hasActiveSubscription[_patient] && - block.timestamp < lastPaymentTimestamp[_patient] + SUBSCRIPTION_PERIOD; + return hasActiveSubscription[_patient] && block.timestamp < lastPaymentTimestamp[_patient] + SUBSCRIPTION_PERIOD; } -} \ No newline at end of file +} diff --git a/src/MedFiRegistry.sol b/src/MedFiRegistry.sol index a932888..8e92e50 100644 --- a/src/MedFiRegistry.sol +++ b/src/MedFiRegistry.sol @@ -1,4 +1,4 @@ - // SPDX-License-Identifier: MIT + // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/access/Ownable.sol"; @@ -157,7 +157,7 @@ contract MedFiRegistry is Ownable { */ function registerHospital() external { if (hospitals[msg.sender].registrationTimestamp != 0) revert HospitalAlreadyRegistered(); - + hospitals[msg.sender].registrationTimestamp = block.timestamp; hospitals[msg.sender].isVerified = false; @@ -192,7 +192,7 @@ contract MedFiRegistry is Ownable { */ function approveAffiliation(address _professionalAddress) external onlyVerifiedHospital(msg.sender) { if (_professionalAddress == address(0)) revert InvalidHospitalAddress(); - + pendingAffiliations[msg.sender][_professionalAddress] = true; emit AffiliationApproved(msg.sender, _professionalAddress); } @@ -200,7 +200,10 @@ contract MedFiRegistry is Ownable { /** * @notice Allows a verified hospital to approve multiple professionals for affiliation. */ - function approveMultipleAffiliations(address[] memory _professionalAddresses) external onlyVerifiedHospital(msg.sender) { + function approveMultipleAffiliations(address[] memory _professionalAddresses) + external + onlyVerifiedHospital(msg.sender) + { for (uint256 i = 0; i < _professionalAddresses.length; i++) { address professionalAddress = _professionalAddresses[i]; if (professionalAddress != address(0)) { @@ -221,7 +224,10 @@ contract MedFiRegistry is Ownable { /** * @notice Allows a hospital to revoke affiliation approval for multiple professionals. */ - function revokeMultipleAffiliationApprovals(address[] memory _professionalAddresses) external onlyVerifiedHospital(msg.sender) { + function revokeMultipleAffiliationApprovals(address[] memory _professionalAddresses) + external + onlyVerifiedHospital(msg.sender) + { for (uint256 i = 0; i < _professionalAddresses.length; i++) { address professionalAddress = _professionalAddresses[i]; if (professionalAddress != address(0)) { @@ -239,7 +245,7 @@ contract MedFiRegistry is Ownable { if (professionals[msg.sender].professionalAddress != address(0)) { revert ProfessionalAlreadyRegistered(); } - + if (isBlacklisted[msg.sender]) { revert ProfessionalIsBlacklisted(); } @@ -285,7 +291,7 @@ contract MedFiRegistry is Ownable { */ function unaffiliateProfessional(address _professionalAddress) external { Professional storage professional = professionals[_professionalAddress]; - + if (professional.affiliatedHospital != msg.sender) revert UnauthorizedAffiliation(); if (!professional.isAffiliated) revert ProfessionalNotAffiliated(); @@ -306,7 +312,7 @@ contract MedFiRegistry is Ownable { address professionalAddress = _professionalAddresses[i]; if (professionalAddress != address(0)) { Professional storage professional = professionals[professionalAddress]; - + if (professional.affiliatedHospital == msg.sender && professional.isAffiliated) { professional.isAffiliated = false; professional.isVerified = false; @@ -325,11 +331,11 @@ contract MedFiRegistry is Ownable { */ function changeAffiliation(address _newHospitalAddress) external { Professional storage professional = professionals[msg.sender]; - + if (professional.professionalAddress == address(0)) { revert ProfessionalNotRegistered(); } - + if (isBlacklisted[msg.sender]) { revert ProfessionalIsBlacklisted(); } @@ -390,12 +396,12 @@ contract MedFiRegistry is Ownable { */ function blacklistProfessional(address _professional) external onlyAdmin { isBlacklisted[_professional] = true; - + if (professionals[_professional].professionalAddress != address(0)) { professionals[_professional].isVerified = false; professionals[_professional].isActive = false; } - + emit ProfessionalBlacklisted(_professional); } @@ -404,12 +410,12 @@ contract MedFiRegistry is Ownable { */ function unblacklistProfessional(address _professional) external onlyAdmin { isBlacklisted[_professional] = false; - - if (professionals[_professional].professionalAddress != address(0) && - professionals[_professional].isAffiliated) { + + if (professionals[_professional].professionalAddress != address(0) && professionals[_professional].isAffiliated) + { professionals[_professional].isActive = true; } - + emit ProfessionalUnblacklisted(_professional); } @@ -448,15 +454,9 @@ contract MedFiRegistry is Ownable { /** * @notice Gets hospital details by address. */ - function getHospital(address _hospital) external view returns ( - bool verified, - uint256 registrationTimestamp - ) { + function getHospital(address _hospital) external view returns (bool verified, uint256 registrationTimestamp) { Hospital storage hospital = hospitals[_hospital]; - return ( - hospital.isVerified, - hospital.registrationTimestamp - ); + return (hospital.isVerified, hospital.registrationTimestamp); } /** @@ -493,4 +493,4 @@ contract MedFiRegistry is Ownable { function isProfessionalAffiliated(address _hospital, address _professional) external view returns (bool) { return hospitals[_hospital].affiliatedProfessionals[_professional]; } -} \ No newline at end of file +} diff --git a/src/interface/Interfaces.sol b/src/interface/Interfaces.sol index 030b0d9..05f7203 100644 --- a/src/interface/Interfaces.sol +++ b/src/interface/Interfaces.sol @@ -5,23 +5,26 @@ pragma solidity ^0.8.20; interface IMedFiRegistry { function isVerified(address) external view returns (bool); function canPerformActivities(address) external view returns (bool); - function getProfessional(address) external view returns ( - address professionalAddress, - address affiliatedHospital, - bool isVerified, - bool isAffiliated, - string memory name, - string memory profession, - string memory licenseNumber, - string memory specialization, - string memory contactInfo, - uint256 registrationTimestamp, - bool isActive - ); + function getProfessional(address) + external + view + returns ( + address professionalAddress, + address affiliatedHospital, + bool isVerified, + bool isAffiliated, + string memory name, + string memory profession, + string memory licenseNumber, + string memory specialization, + string memory contactInfo, + uint256 registrationTimestamp, + bool isActive + ); function isBlacklisted(address) external view returns (bool); } // Interface for the MedFiEscrow contract to get the count of confirmed bookings for a provider interface IMedFiEscrow { function getConfirmedBookingsCount(address _provider) external view returns (uint256); -} \ No newline at end of file +} diff --git a/test/MedFiEdgeCaseTest.t.sol b/test/MedFiEdgeCaseTest.t.sol index 19e75d6..0f33f1c 100644 --- a/test/MedFiEdgeCaseTest.t.sol +++ b/test/MedFiEdgeCaseTest.t.sol @@ -28,7 +28,7 @@ contract MedFiEdgeCaseTest is Test { escrow = new MedFiEscrow(address(registry), address(usdc), 5); records = new MedFiRecords(address(registry), address(escrow), address(usdc)); registry.addAdmin(admin); - usdc.mint(patient, 10000 * 10**6); + usdc.mint(patient, 10000 * 10 ** 6); vm.stopPrank(); } @@ -37,7 +37,7 @@ contract MedFiEdgeCaseTest is Test { function testDoubleRegistrationAttempts() public { vm.startPrank(hospital); registry.registerHospital(); - + vm.expectRevert(MedFiRegistry.HospitalAlreadyRegistered.selector); registry.registerHospital(); vm.stopPrank(); @@ -52,7 +52,7 @@ contract MedFiEdgeCaseTest is Test { function testUnverifyUnverifiedHospital() public { vm.prank(hospital); registry.registerHospital(); - + vm.prank(admin); vm.expectRevert(MedFiRegistry.HospitalNotVerified.selector); registry.unverifyHospital(hospital); @@ -60,7 +60,7 @@ contract MedFiEdgeCaseTest is Test { function testProfessionalRegistrationWithoutApproval() public { _setupVerifiedHospital(); - + vm.prank(doctor); vm.expectRevert(MedFiRegistry.UnauthorizedAffiliation.selector); registry.registerProfessional(hospital); @@ -69,11 +69,11 @@ contract MedFiEdgeCaseTest is Test { function testProfessionalRegistrationWithUnverifiedHospital() public { vm.prank(hospital); registry.registerHospital(); - + vm.prank(hospital); vm.expectRevert(MedFiRegistry.HospitalNotVerified.selector); registry.approveAffiliation(doctor); - + vm.prank(doctor); vm.expectRevert(MedFiRegistry.HospitalNotVerified.selector); registry.registerProfessional(hospital); @@ -81,13 +81,13 @@ contract MedFiEdgeCaseTest is Test { function testChangeAffiliationToSameHospital() public { _setupVerifiedDoctor(); - + vm.prank(hospital); registry.approveAffiliation(doctor); - + vm.prank(doctor); registry.changeAffiliation(hospital); - + MedFiRegistry.Professional memory prof = registry.getProfessional(doctor); assertEq(prof.affiliatedHospital, hospital); assertFalse(prof.isVerified); @@ -95,14 +95,14 @@ contract MedFiEdgeCaseTest is Test { function testUnaffiliateNonAffiliatedProfessional() public { _setupVerifiedDoctor(); - + address otherHospital = makeAddr("otherHospital"); vm.prank(otherHospital); registry.registerHospital(); - + vm.prank(admin); registry.verifyHospital(otherHospital); - + vm.prank(otherHospital); vm.expectRevert(MedFiRegistry.UnauthorizedAffiliation.selector); registry.unaffiliateProfessional(doctor); @@ -110,13 +110,13 @@ contract MedFiEdgeCaseTest is Test { function testBlacklistAfterUnaffiliation() public { _setupVerifiedDoctor(); - + vm.prank(hospital); registry.unaffiliateProfessional(doctor); - + vm.prank(admin); registry.blacklistProfessional(doctor); - + assertTrue(registry.isBlacklisted(doctor)); assertFalse(registry.canPerformActivities(doctor)); } @@ -125,11 +125,11 @@ contract MedFiEdgeCaseTest is Test { function testBookingWithZeroAddress() public { _setupVerifiedDoctor(); - - uint256 bookingAmount = 100 * 10**6; + + uint256 bookingAmount = 100 * 10 ** 6; vm.prank(patient); usdc.approve(address(escrow), bookingAmount); - + vm.prank(patient); vm.expectRevert(); escrow.bookService(address(0), bookingAmount); @@ -143,7 +143,7 @@ contract MedFiEdgeCaseTest is Test { function testConfirmBookingAsWrongPatient() public { _setupBooking(); - + address wrongPatient = makeAddr("wrongPatient"); vm.prank(wrongPatient); vm.expectRevert(MedFiEscrow.OnlyPatient.selector); @@ -152,7 +152,7 @@ contract MedFiEdgeCaseTest is Test { function testRefundAsWrongProvider() public { _setupBooking(); - + address wrongProvider = makeAddr("wrongProvider"); vm.prank(wrongProvider); vm.expectRevert(MedFiEscrow.OnlyProvider.selector); @@ -161,10 +161,10 @@ contract MedFiEdgeCaseTest is Test { function testDoubleConfirmation() public { _setupBooking(); - + vm.prank(patient); escrow.confirmService(1); - + vm.prank(patient); vm.expectRevert(MedFiEscrow.BookingAlreadyConfirmed.selector); escrow.confirmService(1); @@ -172,10 +172,10 @@ contract MedFiEdgeCaseTest is Test { function testRefundAfterConfirmation() public { _setupBooking(); - + vm.prank(patient); escrow.confirmService(1); - + vm.prank(doctor); vm.expectRevert(MedFiEscrow.BookingAlreadyConfirmed.selector); escrow.refundPatient(1); @@ -189,14 +189,14 @@ contract MedFiEdgeCaseTest is Test { function testBookingAfterProviderLosesVerification() public { _setupVerifiedDoctor(); - + vm.prank(admin); registry.unverifyProfessional(doctor); - - uint256 bookingAmount = 100 * 10**6; + + uint256 bookingAmount = 100 * 10 ** 6; vm.prank(patient); usdc.approve(address(escrow), bookingAmount); - + vm.prank(patient); vm.expectRevert(MedFiEscrow.ProviderCannotPerformActivities.selector); escrow.bookService(doctor, bookingAmount); @@ -207,10 +207,10 @@ contract MedFiEdgeCaseTest is Test { function testStoreRecordWithEmptyHash() public { _setupVerifiedDoctor(); _setupPatientSubscription(); - + vm.prank(patient); records.grantAccess(doctor); - + vm.prank(doctor); vm.expectRevert(MedFiRecords.EmptyRecordHash.selector); records.storeRecordByCareProvider(patient, ""); @@ -219,13 +219,13 @@ contract MedFiEdgeCaseTest is Test { function testGrantAccessToUnverifiedProfessional() public { _setupVerifiedHospital(); _setupPatientSubscription(); - + vm.prank(hospital); registry.approveAffiliation(doctor); - + vm.prank(doctor); registry.registerProfessional(hospital); - + vm.prank(patient); vm.expectRevert(MedFiRecords.CannotPerformActivities.selector); records.grantAccess(doctor); @@ -234,10 +234,10 @@ contract MedFiEdgeCaseTest is Test { function testGrantAccessAlreadyGranted() public { _setupVerifiedDoctor(); _setupPatientSubscription(); - + vm.prank(patient); records.grantAccess(doctor); - + vm.prank(patient); vm.expectRevert(MedFiRecords.AccessAlreadyGranted.selector); records.grantAccess(doctor); @@ -245,7 +245,7 @@ contract MedFiEdgeCaseTest is Test { function testRevokeAccessNotGranted() public { _setupVerifiedDoctor(); - + vm.prank(patient); vm.expectRevert(MedFiRecords.AccessNotRevoked.selector); records.revokeAccess(doctor); @@ -254,10 +254,10 @@ contract MedFiEdgeCaseTest is Test { function testUnauthorizedGetPatientMedicalRecords() public { _setupVerifiedDoctor(); _setupPatientSubscription(); - + vm.prank(patient); records.addMedicalRecord("QmPatientRecord1"); - + vm.prank(doctor); vm.expectRevert(MedFiRecords.AccessNotGranted.selector); records.getPatientMedicalRecords(patient); @@ -266,10 +266,10 @@ contract MedFiEdgeCaseTest is Test { function testGetMedicalRecordUnauthorized() public { _setupVerifiedDoctor(); _setupPatientSubscription(); - + vm.prank(patient); records.addMedicalRecord("QmTestRecord"); - + vm.prank(doctor); vm.expectRevert(MedFiRecords.AccessNotGranted.selector); records.getMedicalRecord(1); @@ -278,10 +278,10 @@ contract MedFiEdgeCaseTest is Test { function testGetPatientRecordCountUnauthorized() public { _setupVerifiedDoctor(); _setupPatientSubscription(); - + vm.prank(patient); records.addMedicalRecord("QmRecord1"); - + vm.prank(doctor); vm.expectRevert(MedFiRecords.AccessNotGranted.selector); records.getPatientRecordCount(patient); @@ -294,17 +294,17 @@ contract MedFiEdgeCaseTest is Test { } function testRateWithInvalidRating() public { - // _setupVerifiedDoctor(); + // _setupVerifiedDoctor(); _createConfirmedBooking(); - + vm.prank(patient); vm.expectRevert(MedFiRecords.InvalidRating.selector); records.rateHealthWorker(doctor, 0); - + vm.prank(patient); vm.expectRevert(MedFiRecords.InvalidRating.selector); records.rateHealthWorker(doctor, 6); - + vm.prank(patient); vm.expectRevert(MedFiRecords.InvalidRating.selector); records.rateHealthWorker(doctor, 255); @@ -312,13 +312,13 @@ contract MedFiEdgeCaseTest is Test { function testRateUnverifiedHealthWorker() public { _setupVerifiedHospital(); - + vm.prank(hospital); registry.approveAffiliation(doctor); - + vm.prank(doctor); registry.registerProfessional(hospital); - + vm.prank(patient); vm.expectRevert(MedFiRecords.CannotPerformActivities.selector); records.rateHealthWorker(doctor, 5); @@ -326,10 +326,10 @@ contract MedFiEdgeCaseTest is Test { function testMultipleRatingAttempts() public { _createConfirmedBooking(); - + vm.prank(patient); records.rateHealthWorker(doctor, 5); - + vm.prank(patient); vm.expectRevert(MedFiRecords.AlreadyRated.selector); records.rateHealthWorker(doctor, 4); @@ -338,13 +338,13 @@ contract MedFiEdgeCaseTest is Test { function testStoreRecordAfterAccessRevoked() public { _setupVerifiedDoctor(); _setupPatientSubscription(); - + vm.prank(patient); records.grantAccess(doctor); - + vm.prank(patient); records.revokeAccess(doctor); - + vm.prank(doctor); vm.expectRevert(MedFiRecords.AccessNotGranted.selector); records.storeRecordByCareProvider(patient, "QmTestRecord"); @@ -353,13 +353,13 @@ contract MedFiEdgeCaseTest is Test { function testStoreRecordAfterDoctorBlacklisted() public { _setupVerifiedDoctor(); _setupPatientSubscription(); - + vm.prank(patient); records.grantAccess(doctor); - + vm.prank(admin); registry.blacklistProfessional(doctor); - + vm.prank(doctor); vm.expectRevert(MedFiRecords.CannotPerformActivities.selector); records.storeRecordByCareProvider(patient, "QmTestRecord"); @@ -368,38 +368,38 @@ contract MedFiEdgeCaseTest is Test { function testAccessControlWithMultipleProfessionals() public { _setupVerifiedHospital(); _setupPatientSubscription(); - + address doctor2 = makeAddr("doctor2"); - + vm.prank(hospital); registry.approveAffiliation(doctor); vm.prank(hospital); registry.approveAffiliation(doctor2); - + vm.prank(doctor); registry.registerProfessional(hospital); vm.prank(doctor2); registry.registerProfessional(hospital); - + vm.prank(admin); registry.verifyProfessional(doctor); vm.prank(admin); registry.verifyProfessional(doctor2); - + vm.prank(patient); records.grantAccess(doctor); - + vm.prank(doctor); records.storeRecordByCareProvider(patient, "QmRecord1"); - + vm.prank(doctor2); vm.expectRevert(MedFiRecords.AccessNotGranted.selector); records.storeRecordByCareProvider(patient, "QmRecord2"); - + vm.prank(doctor); MedFiRecords.MedicalRecord[] memory records1 = records.getPatientMedicalRecords(patient); assertEq(records1.length, 1); - + vm.prank(doctor2); vm.expectRevert(MedFiRecords.AccessNotGranted.selector); records.getPatientMedicalRecords(patient); @@ -413,7 +413,7 @@ contract MedFiEdgeCaseTest is Test { function testGrantAccessWithoutSubscription() public { _setupVerifiedDoctor(); - + vm.prank(patient); vm.expectRevert(MedFiRecords.InsufficientUSDCBalance.selector); records.grantAccess(doctor); @@ -421,10 +421,10 @@ contract MedFiEdgeCaseTest is Test { function testSubscriptionExpiry() public { _setupPatientSubscription(); - + vm.warp(block.timestamp + 31 days); - - (,,bool isActive,,) = records.checkPatientSubscriptionStatus(patient); + + (,, bool isActive,,) = records.checkPatientSubscriptionStatus(patient); assertFalse(isActive); } @@ -432,34 +432,34 @@ contract MedFiEdgeCaseTest is Test { function testUSDCFaucetLimits() public { address newUser = makeAddr("newUser"); - + vm.prank(newUser); - usdc.faucet(1000 * 10**6); - assertEq(usdc.balanceOf(newUser), 1000 * 10**6); - + usdc.faucet(1000 * 10 ** 6); + assertEq(usdc.balanceOf(newUser), 1000 * 10 ** 6); + vm.prank(owner); - usdc.mint(newUser, 9001 * 10**6); - + usdc.mint(newUser, 9001 * 10 ** 6); + vm.prank(newUser); vm.expectRevert("You already have enough test USDC"); - usdc.faucet(1 * 10**6); - + usdc.faucet(1 * 10 ** 6); + address anotherUser = makeAddr("anotherUser"); vm.prank(anotherUser); vm.expectRevert("Cannot request more than 1000 USDC at once"); - usdc.faucet(1001 * 10**6); + usdc.faucet(1001 * 10 ** 6); } function testUSDCBatchMintMismatch() public { address[] memory recipients = new address[](2); uint256[] memory amounts = new uint256[](3); - + recipients[0] = makeAddr("user1"); recipients[1] = makeAddr("user2"); - amounts[0] = 100 * 10**6; - amounts[1] = 200 * 10**6; - amounts[2] = 300 * 10**6; - + amounts[0] = 100 * 10 ** 6; + amounts[1] = 200 * 10 ** 6; + amounts[2] = 300 * 10 ** 6; + vm.prank(owner); vm.expectRevert("Arrays length mismatch"); usdc.batchMint(recipients, amounts); @@ -469,7 +469,7 @@ contract MedFiEdgeCaseTest is Test { address testUser = makeAddr("testUser"); vm.prank(owner); usdc.mint(testUser, 1234560000); - + string memory formatted = usdc.getFormattedBalance(testUser); assertGt(bytes(formatted).length, 0); } @@ -478,10 +478,10 @@ contract MedFiEdgeCaseTest is Test { function testReentrancyProtection() public { _setupBooking(); - + vm.prank(patient); escrow.confirmService(1); - + assertTrue(escrow.getBooking(1).confirmed); } @@ -490,20 +490,20 @@ contract MedFiEdgeCaseTest is Test { function testGasOptimizedRecordRetrieval() public { _setupVerifiedDoctor(); _setupPatientSubscription(); - + vm.prank(patient); records.grantAccess(doctor); - - for (uint i = 0; i < 10; i++) { + + for (uint256 i = 0; i < 10; i++) { vm.prank(patient); records.addMedicalRecord(string(abi.encodePacked("QmRecord", vm.toString(i)))); } - + uint256 gasBefore = gasleft(); vm.prank(patient); MedFiRecords.MedicalRecord[] memory patientRecords = records.getMyMedicalRecords(); uint256 gasUsed = gasBefore - gasleft(); - + assertEq(patientRecords.length, 10); assertLt(gasUsed, 500000); } @@ -512,25 +512,25 @@ contract MedFiEdgeCaseTest is Test { function testStateConsistencyAfterOperations() public { _setupVerifiedDoctor(); - - uint256 bookingAmount = 100 * 10**6; + + uint256 bookingAmount = 100 * 10 ** 6; vm.prank(patient); usdc.approve(address(escrow), bookingAmount); - + vm.prank(patient); escrow.bookService(doctor, bookingAmount); - + (uint256 beforeConfirmed, uint256 beforeEarnings,) = escrow.getProviderStats(doctor); assertEq(beforeConfirmed, 0); assertEq(beforeEarnings, 0); - + vm.prank(patient); escrow.confirmService(1); - + (uint256 afterConfirmed, uint256 afterEarnings,) = escrow.getProviderStats(doctor); assertEq(afterConfirmed, 1); assertGt(afterEarnings, 0); - + MedFiEscrow.Booking memory booking = escrow.getBooking(1); assertTrue(booking.confirmed); assertTrue(booking.isPaid); @@ -539,20 +539,19 @@ contract MedFiEdgeCaseTest is Test { function testContractStatsAccuracy() public { _setupVerifiedDoctor(); - + (uint256 totalBookings1, uint256 totalFees1,,) = escrow.getContractStats(); assertEq(totalBookings1, 0); assertEq(totalFees1, 0); - - uint256 bookingAmount = 100 * 10**6; + + uint256 bookingAmount = 100 * 10 ** 6; vm.prank(patient); usdc.approve(address(escrow), bookingAmount); - + vm.prank(patient); escrow.bookService(doctor, bookingAmount); - - (uint256 totalBookings2, uint256 totalFees2, uint256 feePercent2, uint256 balance2) = - escrow.getContractStats(); + + (uint256 totalBookings2, uint256 totalFees2, uint256 feePercent2, uint256 balance2) = escrow.getContractStats(); assertEq(totalBookings2, 1); assertEq(totalFees2, (bookingAmount * 5) / 100); assertEq(feePercent2, 5); @@ -564,38 +563,38 @@ contract MedFiEdgeCaseTest is Test { function _setupVerifiedHospital() internal { vm.prank(hospital); registry.registerHospital(); - + vm.prank(admin); registry.verifyHospital(hospital); } function _setupVerifiedDoctor() internal { _setupVerifiedHospital(); - + vm.prank(hospital); registry.approveAffiliation(doctor); - + vm.prank(doctor); registry.registerProfessional(hospital); - + vm.prank(admin); registry.verifyProfessional(doctor); } function _setupBooking() internal { _setupVerifiedDoctor(); - - uint256 bookingAmount = 100 * 10**6; + + uint256 bookingAmount = 100 * 10 ** 6; vm.prank(patient); usdc.approve(address(escrow), bookingAmount); - + vm.prank(patient); escrow.bookService(doctor, bookingAmount); } function _createConfirmedBooking() internal { _setupBooking(); - + vm.prank(patient); escrow.confirmService(1); } @@ -606,4 +605,4 @@ contract MedFiEdgeCaseTest is Test { records.renewSubscription(); vm.stopPrank(); } -} \ No newline at end of file +} diff --git a/test/MedFiIntegrationTest.t.sol b/test/MedFiIntegrationTest.t.sol index 333b0fd..f754734 100644 --- a/test/MedFiIntegrationTest.t.sol +++ b/test/MedFiIntegrationTest.t.sol @@ -24,9 +24,9 @@ contract MedFiIntegrationTest is Test { address public patient3 = makeAddr("patient3"); uint256 public constant FEE_PERCENT = 10; // 10% for testing - uint256 public constant INITIAL_USDC_AMOUNT = 10000 * 10**6; // 10,000 USDC - uint256 public constant ONE_USDC_AMOUNT = 1 * 10**6; // 1 USDC - uint256 public constant STORAGE_FEE = 1 * 10**6; // 1 USDC + uint256 public constant INITIAL_USDC_AMOUNT = 10000 * 10 ** 6; // 10,000 USDC + uint256 public constant ONE_USDC_AMOUNT = 1 * 10 ** 6; // 1 USDC + uint256 public constant STORAGE_FEE = 1 * 10 ** 6; // 1 USDC function setUp() public { vm.startPrank(owner); @@ -49,37 +49,37 @@ contract MedFiIntegrationTest is Test { function testCompletePatientJourney() public { // 1. Setup hospital and doctor _setupVerifiedDoctor(); - + // 2. Patient approves USDC for all operations vm.prank(patient1); usdc.approve(address(records), STORAGE_FEE * 10); // For subscription vm.prank(patient1); - usdc.approve(address(escrow), 1000 * 10**6); // For bookings - + usdc.approve(address(escrow), 1000 * 10 ** 6); // For bookings + // 3. Patient grants access to doctor (triggers subscription payment) vm.prank(patient1); records.grantAccess(doctor1); - + // 4. Patient books service - uint256 bookingAmount = 200 * 10**6; // 200 USDC - + uint256 bookingAmount = 200 * 10 ** 6; // 200 USDC + vm.prank(patient1); escrow.bookService(doctor1, bookingAmount); - + // 5. Doctor adds medical record vm.prank(doctor1); records.storeRecordByCareProvider(patient1, "QmHealthRecord123"); - + // 6. Patient confirms service vm.prank(patient1); escrow.confirmService(1); - + // 7. Patient rates doctor vm.prank(patient1); records.rateHealthWorker(doctor1, 5); - (uint256 recordCount, ) = records.getProfessionalStats(doctor1); - + (uint256 recordCount,) = records.getProfessionalStats(doctor1); + // Verify final state assertTrue(escrow.getBooking(1).confirmed); assertEq(records.getAverageRating(doctor1), 5); @@ -91,52 +91,52 @@ contract MedFiIntegrationTest is Test { function testMultipleBookingsAndRatings() public { _setupVerifiedDoctor(); - + // Patient approves USDC for all operations vm.prank(patient1); usdc.approve(address(records), STORAGE_FEE * 10); vm.prank(patient2); usdc.approve(address(records), STORAGE_FEE * 10); - + // Patients grant access vm.prank(patient1); records.grantAccess(doctor1); vm.prank(patient2); records.grantAccess(doctor1); - + // Create multiple bookings - uint256 bookingAmount = 100 * 10**6; - for (uint i = 0; i < 3; i++) { + uint256 bookingAmount = 100 * 10 ** 6; + for (uint256 i = 0; i < 3; i++) { address patient = i == 0 ? patient1 : patient2; - + vm.prank(patient); usdc.approve(address(escrow), bookingAmount); - + vm.prank(patient); escrow.bookService(doctor1, bookingAmount); } - + // Confirm all bookings vm.prank(patient1); escrow.confirmService(1); - + vm.prank(patient2); escrow.confirmService(2); - + vm.prank(patient2); escrow.confirmService(3); - + // Rate the doctor vm.prank(patient1); records.rateHealthWorker(doctor1, 4); - + vm.prank(patient2); records.rateHealthWorker(doctor1, 5); - + // Verify stats (uint256 confirmedBookings, uint256 totalEarnings,) = escrow.getProviderStats(doctor1); assertEq(confirmedBookings, 3); - + (uint256 avgRating, uint256 totalRatings,) = records.getHealthWorkerRatingDetails(doctor1); assertEq(totalRatings, 2); assertEq(avgRating, 4); // (4 + 5) / 2 = 4 (integer division) @@ -145,31 +145,31 @@ contract MedFiIntegrationTest is Test { function testHospitalAffiliationChanges() public { // Setup two hospitals _setupTwoHospitals(); - + // Doctor initially affiliates with hospital1 vm.prank(hospital1); registry.approveAffiliation(doctor1); - + vm.prank(doctor1); registry.registerProfessional(hospital1); - + vm.prank(admin); registry.verifyProfessional(doctor1); - + // Doctor changes affiliation to hospital2 vm.prank(hospital2); registry.approveAffiliation(doctor1); - + vm.prank(doctor1); registry.changeAffiliation(hospital2); - + // Verify affiliation change MedFiRegistry.Professional memory professional = registry.getProfessional(doctor1); assertEq(professional.affiliatedHospital, hospital2); assertTrue(professional.isAffiliated); assertFalse(professional.isVerified); // Should lose verification assertTrue(professional.isActive); - + // Verify old hospital no longer has professional assertFalse(registry.isProfessionalAffiliated(hospital1, doctor1)); assertTrue(registry.isProfessionalAffiliated(hospital2, doctor1)); @@ -177,105 +177,105 @@ contract MedFiIntegrationTest is Test { function testBlacklistingImpactOnExistingBookings() public { _setupVerifiedDoctor(); - + // Patient approves USDC for subscription vm.prank(patient1); usdc.approve(address(records), STORAGE_FEE * 10); - + // Patient grants access vm.prank(patient1); records.grantAccess(doctor1); - + // Create booking - uint256 bookingAmount = 100 * 10**6; + uint256 bookingAmount = 100 * 10 ** 6; vm.prank(patient1); usdc.approve(address(escrow), bookingAmount); - + vm.prank(patient1); escrow.bookService(doctor1, bookingAmount); - + // Blacklist doctor vm.prank(admin); registry.blacklistProfessional(doctor1); - + // Patient should not be able to confirm service with blacklisted doctor vm.prank(patient1); vm.expectRevert(MedFiEscrow.ProviderCannotPerformActivities.selector); escrow.confirmService(1); - + // But owner can emergency refund vm.prank(owner); escrow.emergencyRefund(1); - + assertFalse(escrow.getBooking(1).isPaid); } function testPatientAccessManagement() public { _setupTwoHospitals(); - + // Setup two doctors from different hospitals vm.prank(hospital1); registry.approveAffiliation(doctor1); vm.prank(hospital2); registry.approveAffiliation(doctor2); - + vm.prank(doctor1); registry.registerProfessional(hospital1); vm.prank(doctor2); registry.registerProfessional(hospital2); - + vm.prank(admin); registry.verifyProfessional(doctor1); vm.prank(admin); registry.verifyProfessional(doctor2); - + // Patient approves USDC for subscription vm.prank(patient1); usdc.approve(address(records), STORAGE_FEE * 10); - + // Patient grants access to doctor1 only vm.prank(patient1); records.grantAccess(doctor1); - + // Verify access states assertTrue(records.hasAccess(patient1, doctor1)); assertFalse(records.hasAccess(patient1, doctor2)); - + // Doctor1 can store records vm.prank(doctor1); records.storeRecordByCareProvider(patient1, "QmCardiacRecord"); - + // Doctor2 cannot store records vm.prank(doctor2); vm.expectRevert(MedFiRecords.AccessNotGranted.selector); records.storeRecordByCareProvider(patient1, "QmNeuroRecord"); - + // Grant access to doctor2 vm.prank(patient1); records.grantAccess(doctor2); - + // Now doctor2 can store records vm.prank(doctor2); records.storeRecordByCareProvider(patient1, "QmNeuroRecord"); - + // Both doctors can view patient records vm.prank(doctor1); MedFiRecords.MedicalRecord[] memory records1 = records.getPatientMedicalRecords(patient1); assertEq(records1.length, 2); - + vm.prank(doctor2); MedFiRecords.MedicalRecord[] memory records2 = records.getPatientMedicalRecords(patient1); assertEq(records2.length, 2); - + // Revoke access from doctor1 vm.prank(patient1); records.revokeAccess(doctor1); - + // Doctor1 can no longer view records vm.prank(doctor1); vm.expectRevert(MedFiRecords.AccessNotGranted.selector); records.getPatientMedicalRecords(patient1); - + // But doctor2 still can vm.prank(doctor2); MedFiRecords.MedicalRecord[] memory records3 = records.getPatientMedicalRecords(patient1); @@ -284,26 +284,26 @@ contract MedFiIntegrationTest is Test { function testSubscriptionRenewalFlow() public { _setupVerifiedDoctor(); - + // Patient approves USDC for initial subscription vm.prank(patient1); usdc.approve(address(records), STORAGE_FEE); - + // Grant access (first subscription payment) vm.prank(patient1); records.grantAccess(doctor1); - + uint256 firstExpiry = records.getSubscriptionExpiry(patient1); assertTrue(records.isSubscriptionActive(patient1)); - + // Approve for manual renewal vm.prank(patient1); usdc.approve(address(records), STORAGE_FEE); - + // Manually renew subscription vm.prank(patient1); records.renewSubscription(); - + uint256 secondExpiry = records.getSubscriptionExpiry(patient1); assertGt(secondExpiry, firstExpiry); assertTrue(records.isSubscriptionActive(patient1)); @@ -311,83 +311,82 @@ contract MedFiIntegrationTest is Test { function testSubscriptionExpiryAndRenewal() public { _setupVerifiedDoctor(); - + // Patient approves and pays initial subscription vm.prank(patient3); usdc.approve(address(records), STORAGE_FEE); - + vm.prank(patient3); records.grantAccess(doctor1); - + // Fast forward past subscription period vm.warp(block.timestamp + 31 days); - + // Subscription should be expired assertFalse(records.isSubscriptionActive(patient3)); - + // Patient needs to renew to access records vm.prank(patient3); usdc.approve(address(records), STORAGE_FEE); - + vm.prank(patient3); vm.expectRevert(MedFiRecords.InsufficientUSDCBalance.selector); records.addMedicalRecord("QmNewRecord"); - + usdc.mint(patient3, ONE_USDC_AMOUNT); // Renew subscription first vm.prank(patient3); records.renewSubscription(); - + // Now can add records vm.prank(patient3); records.addMedicalRecord("QmNewRecord"); - + assertTrue(records.isSubscriptionActive(patient3)); } - function testSubscriptionExpiryAndAutoRenewal() public { + function testSubscriptionExpiryAndAutoRenewal() public { _setupVerifiedDoctor(); - + // Patient approves and pays initial subscription vm.prank(patient2); usdc.approve(address(records), STORAGE_FEE); - + vm.prank(patient2); records.grantAccess(doctor1); - + // Fast forward past subscription period vm.warp(block.timestamp + 31 days); - + // Subscription should be expired assertFalse(records.isSubscriptionActive(patient2)); - + // Patient needs to renew to access records vm.prank(patient2); usdc.approve(address(records), STORAGE_FEE); - + vm.prank(patient2); records.addMedicalRecord("QmNewRecord"); assertTrue(records.isSubscriptionActive(patient2)); } - // ===== EDGE CASES ===== function testZeroFeePercentage() public { vm.startPrank(owner); MedFiEscrow zeroFeeEscrow = new MedFiEscrow(address(registry), address(usdc), 0); vm.stopPrank(); - + _setupVerifiedDoctor(); - - uint256 bookingAmount = 100 * 10**6; + + uint256 bookingAmount = 100 * 10 ** 6; vm.prank(patient1); usdc.approve(address(zeroFeeEscrow), bookingAmount); - + vm.prank(patient1); zeroFeeEscrow.bookService(doctor1, bookingAmount); - + MedFiEscrow.Booking memory booking = zeroFeeEscrow.getBooking(1); assertEq(booking.amount, bookingAmount); // No fee deducted assertEq(booking.originalAmount, bookingAmount); @@ -403,12 +402,12 @@ contract MedFiIntegrationTest is Test { function testInvalidRatings() public { _setupVerifiedDoctor(); _createConfirmedBooking(); - + // Test rating of 0 vm.prank(patient1); vm.expectRevert(MedFiRecords.InvalidRating.selector); records.rateHealthWorker(doctor1, 0); - + // Test rating over 5 vm.prank(patient1); vm.expectRevert(MedFiRecords.InvalidRating.selector); @@ -418,87 +417,87 @@ contract MedFiIntegrationTest is Test { function testComplexWorkflow() public { // 1. Setup multiple hospitals and doctors _setupTwoHospitals(); - + // 2. Register doctors with different hospitals vm.prank(hospital1); registry.approveAffiliation(doctor1); - + vm.prank(hospital2); registry.approveAffiliation(doctor2); - + vm.prank(doctor1); registry.registerProfessional(hospital1); - + vm.prank(doctor2); registry.registerProfessional(hospital2); - + vm.prank(admin); registry.verifyProfessional(doctor1); - + vm.prank(admin); registry.verifyProfessional(doctor2); - + // 3. Patient approves USDC for all operations vm.prank(patient1); usdc.approve(address(records), STORAGE_FEE * 10); - + // 4. Patient grants access to both doctors vm.prank(patient1); records.grantAccess(doctor1); - + vm.prank(patient1); records.grantAccess(doctor2); - + // 5. Patient books with both doctors - uint256 booking1Amount = 150 * 10**6; - uint256 booking2Amount = 250 * 10**6; - + uint256 booking1Amount = 150 * 10 ** 6; + uint256 booking2Amount = 250 * 10 ** 6; + vm.prank(patient1); usdc.approve(address(escrow), booking1Amount + booking2Amount); - + vm.prank(patient1); escrow.bookService(doctor1, booking1Amount); - + vm.prank(patient1); escrow.bookService(doctor2, booking2Amount); - + // 6. Doctors add records vm.prank(doctor1); records.storeRecordByCareProvider(patient1, "QmCardiacRecord"); - + vm.prank(doctor2); records.storeRecordByCareProvider(patient1, "QmNeuroRecord"); - + // 7. Patient confirms both services vm.prank(patient1); escrow.confirmService(1); - + vm.prank(patient1); escrow.confirmService(2); - + // 8. Patient rates both doctors vm.prank(patient1); records.rateHealthWorker(doctor1, 5); - + vm.prank(patient1); records.rateHealthWorker(doctor2, 4); - + // Verify final state assertTrue(escrow.getBooking(1).confirmed); assertTrue(escrow.getBooking(2).confirmed); assertEq(records.getAverageRating(doctor1), 5); assertEq(records.getAverageRating(doctor2), 4); - + // Check patient's medical records vm.prank(patient1); MedFiRecords.MedicalRecord[] memory patientRecords = records.getMyMedicalRecords(); assertEq(patientRecords.length, 2); - + // Verify both doctors can still view records vm.prank(doctor1); MedFiRecords.MedicalRecord[] memory doctor1Records = records.getPatientMedicalRecords(patient1); assertEq(doctor1Records.length, 2); - + vm.prank(doctor2); MedFiRecords.MedicalRecord[] memory doctor2Records = records.getPatientMedicalRecords(patient1); assertEq(doctor2Records.length, 2); @@ -506,27 +505,27 @@ contract MedFiIntegrationTest is Test { function testBatchOperations() public { _setupTwoHospitals(); - + address[] memory doctorsToApprove = new address[](3); doctorsToApprove[0] = makeAddr("batchDoctor1"); doctorsToApprove[1] = makeAddr("batchDoctor2"); doctorsToApprove[2] = makeAddr("batchDoctor3"); - + // Batch approve affiliations vm.prank(hospital1); registry.approveMultipleAffiliations(doctorsToApprove); - + // Verify all are approved - for (uint i = 0; i < doctorsToApprove.length; i++) { + for (uint256 i = 0; i < doctorsToApprove.length; i++) { assertTrue(registry.isAffiliationApproved(hospital1, doctorsToApprove[i])); } - + // Batch revoke affiliations vm.prank(hospital1); registry.revokeMultipleAffiliationApprovals(doctorsToApprove); - + // Verify all are revoked - for (uint i = 0; i < doctorsToApprove.length; i++) { + for (uint256 i = 0; i < doctorsToApprove.length; i++) { assertFalse(registry.isAffiliationApproved(hospital1, doctorsToApprove[i])); } } @@ -534,19 +533,19 @@ contract MedFiIntegrationTest is Test { function testUSDCBatchMinting() public { address[] memory recipients = new address[](3); uint256[] memory amounts = new uint256[](3); - + recipients[0] = makeAddr("user1"); recipients[1] = makeAddr("user2"); recipients[2] = makeAddr("user3"); - - amounts[0] = 100 * 10**6; - amounts[1] = 200 * 10**6; - amounts[2] = 300 * 10**6; - + + amounts[0] = 100 * 10 ** 6; + amounts[1] = 200 * 10 ** 6; + amounts[2] = 300 * 10 ** 6; + vm.prank(owner); usdc.batchMint(recipients, amounts); - - for (uint i = 0; i < recipients.length; i++) { + + for (uint256 i = 0; i < recipients.length; i++) { assertEq(usdc.balanceOf(recipients[i]), amounts[i]); } } @@ -556,16 +555,16 @@ contract MedFiIntegrationTest is Test { function _setupVerifiedDoctor() internal { vm.prank(hospital1); registry.registerHospital(); - + vm.prank(admin); registry.verifyHospital(hospital1); - + vm.prank(hospital1); registry.approveAffiliation(doctor1); - + vm.prank(doctor1); registry.registerProfessional(hospital1); - + vm.prank(admin); registry.verifyProfessional(doctor1); } @@ -573,26 +572,26 @@ contract MedFiIntegrationTest is Test { function _setupTwoHospitals() internal { vm.prank(hospital1); registry.registerHospital(); - + vm.prank(hospital2); registry.registerHospital(); - + vm.prank(admin); registry.verifyHospital(hospital1); - + vm.prank(admin); registry.verifyHospital(hospital2); } function _createConfirmedBooking() internal { - uint256 bookingAmount = 100 * 10**6; + uint256 bookingAmount = 100 * 10 ** 6; vm.prank(patient1); usdc.approve(address(escrow), bookingAmount); - + vm.prank(patient1); escrow.bookService(doctor1, bookingAmount); - + vm.prank(patient1); escrow.confirmService(1); } -} \ No newline at end of file +} diff --git a/test/MedFiTest.t.sol b/test/MedFiTest.t.sol index aba15f9..07a31ab 100644 --- a/test/MedFiTest.t.sol +++ b/test/MedFiTest.t.sol @@ -26,8 +26,8 @@ contract MedFiTest is Test { // Test constants uint256 public constant FEE_PERCENT = 5; // 5% - uint256 public constant INITIAL_USDC_AMOUNT = 1000 * 10**6; // 1000 USDC - uint256 public constant STORAGE_FEE = 1 * 10**6; // 1 USDC + uint256 public constant INITIAL_USDC_AMOUNT = 1000 * 10 ** 6; // 1000 USDC + uint256 public constant STORAGE_FEE = 1 * 10 ** 6; // 1 USDC function setUp() public { // Start impersonating owner for deployment @@ -53,14 +53,14 @@ contract MedFiTest is Test { function testHospitalRegistration() public { vm.startPrank(hospital1); - + registry.registerHospital(); - + (bool verified, uint256 timestamp) = registry.getHospital(hospital1); - + assertFalse(verified); assertGt(timestamp, 0); - + vm.stopPrank(); } @@ -68,12 +68,12 @@ contract MedFiTest is Test { // Register hospital first vm.prank(hospital1); registry.registerHospital(); - + // Verify hospital as admin vm.prank(admin); registry.verifyHospital(hospital1); - - (bool verified, ) = registry.getHospital(hospital1); + + (bool verified,) = registry.getHospital(hospital1); assertTrue(verified); } @@ -81,18 +81,18 @@ contract MedFiTest is Test { // Setup: Register and verify hospital vm.prank(hospital1); registry.registerHospital(); - + vm.prank(admin); registry.verifyHospital(hospital1); - + // Hospital approves affiliation vm.prank(hospital1); registry.approveAffiliation(doctor1); - + // Doctor registers vm.prank(doctor1); registry.registerProfessional(hospital1); - + MedFiRegistry.Professional memory professional = registry.getProfessional(doctor1); assertEq(professional.affiliatedHospital, hospital1); assertTrue(professional.isAffiliated); @@ -101,27 +101,27 @@ contract MedFiTest is Test { function testProfessionalVerification() public { _setupVerifiedDoctor(); - + // Verify professional vm.prank(admin); registry.verifyProfessional(doctor1); - + assertTrue(registry.isVerified(doctor1)); assertTrue(registry.canPerformActivities(doctor1)); } function testBlacklistPreventsActivities() public { _setupVerifiedDoctor(); - + vm.prank(admin); registry.verifyProfessional(doctor1); - + assertTrue(registry.canPerformActivities(doctor1)); - + // Blacklist professional vm.prank(admin); registry.blacklistProfessional(doctor1); - + assertFalse(registry.canPerformActivities(doctor1)); assertTrue(registry.isBlacklisted(doctor1)); } @@ -137,19 +137,19 @@ contract MedFiTest is Test { function testUSDCFaucet() public { address newUser = makeAddr("newUser"); - uint256 faucetAmount = 100 * 10**6; // 100 USDC - + uint256 faucetAmount = 100 * 10 ** 6; // 100 USDC + vm.prank(newUser); usdc.faucet(faucetAmount); - + assertEq(usdc.balanceOf(newUser), faucetAmount); } function testUSDCConversionFunctions() public { uint256 dollarAmount = 100; uint256 weiAmount = usdc.dollarToWei(dollarAmount); - assertEq(weiAmount, 100 * 10**6); - + assertEq(weiAmount, 100 * 10 ** 6); + uint256 backToDollars = usdc.weiToDollar(weiAmount); assertEq(backToDollars, dollarAmount); } @@ -160,17 +160,17 @@ contract MedFiTest is Test { _setupVerifiedDoctor(); vm.prank(admin); registry.verifyProfessional(doctor1); - - uint256 bookingAmount = 100 * 10**6; // 100 USDC - + + uint256 bookingAmount = 100 * 10 ** 6; // 100 USDC + // Patient approves USDC spending vm.prank(patient1); usdc.approve(address(escrow), bookingAmount); - + // Book service vm.prank(patient1); escrow.bookService(doctor1, bookingAmount); - + // Check booking details MedFiEscrow.Booking memory booking = escrow.getBooking(1); assertEq(booking.patient, patient1); @@ -178,7 +178,7 @@ contract MedFiTest is Test { assertEq(booking.originalAmount, bookingAmount); assertTrue(booking.isPaid); assertFalse(booking.confirmed); - + // Check fee calculation uint256 expectedFee = (bookingAmount * FEE_PERCENT) / 100; uint256 expectedAmount = bookingAmount - expectedFee; @@ -188,24 +188,23 @@ contract MedFiTest is Test { function testConfirmService() public { _setupBooking(); - + uint256 doctorBalanceBefore = usdc.balanceOf(doctor1); - + // Confirm service vm.prank(patient1); escrow.confirmService(1); - + // Check booking status MedFiEscrow.Booking memory booking = escrow.getBooking(1); assertTrue(booking.confirmed); - + // Check doctor received payment uint256 doctorBalanceAfter = usdc.balanceOf(doctor1); assertEq(doctorBalanceAfter, doctorBalanceBefore + booking.amount); - + // Check provider stats - (uint256 confirmedBookings, uint256 totalEarnings, bool canReceive) = - escrow.getProviderStats(doctor1); + (uint256 confirmedBookings, uint256 totalEarnings, bool canReceive) = escrow.getProviderStats(doctor1); assertEq(confirmedBookings, 1); assertEq(totalEarnings, booking.amount); assertTrue(canReceive); @@ -213,18 +212,18 @@ contract MedFiTest is Test { function testRefundPatient() public { _setupBooking(); - + uint256 patientBalanceBefore = usdc.balanceOf(patient1); MedFiEscrow.Booking memory bookingBefore = escrow.getBooking(1); - + // Provider refunds patient vm.prank(doctor1); escrow.refundPatient(1); - + // Check booking status MedFiEscrow.Booking memory booking = escrow.getBooking(1); assertFalse(booking.isPaid); - + // Check patient received refund uint256 patientBalanceAfter = usdc.balanceOf(patient1); assertEq(patientBalanceAfter, patientBalanceBefore + bookingBefore.amount); @@ -232,14 +231,14 @@ contract MedFiTest is Test { function testEmergencyRefund() public { _setupBooking(); - + uint256 patientBalanceBefore = usdc.balanceOf(patient1); MedFiEscrow.Booking memory bookingBefore = escrow.getBooking(1); - + // Owner performs emergency refund vm.prank(owner); escrow.emergencyRefund(1); - + // Check patient received refund uint256 patientBalanceAfter = usdc.balanceOf(patient1); assertEq(patientBalanceAfter, patientBalanceBefore + bookingBefore.amount); @@ -247,14 +246,14 @@ contract MedFiTest is Test { function testWithdrawFees() public { _setupBooking(); - + uint256 ownerBalanceBefore = usdc.balanceOf(owner); uint256 collectedFees = escrow.collectedFees(); - + // Withdraw fees vm.prank(owner); escrow.withdrawFees(); - + // Check owner received fees uint256 ownerBalanceAfter = usdc.balanceOf(owner); assertEq(ownerBalanceAfter, ownerBalanceBefore + collectedFees); @@ -265,15 +264,15 @@ contract MedFiTest is Test { _setupVerifiedDoctor(); vm.prank(admin); registry.verifyProfessional(doctor1); - + // Blacklist doctor vm.prank(admin); registry.blacklistProfessional(doctor1); - - uint256 bookingAmount = 100 * 10**6; + + uint256 bookingAmount = 100 * 10 ** 6; vm.prank(patient1); usdc.approve(address(escrow), bookingAmount); - + // Should revert when trying to book vm.prank(patient1); vm.expectRevert(MedFiEscrow.ProviderCannotPerformActivities.selector); @@ -286,36 +285,36 @@ contract MedFiTest is Test { _setupVerifiedDoctor(); vm.prank(admin); registry.verifyProfessional(doctor1); - + // Patient approves USDC for subscription vm.prank(patient1); usdc.approve(address(records), STORAGE_FEE); - + // Patient grants access to doctor (will trigger subscription payment) vm.prank(patient1); records.grantAccess(doctor1); - + // Check access granted assertTrue(records.hasAccess(patient1, doctor1)); assertTrue(records.patientAccessGrants(patient1, doctor1)); - + // Check subscription is active assertTrue(records.isSubscriptionActive(patient1)); } function testRevokeAccess() public { _setupVerifiedDoctorWithSubscription(); - + // Grant access first vm.prank(patient1); records.grantAccess(doctor1); - + assertTrue(records.hasAccess(patient1, doctor1)); - + // Revoke access vm.prank(patient1); records.revokeAccess(doctor1); - + // Check access revoked assertFalse(records.hasAccess(patient1, doctor1)); assertFalse(records.patientAccessGrants(patient1, doctor1)); @@ -323,11 +322,11 @@ contract MedFiTest is Test { function testCannotGrantAccessToBlacklistedProfessional() public { _setupVerifiedDoctorWithSubscription(); - + // Blacklist doctor vm.prank(admin); registry.blacklistProfessional(doctor1); - + // Try to grant access - should revert vm.prank(patient1); vm.expectRevert(MedFiRecords.CannotPerformActivities.selector); @@ -336,17 +335,17 @@ contract MedFiTest is Test { function testStoreRecordByCareProviderWithAccess() public { _setupVerifiedDoctorWithSubscription(); - + // Grant access to doctor vm.prank(patient1); records.grantAccess(doctor1); - + string memory recordHash = "QmTestHash123"; - + // Doctor stores record vm.prank(doctor1); records.storeRecordByCareProvider(patient1, recordHash); - + // Check professional stats - Note: profession is stored off-chain now (uint256 recordCount, bool isActive) = records.getProfessionalStats(doctor1); assertEq(recordCount, 1); @@ -355,10 +354,10 @@ contract MedFiTest is Test { function testCannotStoreRecordWithoutAccess() public { _setupVerifiedDoctorWithSubscription(); - + // Don't grant access string memory recordHash = "QmTestHash123"; - + // Try to store record - should revert vm.prank(doctor1); vm.expectRevert(MedFiRecords.AccessNotGranted.selector); @@ -369,12 +368,12 @@ contract MedFiTest is Test { // Patient approves USDC for subscription vm.prank(patient1); usdc.approve(address(records), STORAGE_FEE); - + string memory recordHash = "QmPatientRecord456"; - + vm.prank(patient1); records.addMedicalRecord(recordHash); - + // Check patient can view their own records vm.prank(patient1); MedFiRecords.MedicalRecord[] memory patientRecords = records.getMyMedicalRecords(); @@ -386,17 +385,17 @@ contract MedFiTest is Test { function testPatientCanViewOwnRecords() public { _setupVerifiedDoctorWithSubscription(); - + // Grant access and add records vm.prank(patient1); records.grantAccess(doctor1); - + vm.prank(patient1); records.addMedicalRecord("QmPatientRecord1"); - + vm.prank(doctor1); records.storeRecordByCareProvider(patient1, "QmDoctorRecord1"); - + // Patient can view all their records vm.prank(patient1); MedFiRecords.MedicalRecord[] memory patientRecords = records.getMyMedicalRecords(); @@ -405,14 +404,14 @@ contract MedFiTest is Test { function testAuthorizedProfessionalCanViewPatientRecords() public { _setupVerifiedDoctorWithSubscription(); - + // Grant access and add records vm.prank(patient1); records.grantAccess(doctor1); - + vm.prank(patient1); records.addMedicalRecord("QmPatientRecord1"); - + // Doctor can view patient's records vm.prank(doctor1); MedFiRecords.MedicalRecord[] memory patientRecords = records.getPatientMedicalRecords(patient1); @@ -422,11 +421,11 @@ contract MedFiTest is Test { function testUnauthorizedProfessionalCannotViewPatientRecords() public { _setupVerifiedDoctorWithSubscription(); - + // Don't grant access vm.prank(patient1); records.addMedicalRecord("QmPatientRecord1"); - + // Doctor cannot view patient's records without permission vm.prank(doctor1); vm.expectRevert(MedFiRecords.AccessNotGranted.selector); @@ -435,33 +434,33 @@ contract MedFiTest is Test { function testManualSubscriptionRenewal() public { _setupVerifiedDoctorWithSubscription(); - + // Grant access (first subscription) vm.prank(patient1); records.grantAccess(doctor1); - + // Approve for manual renewal vm.prank(patient1); usdc.approve(address(records), STORAGE_FEE); - + // Manually renew subscription vm.prank(patient1); records.renewSubscription(); - + // Check subscription is still active assertTrue(records.isSubscriptionActive(patient1)); } function testSubscriptionExpiry() public { _setupVerifiedDoctorWithSubscription(); - + // Grant access (pay subscription) vm.prank(patient1); records.grantAccess(doctor1); - + // Fast forward past subscription period vm.warp(block.timestamp + 31 days); - + // Subscription should be expired assertFalse(records.isSubscriptionActive(patient1)); } @@ -471,17 +470,16 @@ contract MedFiTest is Test { _setupBooking(); vm.prank(patient1); escrow.confirmService(1); - + uint8 rating = 5; vm.prank(patient1); records.rateHealthWorker(doctor1, rating); - + // Check rating uint256 averageRating = records.getAverageRating(doctor1); assertEq(averageRating, rating); - - (uint256 avgRating, uint256 totalRatings, bool canBeRated) = - records.getHealthWorkerRatingDetails(doctor1); + + (uint256 avgRating, uint256 totalRatings, bool canBeRated) = records.getHealthWorkerRatingDetails(doctor1); assertEq(avgRating, rating); assertEq(totalRatings, 1); assertTrue(canBeRated); @@ -491,10 +489,10 @@ contract MedFiTest is Test { _setupBooking(); vm.prank(patient1); escrow.confirmService(1); - + vm.prank(patient1); records.rateHealthWorker(doctor1, 5); - + // Try to rate again - should revert vm.prank(patient1); vm.expectRevert(MedFiRecords.AlreadyRated.selector); @@ -505,7 +503,7 @@ contract MedFiTest is Test { _setupVerifiedDoctor(); vm.prank(admin); registry.verifyProfessional(doctor1); - + // Try to rate without confirmed booking - should revert vm.prank(patient1); vm.expectRevert(MedFiRecords.ExceedsConfirmedBookings.selector); @@ -518,14 +516,14 @@ contract MedFiTest is Test { // Register and verify hospital vm.prank(hospital1); registry.registerHospital(); - + vm.prank(admin); registry.verifyHospital(hospital1); - + // Hospital approves affiliation vm.prank(hospital1); registry.approveAffiliation(doctor1); - + // Doctor registers vm.prank(doctor1); registry.registerProfessional(hospital1); @@ -535,7 +533,7 @@ contract MedFiTest is Test { _setupVerifiedDoctor(); vm.prank(admin); registry.verifyProfessional(doctor1); - + // Patient approves USDC for subscription vm.prank(patient1); usdc.approve(address(records), STORAGE_FEE * 10); // Approve for multiple operations @@ -545,13 +543,13 @@ contract MedFiTest is Test { _setupVerifiedDoctor(); vm.prank(admin); registry.verifyProfessional(doctor1); - - uint256 bookingAmount = 100 * 10**6; // 100 USDC - + + uint256 bookingAmount = 100 * 10 ** 6; // 100 USDC + vm.prank(patient1); usdc.approve(address(escrow), bookingAmount); - + vm.prank(patient1); escrow.bookService(doctor1, bookingAmount); } -} \ No newline at end of file +} diff --git a/test/mock/MockUSDC.sol b/test/mock/MockUSDC.sol index 70a759a..7197099 100644 --- a/test/mock/MockUSDC.sol +++ b/test/mock/MockUSDC.sol @@ -11,7 +11,7 @@ import "@openzeppelin/contracts/access/Ownable.sol"; */ contract MockUSDC is ERC20, Ownable { uint8 private constant DECIMALS = 6; // USDC has 6 decimal places - uint256 public constant INITIAL_SUPPLY = 1000000 * 10**DECIMALS; // 1,000,000 USDC + uint256 public constant INITIAL_SUPPLY = 1000000 * 10 ** DECIMALS; // 1,000,000 USDC // Events event TokensMinted(address indexed to, uint256 amount); @@ -21,8 +21,8 @@ contract MockUSDC is ERC20, Ownable { * @notice Constructor to initialize the mock USDC token * @param initialOwner The initial owner of the contract */ - constructor(address initialOwner) ERC20("Mock USD Coin", "USDC") Ownable(initialOwner) { - _mint(initialOwner, INITIAL_SUPPLY); + constructor() ERC20("Mock USD Coin", "USDC") Ownable(initialOwner) { + _mint(msg.sender, INITIAL_SUPPLY); } /** @@ -38,7 +38,7 @@ contract MockUSDC is ERC20, Ownable { * @param to The address to mint tokens to * @param amount The amount of tokens to mint (in wei, considering 6 decimals) */ - function mint(address to, uint256 amount) external { + function mint(address to, uint256 amount) external { _mint(to, amount); emit TokensMinted(to, amount); } @@ -60,7 +60,7 @@ contract MockUSDC is ERC20, Ownable { */ function batchMint(address[] calldata recipients, uint256[] calldata amounts) external onlyOwner { require(recipients.length == amounts.length, "Arrays length mismatch"); - + for (uint256 i = 0; i < recipients.length; i++) { _mint(recipients[i], amounts[i]); emit TokensMinted(recipients[i], amounts[i]); @@ -72,9 +72,9 @@ contract MockUSDC is ERC20, Ownable { * @param amount The amount of USDC to request (in wei, considering 6 decimals) */ function faucet(uint256 amount) external { - require(amount <= 1000 * 10**DECIMALS, "Cannot request more than 1000 USDC at once"); - require(balanceOf(msg.sender) <= 10000 * 10**DECIMALS, "You already have enough test USDC"); - + require(amount <= 1000 * 10 ** DECIMALS, "Cannot request more than 1000 USDC at once"); + require(balanceOf(msg.sender) <= 10000 * 10 ** DECIMALS, "You already have enough test USDC"); + _mint(msg.sender, amount); emit TokensMinted(msg.sender, amount); } @@ -85,7 +85,7 @@ contract MockUSDC is ERC20, Ownable { * @return The amount in wei */ function dollarToWei(uint256 dollarAmount) external pure returns (uint256) { - return dollarAmount * 10**DECIMALS; + return dollarAmount * 10 ** DECIMALS; } /** @@ -94,7 +94,7 @@ contract MockUSDC is ERC20, Ownable { * @return The amount in dollars */ function weiToDollar(uint256 weiAmount) external pure returns (uint256) { - return weiAmount / 10**DECIMALS; + return weiAmount / 10 ** DECIMALS; } /** @@ -104,15 +104,10 @@ contract MockUSDC is ERC20, Ownable { */ function getFormattedBalance(address account) external view returns (string memory) { uint256 balance = balanceOf(account); - uint256 dollars = balance / 10**DECIMALS; - uint256 cents = (balance % 10**DECIMALS) / 10**(DECIMALS-2); - - return string(abi.encodePacked( - _toString(dollars), - ".", - cents < 10 ? "0" : "", - _toString(cents) - )); + uint256 dollars = balance / 10 ** DECIMALS; + uint256 cents = (balance % 10 ** DECIMALS) / 10 ** (DECIMALS - 2); + + return string(abi.encodePacked(_toString(dollars), ".", cents < 10 ? "0" : "", _toString(cents))); } /** @@ -154,24 +149,22 @@ contract MockUSDC is ERC20, Ownable { /** * @notice Get contract info for debugging * @return tokenName The token name - * @return tokenSymbol The token symbol + * @return tokenSymbol The token symbol * @return tokenDecimals The number of decimals * @return tokenTotalSupply The total supply * @return contractBalance The contract's token balance */ - function getContractInfo() external view returns ( - string memory tokenName, - string memory tokenSymbol, - uint8 tokenDecimals, - uint256 tokenTotalSupply, - uint256 contractBalance - ) { - return ( - name(), - symbol(), - decimals(), - totalSupply(), - balanceOf(address(this)) - ); + function getContractInfo() + external + view + returns ( + string memory tokenName, + string memory tokenSymbol, + uint8 tokenDecimals, + uint256 tokenTotalSupply, + uint256 contractBalance + ) + { + return (name(), symbol(), decimals(), totalSupply(), balanceOf(address(this))); } -} \ No newline at end of file +} From a42d0ab483dcb6c13c380105f568fe8275e5da66 Mon Sep 17 00:00:00 2001 From: Therock Ani Date: Mon, 5 Jan 2026 20:08:21 +0100 Subject: [PATCH 2/3] refactor: simplify registry by removing hospital affiliation system - Remove hospital-professional affiliation dependencies - Professionals now register independently without hospital approval - Admin-only verification for both hospitals and professionals - Add blacklisting functionality for hospitals - Implement unified canPerformActivities() for both entity types - Remove redundant deactivate/reactivate functions (use blacklist instead) - Hospital struct now includes isActive field - unblacklist sets only isActive=true, admin must re-verify explicitly - Clean up dead code and improve security Resolves #1 --- src/MedFiRecords.sol | 2 +- src/MedFiRegistry.sol | 306 +++++++++++--------------------- test/MedFiEdgeCaseTest.t.sol | 2 +- test/MedFiIntegrationTest.t.sol | 2 +- test/MedFiTest.t.sol | 2 +- test/mock/MockUSDC.sol | 4 +- 6 files changed, 113 insertions(+), 205 deletions(-) diff --git a/src/MedFiRecords.sol b/src/MedFiRecords.sol index 0583127..bda33c4 100644 --- a/src/MedFiRecords.sol +++ b/src/MedFiRecords.sol @@ -1,4 +1,4 @@ - // SPDX-License-Identifier: MIT +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {IMedFiRegistry, IMedFiEscrow} from "./interface/Interfaces.sol"; diff --git a/src/MedFiRegistry.sol b/src/MedFiRegistry.sol index 8e92e50..9369272 100644 --- a/src/MedFiRegistry.sol +++ b/src/MedFiRegistry.sol @@ -1,4 +1,4 @@ - // SPDX-License-Identifier: MIT +// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/access/Ownable.sol"; @@ -6,23 +6,22 @@ import "@openzeppelin/contracts/access/Ownable.sol"; /** * @title MedFiRegistry * @author Therock Ani - * @notice A scalable registry contract for managing hospitals and medical professionals with role-based administration, - * address-based verification, and blacklisting functionality. + * @notice A scalable registry contract for managing hospitals and medical professionals with admin-only verification + * and blacklisting functionality. * @dev This contract uses OpenZeppelin's Ownable for ownership control and includes custom errors for gas-efficient error handling. * All metadata (names, locations, etc.) are stored off-chain and managed by frontend/backend. */ contract MedFiRegistry is Ownable { // Custom Errors error OnlyAdminAllowed(); - error HospitalNotVerified(); error HospitalAlreadyRegistered(); error HospitalNotRegistered(); + error HospitalNotVerified(); + error HospitalIsBlacklisted(); error ProfessionalAlreadyRegistered(); - error ProfessionalIsBlacklisted(); error ProfessionalNotRegistered(); - error ProfessionalNotAffiliated(); - error InvalidHospitalAddress(); - error UnauthorizedAffiliation(); + error ProfessionalIsBlacklisted(); + error InvalidAddress(); /** * @notice Mapping of addresses to their admin status. @@ -35,20 +34,15 @@ contract MedFiRegistry is Ownable { mapping(address => Hospital) public hospitals; /** - * @notice Unified mapping of professional addresses to their Professional struct. + * @notice Mapping of professional addresses to their Professional struct. */ mapping(address => Professional) public professionals; /** - * @notice Mapping of addresses to their blacklist status. + * @notice Mapping of addresses to their blacklist status (for both hospitals and professionals). */ mapping(address => bool) public isBlacklisted; - /** - * @notice Mapping to track pending affiliations (hospital => professional => isPending). - */ - mapping(address => mapping(address => bool)) public pendingAffiliations; - /** * @notice Array to store all registered professional addresses for enumeration. */ @@ -70,12 +64,12 @@ contract MedFiRegistry is Ownable { mapping(address => bool) private isHospitalInArray; /** - * @notice Struct to store hospital details and affiliated professionals. + * @notice Struct to store hospital details. */ struct Hospital { bool isVerified; uint256 registrationTimestamp; - mapping(address => bool) affiliatedProfessionals; + bool isActive; } /** @@ -83,9 +77,7 @@ contract MedFiRegistry is Ownable { */ struct Professional { address professionalAddress; - address affiliatedHospital; bool isVerified; - bool isAffiliated; uint256 registrationTimestamp; bool isActive; } @@ -94,11 +86,9 @@ contract MedFiRegistry is Ownable { event HospitalRegistered(address indexed hospital); event HospitalVerified(address indexed hospital); event HospitalUnverified(address indexed hospital); - event AffiliationApproved(address indexed hospital, address indexed professional); - event AffiliationRevoked(address indexed hospital, address indexed professional); - event ProfessionalRegistered(address indexed professional, address indexed hospital); - event AffiliationChanged(address indexed professional, address indexed oldHospital, address indexed newHospital); - event ProfessionalUnaffiliated(address indexed professional, address indexed hospital); + event HospitalBlacklisted(address indexed hospital); + event HospitalUnblacklisted(address indexed hospital); + event ProfessionalRegistered(address indexed professional); event ProfessionalVerified(address indexed professional); event ProfessionalUnverified(address indexed professional); event ProfessionalProfileUpdated(address indexed professional); @@ -114,19 +104,12 @@ contract MedFiRegistry is Ownable { } /** - * @notice Restricts function access to verified hospitals only. - */ - modifier onlyVerifiedHospital(address _hospital) { - if (!hospitals[_hospital].isVerified) revert HospitalNotVerified(); - _; - } - - /** - * @notice Restricts function access to affiliated and verified professionals only. + * @notice Restricts function access to registered and active professionals only. */ - modifier onlyAffiliatedProfessional() { + modifier onlyActiveProfessional() { Professional memory professional = professionals[msg.sender]; - if (!professional.isAffiliated || !professional.isActive) revert ProfessionalNotAffiliated(); + if (professional.professionalAddress == address(0)) revert ProfessionalNotRegistered(); + if (!professional.isActive) revert ProfessionalIsBlacklisted(); _; } @@ -141,6 +124,7 @@ contract MedFiRegistry is Ownable { * @notice Adds an address to the admin list. */ function addAdmin(address _admin) external onlyOwner { + if (_admin == address(0)) revert InvalidAddress(); isAdmin[_admin] = true; } @@ -157,9 +141,9 @@ contract MedFiRegistry is Ownable { */ function registerHospital() external { if (hospitals[msg.sender].registrationTimestamp != 0) revert HospitalAlreadyRegistered(); + if (isBlacklisted[msg.sender]) revert HospitalIsBlacklisted(); - hospitals[msg.sender].registrationTimestamp = block.timestamp; - hospitals[msg.sender].isVerified = false; + hospitals[msg.sender] = Hospital({registrationTimestamp: block.timestamp, isVerified: false, isActive: true}); if (!isHospitalInArray[msg.sender]) { hospitalAddresses.push(msg.sender); @@ -174,6 +158,7 @@ contract MedFiRegistry is Ownable { */ function verifyHospital(address _hospital) external onlyAdmin { if (hospitals[_hospital].registrationTimestamp == 0) revert HospitalNotRegistered(); + if (isBlacklisted[_hospital]) revert HospitalIsBlacklisted(); hospitals[_hospital].isVerified = true; emit HospitalVerified(_hospital); } @@ -188,60 +173,37 @@ contract MedFiRegistry is Ownable { } /** - * @notice Allows a verified hospital to approve a professional for affiliation. + * @notice Blacklists a hospital, preventing further activities and revoking verification. */ - function approveAffiliation(address _professionalAddress) external onlyVerifiedHospital(msg.sender) { - if (_professionalAddress == address(0)) revert InvalidHospitalAddress(); + function blacklistHospital(address _hospital) external onlyAdmin { + isBlacklisted[_hospital] = true; - pendingAffiliations[msg.sender][_professionalAddress] = true; - emit AffiliationApproved(msg.sender, _professionalAddress); - } - - /** - * @notice Allows a verified hospital to approve multiple professionals for affiliation. - */ - function approveMultipleAffiliations(address[] memory _professionalAddresses) - external - onlyVerifiedHospital(msg.sender) - { - for (uint256 i = 0; i < _professionalAddresses.length; i++) { - address professionalAddress = _professionalAddresses[i]; - if (professionalAddress != address(0)) { - pendingAffiliations[msg.sender][professionalAddress] = true; - emit AffiliationApproved(msg.sender, professionalAddress); - } + if (hospitals[_hospital].registrationTimestamp != 0) { + hospitals[_hospital].isVerified = false; + hospitals[_hospital].isActive = false; } - } - /** - * @notice Allows a hospital to revoke affiliation approval for a professional. - */ - function revokeAffiliationApproval(address _professionalAddress) external onlyVerifiedHospital(msg.sender) { - pendingAffiliations[msg.sender][_professionalAddress] = false; - emit AffiliationRevoked(msg.sender, _professionalAddress); + emit HospitalBlacklisted(_hospital); } /** - * @notice Allows a hospital to revoke affiliation approval for multiple professionals. + * @notice Removes a hospital from the blacklist. */ - function revokeMultipleAffiliationApprovals(address[] memory _professionalAddresses) - external - onlyVerifiedHospital(msg.sender) - { - for (uint256 i = 0; i < _professionalAddresses.length; i++) { - address professionalAddress = _professionalAddresses[i]; - if (professionalAddress != address(0)) { - pendingAffiliations[msg.sender][professionalAddress] = false; - emit AffiliationRevoked(msg.sender, professionalAddress); - } + function unblacklistHospital(address _hospital) external onlyAdmin { + isBlacklisted[_hospital] = false; + + if (hospitals[_hospital].registrationTimestamp != 0) { + hospitals[_hospital].isActive = true; } + + emit HospitalUnblacklisted(_hospital); } /** - * @notice Allows a professional to register with a hospital that has approved their affiliation. + * @notice Allows a professional to register themselves. * @dev Metadata stored off-chain. Emits `ProfessionalRegistered`. */ - function registerProfessional(address _hospitalAddress) external { + function registerProfessional() external { if (professionals[msg.sender].professionalAddress != address(0)) { revert ProfessionalAlreadyRegistered(); } @@ -250,134 +212,55 @@ contract MedFiRegistry is Ownable { revert ProfessionalIsBlacklisted(); } - if (_hospitalAddress == address(0)) revert InvalidHospitalAddress(); - if (hospitals[_hospitalAddress].registrationTimestamp == 0) revert HospitalNotRegistered(); - if (!hospitals[_hospitalAddress].isVerified) revert HospitalNotVerified(); - - if (!pendingAffiliations[_hospitalAddress][msg.sender]) { - revert UnauthorizedAffiliation(); - } - professionals[msg.sender] = Professional({ - professionalAddress: msg.sender, - affiliatedHospital: _hospitalAddress, - isVerified: false, - isAffiliated: true, - registrationTimestamp: block.timestamp, - isActive: true + professionalAddress: msg.sender, isVerified: false, registrationTimestamp: block.timestamp, isActive: true }); - hospitals[_hospitalAddress].affiliatedProfessionals[msg.sender] = true; - pendingAffiliations[_hospitalAddress][msg.sender] = false; - if (!isProfessionalInArray[msg.sender]) { professionalAddresses.push(msg.sender); isProfessionalInArray[msg.sender] = true; } - emit ProfessionalRegistered(msg.sender, _hospitalAddress); + emit ProfessionalRegistered(msg.sender); } /** * @notice Allows a professional to update their profile information. * @dev Metadata updated off-chain. Emits `ProfessionalProfileUpdated`. */ - function updateProfessionalProfile() external onlyAffiliatedProfessional { + function updateProfessionalProfile() external onlyActiveProfessional { emit ProfessionalProfileUpdated(msg.sender); } /** - * @notice Allows a hospital to unaffiliate a professional, rendering them inactive. - */ - function unaffiliateProfessional(address _professionalAddress) external { - Professional storage professional = professionals[_professionalAddress]; - - if (professional.affiliatedHospital != msg.sender) revert UnauthorizedAffiliation(); - if (!professional.isAffiliated) revert ProfessionalNotAffiliated(); - - professional.isAffiliated = false; - professional.isVerified = false; - professional.isActive = false; - - hospitals[msg.sender].affiliatedProfessionals[_professionalAddress] = false; - - emit ProfessionalUnaffiliated(_professionalAddress, msg.sender); - } - - /** - * @notice Allows a hospital to unaffiliate multiple professionals, rendering them inactive. - */ - function unaffiliateMultipleProfessionals(address[] memory _professionalAddresses) external { - for (uint256 i = 0; i < _professionalAddresses.length; i++) { - address professionalAddress = _professionalAddresses[i]; - if (professionalAddress != address(0)) { - Professional storage professional = professionals[professionalAddress]; - - if (professional.affiliatedHospital == msg.sender && professional.isAffiliated) { - professional.isAffiliated = false; - professional.isVerified = false; - professional.isActive = false; - - hospitals[msg.sender].affiliatedProfessionals[professionalAddress] = false; - - emit ProfessionalUnaffiliated(professionalAddress, msg.sender); - } - } - } - } - - /** - * @notice Allows a professional to change their hospital affiliation. + * @notice Verifies a registered professional. */ - function changeAffiliation(address _newHospitalAddress) external { - Professional storage professional = professionals[msg.sender]; - + function verifyProfessional(address _professional) external onlyAdmin { + Professional storage professional = professionals[_professional]; if (professional.professionalAddress == address(0)) { revert ProfessionalNotRegistered(); } - - if (isBlacklisted[msg.sender]) { + if (isBlacklisted[_professional]) { revert ProfessionalIsBlacklisted(); } - - if (_newHospitalAddress == address(0)) revert InvalidHospitalAddress(); - if (hospitals[_newHospitalAddress].registrationTimestamp == 0) revert HospitalNotRegistered(); - if (!hospitals[_newHospitalAddress].isVerified) revert HospitalNotVerified(); - - if (!pendingAffiliations[_newHospitalAddress][msg.sender]) { - revert UnauthorizedAffiliation(); - } - - address oldHospital = professional.affiliatedHospital; - if (oldHospital != address(0) && professional.isAffiliated) { - hospitals[oldHospital].affiliatedProfessionals[msg.sender] = false; - emit ProfessionalUnaffiliated(msg.sender, oldHospital); - } - - professional.affiliatedHospital = _newHospitalAddress; - professional.isAffiliated = true; - professional.isVerified = false; - professional.isActive = true; - - hospitals[_newHospitalAddress].affiliatedProfessionals[msg.sender] = true; - pendingAffiliations[_newHospitalAddress][msg.sender] = false; - - emit AffiliationChanged(msg.sender, oldHospital, _newHospitalAddress); + professional.isVerified = true; + emit ProfessionalVerified(_professional); } /** - * @notice Verifies a registered professional. + * @notice Verifies multiple professionals at once. */ - function verifyProfessional(address _professional) external onlyAdmin { - Professional storage professional = professionals[_professional]; - if (professional.professionalAddress == address(0)) { - revert ProfessionalNotRegistered(); - } - if (!professional.isAffiliated) { - revert ProfessionalNotAffiliated(); + function verifyMultipleProfessionals(address[] memory _professionals) external onlyAdmin { + for (uint256 i = 0; i < _professionals.length; i++) { + address professionalAddress = _professionals[i]; + if (professionalAddress != address(0)) { + Professional storage professional = professionals[professionalAddress]; + if (professional.professionalAddress != address(0) && !isBlacklisted[professionalAddress]) { + professional.isVerified = true; + emit ProfessionalVerified(professionalAddress); + } + } } - professional.isVerified = true; - emit ProfessionalVerified(_professional); } /** @@ -391,6 +274,21 @@ contract MedFiRegistry is Ownable { emit ProfessionalUnverified(_professional); } + /** + * @notice Revokes verification from multiple professionals at once. + */ + function unverifyMultipleProfessionals(address[] memory _professionals) external onlyAdmin { + for (uint256 i = 0; i < _professionals.length; i++) { + address professionalAddress = _professionals[i]; + if (professionalAddress != address(0)) { + if (professionals[professionalAddress].professionalAddress != address(0)) { + professionals[professionalAddress].isVerified = false; + emit ProfessionalUnverified(professionalAddress); + } + } + } + } + /** * @notice Blacklists a professional, preventing further activities and revoking verification. */ @@ -411,8 +309,7 @@ contract MedFiRegistry is Ownable { function unblacklistProfessional(address _professional) external onlyAdmin { isBlacklisted[_professional] = false; - if (professionals[_professional].professionalAddress != address(0) && professionals[_professional].isAffiliated) - { + if (professionals[_professional].professionalAddress != address(0)) { professionals[_professional].isActive = true; } @@ -420,28 +317,42 @@ contract MedFiRegistry is Ownable { } /** - * @notice Checks if a professional is verified and active. + * @notice Checks if a hospital is verified and active. */ - function isVerified(address _professional) external view returns (bool) { - if (isBlacklisted[_professional]) return false; - Professional memory professional = professionals[_professional]; - return professional.isVerified && professional.isAffiliated && professional.isActive; + function isVerifiedHospital(address _hospital) external view returns (bool) { + if (isBlacklisted[_hospital]) return false; + Hospital memory hospital = hospitals[_hospital]; + return hospital.isVerified && hospital.isActive; } /** - * @notice Checks if a professional can perform activities. + * @notice Checks if a professional is verified and active. */ - function canPerformActivities(address _professional) external view returns (bool) { + function isVerifiedProfessional(address _professional) external view returns (bool) { if (isBlacklisted[_professional]) return false; Professional memory professional = professionals[_professional]; - return professional.isVerified && professional.isAffiliated && professional.isActive; + return professional.isVerified && professional.isActive; } /** - * @notice Checks if a hospital has approved affiliation for a professional. + * @notice Checks if an address (hospital or professional) can perform activities. */ - function isAffiliationApproved(address _hospital, address _professional) external view returns (bool) { - return pendingAffiliations[_hospital][_professional]; + function canPerformActivities(address _address) external view returns (bool) { + if (isBlacklisted[_address]) return false; + + // Check if it's a hospital + if (hospitals[_address].registrationTimestamp != 0) { + Hospital memory hospital = hospitals[_address]; + return hospital.isVerified && hospital.isActive; + } + + // Check if it's a professional + if (professionals[_address].professionalAddress != address(0)) { + Professional memory professional = professionals[_address]; + return professional.isVerified && professional.isActive; + } + + return false; } /** @@ -454,9 +365,13 @@ contract MedFiRegistry is Ownable { /** * @notice Gets hospital details by address. */ - function getHospital(address _hospital) external view returns (bool verified, uint256 registrationTimestamp) { - Hospital storage hospital = hospitals[_hospital]; - return (hospital.isVerified, hospital.registrationTimestamp); + function getHospital(address _hospital) + external + view + returns (bool verified, uint256 registrationTimestamp, bool active) + { + Hospital memory hospital = hospitals[_hospital]; + return (hospital.isVerified, hospital.registrationTimestamp, hospital.isActive); } /** @@ -486,11 +401,4 @@ contract MedFiRegistry is Ownable { function getTotalHospitals() external view returns (uint256) { return hospitalAddresses.length; } - - /** - * @notice Checks if a professional is affiliated with a specific hospital. - */ - function isProfessionalAffiliated(address _hospital, address _professional) external view returns (bool) { - return hospitals[_hospital].affiliatedProfessionals[_professional]; - } } diff --git a/test/MedFiEdgeCaseTest.t.sol b/test/MedFiEdgeCaseTest.t.sol index 0f33f1c..e05e905 100644 --- a/test/MedFiEdgeCaseTest.t.sol +++ b/test/MedFiEdgeCaseTest.t.sol @@ -24,7 +24,7 @@ contract MedFiEdgeCaseTest is Test { function setUp() public { vm.startPrank(owner); registry = new MedFiRegistry(); - usdc = new MockUSDC(owner); + usdc = new MockUSDC(); escrow = new MedFiEscrow(address(registry), address(usdc), 5); records = new MedFiRecords(address(registry), address(escrow), address(usdc)); registry.addAdmin(admin); diff --git a/test/MedFiIntegrationTest.t.sol b/test/MedFiIntegrationTest.t.sol index f754734..700ee21 100644 --- a/test/MedFiIntegrationTest.t.sol +++ b/test/MedFiIntegrationTest.t.sol @@ -32,7 +32,7 @@ contract MedFiIntegrationTest is Test { vm.startPrank(owner); registry = new MedFiRegistry(); - usdc = new MockUSDC(owner); + usdc = new MockUSDC(); escrow = new MedFiEscrow(address(registry), address(usdc), FEE_PERCENT); records = new MedFiRecords(address(registry), address(escrow), address(usdc)); diff --git a/test/MedFiTest.t.sol b/test/MedFiTest.t.sol index 07a31ab..d35d4b0 100644 --- a/test/MedFiTest.t.sol +++ b/test/MedFiTest.t.sol @@ -35,7 +35,7 @@ contract MedFiTest is Test { // Deploy contracts registry = new MedFiRegistry(); - usdc = new MockUSDC(owner); + usdc = new MockUSDC(); escrow = new MedFiEscrow(address(registry), address(usdc), FEE_PERCENT); records = new MedFiRecords(address(registry), address(escrow), address(usdc)); diff --git a/test/mock/MockUSDC.sol b/test/mock/MockUSDC.sol index 7197099..73fc86d 100644 --- a/test/mock/MockUSDC.sol +++ b/test/mock/MockUSDC.sol @@ -19,9 +19,9 @@ contract MockUSDC is ERC20, Ownable { /** * @notice Constructor to initialize the mock USDC token - * @param initialOwner The initial owner of the contract + * @param msg.sender The initial owner of the contract */ - constructor() ERC20("Mock USD Coin", "USDC") Ownable(initialOwner) { + constructor() ERC20("Mock USD Coin", "USDC") Ownable(msg.sender) { _mint(msg.sender, INITIAL_SUPPLY); } From fadda6f3e011eb1323662326cd4139177661200f Mon Sep 17 00:00:00 2001 From: Therock Ani Date: Tue, 6 Jan 2026 16:52:49 +0100 Subject: [PATCH 3/3] updated unit tests --- test/MedFiEdgeCaseTest.t.sol | 123 ++++++++----------- test/MedFiIntegrationTest.t.sol | 202 ++++---------------------------- test/MedFiTest.t.sol | 115 ++++-------------- test/mock/MockUSDC.sol | 1 - 4 files changed, 97 insertions(+), 344 deletions(-) diff --git a/test/MedFiEdgeCaseTest.t.sol b/test/MedFiEdgeCaseTest.t.sol index e05e905..23fdb97 100644 --- a/test/MedFiEdgeCaseTest.t.sol +++ b/test/MedFiEdgeCaseTest.t.sol @@ -7,7 +7,7 @@ import "../src/MedFiEscrow.sol"; import "../src/MedFiRecords.sol"; import "./mock/MockUSDC.sol"; -contract MedFiEdgeCaseTest is Test { +contract MedFiEdgeCaseTst is Test { MedFiRegistry public registry; MedFiEscrow public escrow; MedFiRecords public records; @@ -19,7 +19,7 @@ contract MedFiEdgeCaseTest is Test { address public doctor = makeAddr("doctor"); address public patient = makeAddr("patient"); - uint256 constant STORAGE_FEE = 1e6; // 1 USDC + uint256 constant STORAGE_FEE = 1e6; function setUp() public { vm.startPrank(owner); @@ -58,67 +58,75 @@ contract MedFiEdgeCaseTest is Test { registry.unverifyHospital(hospital); } - function testProfessionalRegistrationWithoutApproval() public { - _setupVerifiedHospital(); + function testProfessionalDoubleRegistration() public { + vm.prank(doctor); + registry.registerProfessional(); vm.prank(doctor); - vm.expectRevert(MedFiRegistry.UnauthorizedAffiliation.selector); - registry.registerProfessional(hospital); + vm.expectRevert(MedFiRegistry.ProfessionalAlreadyRegistered.selector); + registry.registerProfessional(); } - function testProfessionalRegistrationWithUnverifiedHospital() public { - vm.prank(hospital); - registry.registerHospital(); - - vm.prank(hospital); - vm.expectRevert(MedFiRegistry.HospitalNotVerified.selector); - registry.approveAffiliation(doctor); + function testBlacklistedProfessionalCannotRegister() public { + vm.prank(admin); + registry.blacklistProfessional(doctor); vm.prank(doctor); - vm.expectRevert(MedFiRegistry.HospitalNotVerified.selector); - registry.registerProfessional(hospital); + vm.expectRevert(MedFiRegistry.ProfessionalIsBlacklisted.selector); + registry.registerProfessional(); } - function testChangeAffiliationToSameHospital() public { - _setupVerifiedDoctor(); + function testBlacklistedHospitalCannotRegister() public { + vm.prank(admin); + registry.blacklistHospital(hospital); vm.prank(hospital); - registry.approveAffiliation(doctor); + vm.expectRevert(MedFiRegistry.HospitalIsBlacklisted.selector); + registry.registerHospital(); + } + function testUnblacklistRestoresActiveStatus() public { vm.prank(doctor); - registry.changeAffiliation(hospital); + registry.registerProfessional(); - MedFiRegistry.Professional memory prof = registry.getProfessional(doctor); - assertEq(prof.affiliatedHospital, hospital); - assertFalse(prof.isVerified); - } + vm.prank(admin); + registry.verifyProfessional(doctor); - function testUnaffiliateNonAffiliatedProfessional() public { - _setupVerifiedDoctor(); + vm.prank(admin); + registry.blacklistProfessional(doctor); - address otherHospital = makeAddr("otherHospital"); - vm.prank(otherHospital); - registry.registerHospital(); + assertFalse(registry.canPerformActivities(doctor)); vm.prank(admin); - registry.verifyHospital(otherHospital); + registry.unblacklistProfessional(doctor); - vm.prank(otherHospital); - vm.expectRevert(MedFiRegistry.UnauthorizedAffiliation.selector); - registry.unaffiliateProfessional(doctor); + MedFiRegistry.Professional memory prof = registry.getProfessional(doctor); + assertTrue(prof.isActive); + assertFalse(prof.isVerified); // Verification not restored } - function testBlacklistAfterUnaffiliation() public { - _setupVerifiedDoctor(); + function testCannotVerifyBlacklistedProfessional() public { + vm.prank(doctor); + registry.registerProfessional(); + + vm.prank(admin); + registry.blacklistProfessional(doctor); + vm.prank(admin); + vm.expectRevert(MedFiRegistry.ProfessionalIsBlacklisted.selector); + registry.verifyProfessional(doctor); + } + + function testCannotVerifyBlacklistedHospital() public { vm.prank(hospital); - registry.unaffiliateProfessional(doctor); + registry.registerHospital(); vm.prank(admin); - registry.blacklistProfessional(doctor); + registry.blacklistHospital(hospital); - assertTrue(registry.isBlacklisted(doctor)); - assertFalse(registry.canPerformActivities(doctor)); + vm.prank(admin); + vm.expectRevert(MedFiRegistry.HospitalIsBlacklisted.selector); + registry.verifyHospital(hospital); } // ===== ESCROW EDGE CASES ===== @@ -217,14 +225,10 @@ contract MedFiEdgeCaseTest is Test { } function testGrantAccessToUnverifiedProfessional() public { - _setupVerifiedHospital(); _setupPatientSubscription(); - vm.prank(hospital); - registry.approveAffiliation(doctor); - vm.prank(doctor); - registry.registerProfessional(hospital); + registry.registerProfessional(); vm.prank(patient); vm.expectRevert(MedFiRecords.CannotPerformActivities.selector); @@ -294,7 +298,6 @@ contract MedFiEdgeCaseTest is Test { } function testRateWithInvalidRating() public { - // _setupVerifiedDoctor(); _createConfirmedBooking(); vm.prank(patient); @@ -311,13 +314,8 @@ contract MedFiEdgeCaseTest is Test { } function testRateUnverifiedHealthWorker() public { - _setupVerifiedHospital(); - - vm.prank(hospital); - registry.approveAffiliation(doctor); - vm.prank(doctor); - registry.registerProfessional(hospital); + registry.registerProfessional(); vm.prank(patient); vm.expectRevert(MedFiRecords.CannotPerformActivities.selector); @@ -366,20 +364,14 @@ contract MedFiEdgeCaseTest is Test { } function testAccessControlWithMultipleProfessionals() public { - _setupVerifiedHospital(); _setupPatientSubscription(); address doctor2 = makeAddr("doctor2"); - vm.prank(hospital); - registry.approveAffiliation(doctor); - vm.prank(hospital); - registry.approveAffiliation(doctor2); - vm.prank(doctor); - registry.registerProfessional(hospital); + registry.registerProfessional(); vm.prank(doctor2); - registry.registerProfessional(hospital); + registry.registerProfessional(); vm.prank(admin); registry.verifyProfessional(doctor); @@ -560,22 +552,9 @@ contract MedFiEdgeCaseTest is Test { // ===== HELPER FUNCTIONS ===== - function _setupVerifiedHospital() internal { - vm.prank(hospital); - registry.registerHospital(); - - vm.prank(admin); - registry.verifyHospital(hospital); - } - function _setupVerifiedDoctor() internal { - _setupVerifiedHospital(); - - vm.prank(hospital); - registry.approveAffiliation(doctor); - vm.prank(doctor); - registry.registerProfessional(hospital); + registry.registerProfessional(); vm.prank(admin); registry.verifyProfessional(doctor); diff --git a/test/MedFiIntegrationTest.t.sol b/test/MedFiIntegrationTest.t.sol index 700ee21..4457762 100644 --- a/test/MedFiIntegrationTest.t.sol +++ b/test/MedFiIntegrationTest.t.sol @@ -7,7 +7,7 @@ import "../src/MedFiEscrow.sol"; import "../src/MedFiRecords.sol"; import "./mock/MockUSDC.sol"; -contract MedFiIntegrationTest is Test { +contract MedFiIntegrationTst is Test { MedFiRegistry public registry; MedFiEscrow public escrow; MedFiRecords public records; @@ -23,10 +23,10 @@ contract MedFiIntegrationTest is Test { address public patient2 = makeAddr("patient2"); address public patient3 = makeAddr("patient3"); - uint256 public constant FEE_PERCENT = 10; // 10% for testing - uint256 public constant INITIAL_USDC_AMOUNT = 10000 * 10 ** 6; // 10,000 USDC - uint256 public constant ONE_USDC_AMOUNT = 1 * 10 ** 6; // 1 USDC - uint256 public constant STORAGE_FEE = 1 * 10 ** 6; // 1 USDC + uint256 public constant FEE_PERCENT = 10; + uint256 public constant INITIAL_USDC_AMOUNT = 10000 * 10 ** 6; + uint256 public constant ONE_USDC_AMOUNT = 1 * 10 ** 6; + uint256 public constant STORAGE_FEE = 1 * 10 ** 6; function setUp() public { vm.startPrank(owner); @@ -47,40 +47,32 @@ contract MedFiIntegrationTest is Test { // ===== INTEGRATION TESTS ===== function testCompletePatientJourney() public { - // 1. Setup hospital and doctor _setupVerifiedDoctor(); - // 2. Patient approves USDC for all operations vm.prank(patient1); - usdc.approve(address(records), STORAGE_FEE * 10); // For subscription + usdc.approve(address(records), STORAGE_FEE * 10); vm.prank(patient1); - usdc.approve(address(escrow), 1000 * 10 ** 6); // For bookings + usdc.approve(address(escrow), 1000 * 10 ** 6); - // 3. Patient grants access to doctor (triggers subscription payment) vm.prank(patient1); records.grantAccess(doctor1); - // 4. Patient books service - uint256 bookingAmount = 200 * 10 ** 6; // 200 USDC + uint256 bookingAmount = 200 * 10 ** 6; vm.prank(patient1); escrow.bookService(doctor1, bookingAmount); - // 5. Doctor adds medical record vm.prank(doctor1); records.storeRecordByCareProvider(patient1, "QmHealthRecord123"); - // 6. Patient confirms service vm.prank(patient1); escrow.confirmService(1); - // 7. Patient rates doctor vm.prank(patient1); records.rateHealthWorker(doctor1, 5); (uint256 recordCount,) = records.getProfessionalStats(doctor1); - // Verify final state assertTrue(escrow.getBooking(1).confirmed); assertEq(records.getAverageRating(doctor1), 5); assertEq(recordCount, 1); @@ -92,19 +84,16 @@ contract MedFiIntegrationTest is Test { function testMultipleBookingsAndRatings() public { _setupVerifiedDoctor(); - // Patient approves USDC for all operations vm.prank(patient1); usdc.approve(address(records), STORAGE_FEE * 10); vm.prank(patient2); usdc.approve(address(records), STORAGE_FEE * 10); - // Patients grant access vm.prank(patient1); records.grantAccess(doctor1); vm.prank(patient2); records.grantAccess(doctor1); - // Create multiple bookings uint256 bookingAmount = 100 * 10 ** 6; for (uint256 i = 0; i < 3; i++) { address patient = i == 0 ? patient1 : patient2; @@ -116,7 +105,6 @@ contract MedFiIntegrationTest is Test { escrow.bookService(doctor1, bookingAmount); } - // Confirm all bookings vm.prank(patient1); escrow.confirmService(1); @@ -126,67 +114,29 @@ contract MedFiIntegrationTest is Test { vm.prank(patient2); escrow.confirmService(3); - // Rate the doctor vm.prank(patient1); records.rateHealthWorker(doctor1, 4); vm.prank(patient2); records.rateHealthWorker(doctor1, 5); - // Verify stats (uint256 confirmedBookings, uint256 totalEarnings,) = escrow.getProviderStats(doctor1); assertEq(confirmedBookings, 3); (uint256 avgRating, uint256 totalRatings,) = records.getHealthWorkerRatingDetails(doctor1); assertEq(totalRatings, 2); - assertEq(avgRating, 4); // (4 + 5) / 2 = 4 (integer division) - } - - function testHospitalAffiliationChanges() public { - // Setup two hospitals - _setupTwoHospitals(); - - // Doctor initially affiliates with hospital1 - vm.prank(hospital1); - registry.approveAffiliation(doctor1); - - vm.prank(doctor1); - registry.registerProfessional(hospital1); - - vm.prank(admin); - registry.verifyProfessional(doctor1); - - // Doctor changes affiliation to hospital2 - vm.prank(hospital2); - registry.approveAffiliation(doctor1); - - vm.prank(doctor1); - registry.changeAffiliation(hospital2); - - // Verify affiliation change - MedFiRegistry.Professional memory professional = registry.getProfessional(doctor1); - assertEq(professional.affiliatedHospital, hospital2); - assertTrue(professional.isAffiliated); - assertFalse(professional.isVerified); // Should lose verification - assertTrue(professional.isActive); - - // Verify old hospital no longer has professional - assertFalse(registry.isProfessionalAffiliated(hospital1, doctor1)); - assertTrue(registry.isProfessionalAffiliated(hospital2, doctor1)); + assertEq(avgRating, 4); } function testBlacklistingImpactOnExistingBookings() public { _setupVerifiedDoctor(); - // Patient approves USDC for subscription vm.prank(patient1); usdc.approve(address(records), STORAGE_FEE * 10); - // Patient grants access vm.prank(patient1); records.grantAccess(doctor1); - // Create booking uint256 bookingAmount = 100 * 10 ** 6; vm.prank(patient1); usdc.approve(address(escrow), bookingAmount); @@ -194,16 +144,13 @@ contract MedFiIntegrationTest is Test { vm.prank(patient1); escrow.bookService(doctor1, bookingAmount); - // Blacklist doctor vm.prank(admin); registry.blacklistProfessional(doctor1); - // Patient should not be able to confirm service with blacklisted doctor vm.prank(patient1); vm.expectRevert(MedFiEscrow.ProviderCannotPerformActivities.selector); escrow.confirmService(1); - // But owner can emergency refund vm.prank(owner); escrow.emergencyRefund(1); @@ -211,54 +158,30 @@ contract MedFiIntegrationTest is Test { } function testPatientAccessManagement() public { - _setupTwoHospitals(); - - // Setup two doctors from different hospitals - vm.prank(hospital1); - registry.approveAffiliation(doctor1); - vm.prank(hospital2); - registry.approveAffiliation(doctor2); + _setupTwoDoctors(); - vm.prank(doctor1); - registry.registerProfessional(hospital1); - vm.prank(doctor2); - registry.registerProfessional(hospital2); - - vm.prank(admin); - registry.verifyProfessional(doctor1); - vm.prank(admin); - registry.verifyProfessional(doctor2); - - // Patient approves USDC for subscription vm.prank(patient1); usdc.approve(address(records), STORAGE_FEE * 10); - // Patient grants access to doctor1 only vm.prank(patient1); records.grantAccess(doctor1); - // Verify access states assertTrue(records.hasAccess(patient1, doctor1)); assertFalse(records.hasAccess(patient1, doctor2)); - // Doctor1 can store records vm.prank(doctor1); records.storeRecordByCareProvider(patient1, "QmCardiacRecord"); - // Doctor2 cannot store records vm.prank(doctor2); vm.expectRevert(MedFiRecords.AccessNotGranted.selector); records.storeRecordByCareProvider(patient1, "QmNeuroRecord"); - // Grant access to doctor2 vm.prank(patient1); records.grantAccess(doctor2); - // Now doctor2 can store records vm.prank(doctor2); records.storeRecordByCareProvider(patient1, "QmNeuroRecord"); - // Both doctors can view patient records vm.prank(doctor1); MedFiRecords.MedicalRecord[] memory records1 = records.getPatientMedicalRecords(patient1); assertEq(records1.length, 2); @@ -267,16 +190,13 @@ contract MedFiIntegrationTest is Test { MedFiRecords.MedicalRecord[] memory records2 = records.getPatientMedicalRecords(patient1); assertEq(records2.length, 2); - // Revoke access from doctor1 vm.prank(patient1); records.revokeAccess(doctor1); - // Doctor1 can no longer view records vm.prank(doctor1); vm.expectRevert(MedFiRecords.AccessNotGranted.selector); records.getPatientMedicalRecords(patient1); - // But doctor2 still can vm.prank(doctor2); MedFiRecords.MedicalRecord[] memory records3 = records.getPatientMedicalRecords(patient1); assertEq(records3.length, 2); @@ -285,22 +205,18 @@ contract MedFiIntegrationTest is Test { function testSubscriptionRenewalFlow() public { _setupVerifiedDoctor(); - // Patient approves USDC for initial subscription vm.prank(patient1); usdc.approve(address(records), STORAGE_FEE); - // Grant access (first subscription payment) vm.prank(patient1); records.grantAccess(doctor1); uint256 firstExpiry = records.getSubscriptionExpiry(patient1); assertTrue(records.isSubscriptionActive(patient1)); - // Approve for manual renewal vm.prank(patient1); usdc.approve(address(records), STORAGE_FEE); - // Manually renew subscription vm.prank(patient1); records.renewSubscription(); @@ -312,20 +228,16 @@ contract MedFiIntegrationTest is Test { function testSubscriptionExpiryAndRenewal() public { _setupVerifiedDoctor(); - // Patient approves and pays initial subscription vm.prank(patient3); usdc.approve(address(records), STORAGE_FEE); vm.prank(patient3); records.grantAccess(doctor1); - // Fast forward past subscription period vm.warp(block.timestamp + 31 days); - // Subscription should be expired assertFalse(records.isSubscriptionActive(patient3)); - // Patient needs to renew to access records vm.prank(patient3); usdc.approve(address(records), STORAGE_FEE); @@ -335,11 +247,9 @@ contract MedFiIntegrationTest is Test { usdc.mint(patient3, ONE_USDC_AMOUNT); - // Renew subscription first vm.prank(patient3); records.renewSubscription(); - // Now can add records vm.prank(patient3); records.addMedicalRecord("QmNewRecord"); @@ -349,20 +259,16 @@ contract MedFiIntegrationTest is Test { function testSubscriptionExpiryAndAutoRenewal() public { _setupVerifiedDoctor(); - // Patient approves and pays initial subscription vm.prank(patient2); usdc.approve(address(records), STORAGE_FEE); vm.prank(patient2); records.grantAccess(doctor1); - // Fast forward past subscription period vm.warp(block.timestamp + 31 days); - // Subscription should be expired assertFalse(records.isSubscriptionActive(patient2)); - // Patient needs to renew to access records vm.prank(patient2); usdc.approve(address(records), STORAGE_FEE); @@ -388,7 +294,7 @@ contract MedFiIntegrationTest is Test { zeroFeeEscrow.bookService(doctor1, bookingAmount); MedFiEscrow.Booking memory booking = zeroFeeEscrow.getBooking(1); - assertEq(booking.amount, bookingAmount); // No fee deducted + assertEq(booking.amount, bookingAmount); assertEq(booking.originalAmount, bookingAmount); assertEq(zeroFeeEscrow.collectedFees(), 0); } @@ -403,52 +309,27 @@ contract MedFiIntegrationTest is Test { _setupVerifiedDoctor(); _createConfirmedBooking(); - // Test rating of 0 vm.prank(patient1); vm.expectRevert(MedFiRecords.InvalidRating.selector); records.rateHealthWorker(doctor1, 0); - // Test rating over 5 vm.prank(patient1); vm.expectRevert(MedFiRecords.InvalidRating.selector); records.rateHealthWorker(doctor1, 6); } function testComplexWorkflow() public { - // 1. Setup multiple hospitals and doctors - _setupTwoHospitals(); - - // 2. Register doctors with different hospitals - vm.prank(hospital1); - registry.approveAffiliation(doctor1); - - vm.prank(hospital2); - registry.approveAffiliation(doctor2); + _setupTwoDoctors(); - vm.prank(doctor1); - registry.registerProfessional(hospital1); - - vm.prank(doctor2); - registry.registerProfessional(hospital2); - - vm.prank(admin); - registry.verifyProfessional(doctor1); - - vm.prank(admin); - registry.verifyProfessional(doctor2); - - // 3. Patient approves USDC for all operations vm.prank(patient1); usdc.approve(address(records), STORAGE_FEE * 10); - // 4. Patient grants access to both doctors vm.prank(patient1); records.grantAccess(doctor1); vm.prank(patient1); records.grantAccess(doctor2); - // 5. Patient books with both doctors uint256 booking1Amount = 150 * 10 ** 6; uint256 booking2Amount = 250 * 10 ** 6; @@ -461,39 +342,33 @@ contract MedFiIntegrationTest is Test { vm.prank(patient1); escrow.bookService(doctor2, booking2Amount); - // 6. Doctors add records vm.prank(doctor1); records.storeRecordByCareProvider(patient1, "QmCardiacRecord"); vm.prank(doctor2); records.storeRecordByCareProvider(patient1, "QmNeuroRecord"); - // 7. Patient confirms both services vm.prank(patient1); escrow.confirmService(1); vm.prank(patient1); escrow.confirmService(2); - // 8. Patient rates both doctors vm.prank(patient1); records.rateHealthWorker(doctor1, 5); vm.prank(patient1); records.rateHealthWorker(doctor2, 4); - // Verify final state assertTrue(escrow.getBooking(1).confirmed); assertTrue(escrow.getBooking(2).confirmed); assertEq(records.getAverageRating(doctor1), 5); assertEq(records.getAverageRating(doctor2), 4); - // Check patient's medical records vm.prank(patient1); MedFiRecords.MedicalRecord[] memory patientRecords = records.getMyMedicalRecords(); assertEq(patientRecords.length, 2); - // Verify both doctors can still view records vm.prank(doctor1); MedFiRecords.MedicalRecord[] memory doctor1Records = records.getPatientMedicalRecords(patient1); assertEq(doctor1Records.length, 2); @@ -503,33 +378,6 @@ contract MedFiIntegrationTest is Test { assertEq(doctor2Records.length, 2); } - function testBatchOperations() public { - _setupTwoHospitals(); - - address[] memory doctorsToApprove = new address[](3); - doctorsToApprove[0] = makeAddr("batchDoctor1"); - doctorsToApprove[1] = makeAddr("batchDoctor2"); - doctorsToApprove[2] = makeAddr("batchDoctor3"); - - // Batch approve affiliations - vm.prank(hospital1); - registry.approveMultipleAffiliations(doctorsToApprove); - - // Verify all are approved - for (uint256 i = 0; i < doctorsToApprove.length; i++) { - assertTrue(registry.isAffiliationApproved(hospital1, doctorsToApprove[i])); - } - - // Batch revoke affiliations - vm.prank(hospital1); - registry.revokeMultipleAffiliationApprovals(doctorsToApprove); - - // Verify all are revoked - for (uint256 i = 0; i < doctorsToApprove.length; i++) { - assertFalse(registry.isAffiliationApproved(hospital1, doctorsToApprove[i])); - } - } - function testUSDCBatchMinting() public { address[] memory recipients = new address[](3); uint256[] memory amounts = new uint256[](3); @@ -553,34 +401,25 @@ contract MedFiIntegrationTest is Test { // ===== HELPER FUNCTIONS ===== function _setupVerifiedDoctor() internal { - vm.prank(hospital1); - registry.registerHospital(); - - vm.prank(admin); - registry.verifyHospital(hospital1); - - vm.prank(hospital1); - registry.approveAffiliation(doctor1); - vm.prank(doctor1); - registry.registerProfessional(hospital1); + registry.registerProfessional(); vm.prank(admin); registry.verifyProfessional(doctor1); } - function _setupTwoHospitals() internal { - vm.prank(hospital1); - registry.registerHospital(); + function _setupTwoDoctors() internal { + vm.prank(doctor1); + registry.registerProfessional(); - vm.prank(hospital2); - registry.registerHospital(); + vm.prank(doctor2); + registry.registerProfessional(); vm.prank(admin); - registry.verifyHospital(hospital1); + registry.verifyProfessional(doctor1); vm.prank(admin); - registry.verifyHospital(hospital2); + registry.verifyProfessional(doctor2); } function _createConfirmedBooking() internal { @@ -595,3 +434,4 @@ contract MedFiIntegrationTest is Test { escrow.confirmService(1); } } + diff --git a/test/MedFiTest.t.sol b/test/MedFiTest.t.sol index d35d4b0..a0e3265 100644 --- a/test/MedFiTest.t.sol +++ b/test/MedFiTest.t.sol @@ -7,6 +7,7 @@ import "../src/MedFiEscrow.sol"; import "../src/MedFiRecords.sol"; import "./mock/MockUSDC.sol"; +// ===== FILE 1: MedFiTest.sol ===== contract MedFiTest is Test { // Contract instances MedFiRegistry public registry; @@ -30,19 +31,15 @@ contract MedFiTest is Test { uint256 public constant STORAGE_FEE = 1 * 10 ** 6; // 1 USDC function setUp() public { - // Start impersonating owner for deployment vm.startPrank(owner); - // Deploy contracts registry = new MedFiRegistry(); usdc = new MockUSDC(); escrow = new MedFiEscrow(address(registry), address(usdc), FEE_PERCENT); records = new MedFiRecords(address(registry), address(escrow), address(usdc)); - // Add admin registry.addAdmin(admin); - // Give test USDC to patients usdc.mint(patient1, INITIAL_USDC_AMOUNT); usdc.mint(patient2, INITIAL_USDC_AMOUNT); @@ -56,57 +53,60 @@ contract MedFiTest is Test { registry.registerHospital(); - (bool verified, uint256 timestamp) = registry.getHospital(hospital1); + (bool verified, uint256 timestamp, bool active) = registry.getHospital(hospital1); assertFalse(verified); assertGt(timestamp, 0); + assertTrue(active); vm.stopPrank(); } function testHospitalVerification() public { - // Register hospital first vm.prank(hospital1); registry.registerHospital(); - // Verify hospital as admin vm.prank(admin); registry.verifyHospital(hospital1); - (bool verified,) = registry.getHospital(hospital1); + (bool verified,, bool active) = registry.getHospital(hospital1); assertTrue(verified); + assertTrue(active); } - function testProfessionalRegistration() public { - // Setup: Register and verify hospital + function testHospitalBlacklisting() public { vm.prank(hospital1); registry.registerHospital(); vm.prank(admin); registry.verifyHospital(hospital1); - // Hospital approves affiliation - vm.prank(hospital1); - registry.approveAffiliation(doctor1); + assertTrue(registry.canPerformActivities(hospital1)); - // Doctor registers + vm.prank(admin); + registry.blacklistHospital(hospital1); + + assertFalse(registry.canPerformActivities(hospital1)); + assertTrue(registry.isBlacklisted(hospital1)); + } + + function testProfessionalRegistration() public { vm.prank(doctor1); - registry.registerProfessional(hospital1); + registry.registerProfessional(); MedFiRegistry.Professional memory professional = registry.getProfessional(doctor1); - assertEq(professional.affiliatedHospital, hospital1); - assertTrue(professional.isAffiliated); - assertFalse(professional.isVerified); // Not verified by admin yet + assertEq(professional.professionalAddress, doctor1); + assertFalse(professional.isVerified); + assertTrue(professional.isActive); } function testProfessionalVerification() public { _setupVerifiedDoctor(); - // Verify professional vm.prank(admin); registry.verifyProfessional(doctor1); - assertTrue(registry.isVerified(doctor1)); + assertTrue(registry.isVerifiedProfessional(doctor1)); assertTrue(registry.canPerformActivities(doctor1)); } @@ -118,7 +118,6 @@ contract MedFiTest is Test { assertTrue(registry.canPerformActivities(doctor1)); - // Blacklist professional vm.prank(admin); registry.blacklistProfessional(doctor1); @@ -137,7 +136,7 @@ contract MedFiTest is Test { function testUSDCFaucet() public { address newUser = makeAddr("newUser"); - uint256 faucetAmount = 100 * 10 ** 6; // 100 USDC + uint256 faucetAmount = 100 * 10 ** 6; vm.prank(newUser); usdc.faucet(faucetAmount); @@ -161,17 +160,14 @@ contract MedFiTest is Test { vm.prank(admin); registry.verifyProfessional(doctor1); - uint256 bookingAmount = 100 * 10 ** 6; // 100 USDC + uint256 bookingAmount = 100 * 10 ** 6; - // Patient approves USDC spending vm.prank(patient1); usdc.approve(address(escrow), bookingAmount); - // Book service vm.prank(patient1); escrow.bookService(doctor1, bookingAmount); - // Check booking details MedFiEscrow.Booking memory booking = escrow.getBooking(1); assertEq(booking.patient, patient1); assertEq(booking.provider, doctor1); @@ -179,7 +175,6 @@ contract MedFiTest is Test { assertTrue(booking.isPaid); assertFalse(booking.confirmed); - // Check fee calculation uint256 expectedFee = (bookingAmount * FEE_PERCENT) / 100; uint256 expectedAmount = bookingAmount - expectedFee; assertEq(booking.amount, expectedAmount); @@ -191,19 +186,15 @@ contract MedFiTest is Test { uint256 doctorBalanceBefore = usdc.balanceOf(doctor1); - // Confirm service vm.prank(patient1); escrow.confirmService(1); - // Check booking status MedFiEscrow.Booking memory booking = escrow.getBooking(1); assertTrue(booking.confirmed); - // Check doctor received payment uint256 doctorBalanceAfter = usdc.balanceOf(doctor1); assertEq(doctorBalanceAfter, doctorBalanceBefore + booking.amount); - // Check provider stats (uint256 confirmedBookings, uint256 totalEarnings, bool canReceive) = escrow.getProviderStats(doctor1); assertEq(confirmedBookings, 1); assertEq(totalEarnings, booking.amount); @@ -216,15 +207,12 @@ contract MedFiTest is Test { uint256 patientBalanceBefore = usdc.balanceOf(patient1); MedFiEscrow.Booking memory bookingBefore = escrow.getBooking(1); - // Provider refunds patient vm.prank(doctor1); escrow.refundPatient(1); - // Check booking status MedFiEscrow.Booking memory booking = escrow.getBooking(1); assertFalse(booking.isPaid); - // Check patient received refund uint256 patientBalanceAfter = usdc.balanceOf(patient1); assertEq(patientBalanceAfter, patientBalanceBefore + bookingBefore.amount); } @@ -235,11 +223,9 @@ contract MedFiTest is Test { uint256 patientBalanceBefore = usdc.balanceOf(patient1); MedFiEscrow.Booking memory bookingBefore = escrow.getBooking(1); - // Owner performs emergency refund vm.prank(owner); escrow.emergencyRefund(1); - // Check patient received refund uint256 patientBalanceAfter = usdc.balanceOf(patient1); assertEq(patientBalanceAfter, patientBalanceBefore + bookingBefore.amount); } @@ -250,11 +236,9 @@ contract MedFiTest is Test { uint256 ownerBalanceBefore = usdc.balanceOf(owner); uint256 collectedFees = escrow.collectedFees(); - // Withdraw fees vm.prank(owner); escrow.withdrawFees(); - // Check owner received fees uint256 ownerBalanceAfter = usdc.balanceOf(owner); assertEq(ownerBalanceAfter, ownerBalanceBefore + collectedFees); assertEq(escrow.collectedFees(), 0); @@ -265,7 +249,6 @@ contract MedFiTest is Test { vm.prank(admin); registry.verifyProfessional(doctor1); - // Blacklist doctor vm.prank(admin); registry.blacklistProfessional(doctor1); @@ -273,7 +256,6 @@ contract MedFiTest is Test { vm.prank(patient1); usdc.approve(address(escrow), bookingAmount); - // Should revert when trying to book vm.prank(patient1); vm.expectRevert(MedFiEscrow.ProviderCannotPerformActivities.selector); escrow.bookService(doctor1, bookingAmount); @@ -286,36 +268,28 @@ contract MedFiTest is Test { vm.prank(admin); registry.verifyProfessional(doctor1); - // Patient approves USDC for subscription vm.prank(patient1); usdc.approve(address(records), STORAGE_FEE); - // Patient grants access to doctor (will trigger subscription payment) vm.prank(patient1); records.grantAccess(doctor1); - // Check access granted assertTrue(records.hasAccess(patient1, doctor1)); assertTrue(records.patientAccessGrants(patient1, doctor1)); - - // Check subscription is active assertTrue(records.isSubscriptionActive(patient1)); } function testRevokeAccess() public { _setupVerifiedDoctorWithSubscription(); - // Grant access first vm.prank(patient1); records.grantAccess(doctor1); assertTrue(records.hasAccess(patient1, doctor1)); - // Revoke access vm.prank(patient1); records.revokeAccess(doctor1); - // Check access revoked assertFalse(records.hasAccess(patient1, doctor1)); assertFalse(records.patientAccessGrants(patient1, doctor1)); } @@ -323,11 +297,9 @@ contract MedFiTest is Test { function testCannotGrantAccessToBlacklistedProfessional() public { _setupVerifiedDoctorWithSubscription(); - // Blacklist doctor vm.prank(admin); registry.blacklistProfessional(doctor1); - // Try to grant access - should revert vm.prank(patient1); vm.expectRevert(MedFiRecords.CannotPerformActivities.selector); records.grantAccess(doctor1); @@ -336,17 +308,14 @@ contract MedFiTest is Test { function testStoreRecordByCareProviderWithAccess() public { _setupVerifiedDoctorWithSubscription(); - // Grant access to doctor vm.prank(patient1); records.grantAccess(doctor1); string memory recordHash = "QmTestHash123"; - // Doctor stores record vm.prank(doctor1); records.storeRecordByCareProvider(patient1, recordHash); - // Check professional stats - Note: profession is stored off-chain now (uint256 recordCount, bool isActive) = records.getProfessionalStats(doctor1); assertEq(recordCount, 1); assertTrue(isActive); @@ -355,17 +324,14 @@ contract MedFiTest is Test { function testCannotStoreRecordWithoutAccess() public { _setupVerifiedDoctorWithSubscription(); - // Don't grant access string memory recordHash = "QmTestHash123"; - // Try to store record - should revert vm.prank(doctor1); vm.expectRevert(MedFiRecords.AccessNotGranted.selector); records.storeRecordByCareProvider(patient1, recordHash); } function testPatientCanAddOwnMedicalRecord() public { - // Patient approves USDC for subscription vm.prank(patient1); usdc.approve(address(records), STORAGE_FEE); @@ -374,7 +340,6 @@ contract MedFiTest is Test { vm.prank(patient1); records.addMedicalRecord(recordHash); - // Check patient can view their own records vm.prank(patient1); MedFiRecords.MedicalRecord[] memory patientRecords = records.getMyMedicalRecords(); assertEq(patientRecords.length, 1); @@ -386,7 +351,6 @@ contract MedFiTest is Test { function testPatientCanViewOwnRecords() public { _setupVerifiedDoctorWithSubscription(); - // Grant access and add records vm.prank(patient1); records.grantAccess(doctor1); @@ -396,7 +360,6 @@ contract MedFiTest is Test { vm.prank(doctor1); records.storeRecordByCareProvider(patient1, "QmDoctorRecord1"); - // Patient can view all their records vm.prank(patient1); MedFiRecords.MedicalRecord[] memory patientRecords = records.getMyMedicalRecords(); assertEq(patientRecords.length, 2); @@ -405,14 +368,12 @@ contract MedFiTest is Test { function testAuthorizedProfessionalCanViewPatientRecords() public { _setupVerifiedDoctorWithSubscription(); - // Grant access and add records vm.prank(patient1); records.grantAccess(doctor1); vm.prank(patient1); records.addMedicalRecord("QmPatientRecord1"); - // Doctor can view patient's records vm.prank(doctor1); MedFiRecords.MedicalRecord[] memory patientRecords = records.getPatientMedicalRecords(patient1); assertEq(patientRecords.length, 1); @@ -422,11 +383,9 @@ contract MedFiTest is Test { function testUnauthorizedProfessionalCannotViewPatientRecords() public { _setupVerifiedDoctorWithSubscription(); - // Don't grant access vm.prank(patient1); records.addMedicalRecord("QmPatientRecord1"); - // Doctor cannot view patient's records without permission vm.prank(doctor1); vm.expectRevert(MedFiRecords.AccessNotGranted.selector); records.getPatientMedicalRecords(patient1); @@ -435,38 +394,30 @@ contract MedFiTest is Test { function testManualSubscriptionRenewal() public { _setupVerifiedDoctorWithSubscription(); - // Grant access (first subscription) vm.prank(patient1); records.grantAccess(doctor1); - // Approve for manual renewal vm.prank(patient1); usdc.approve(address(records), STORAGE_FEE); - // Manually renew subscription vm.prank(patient1); records.renewSubscription(); - // Check subscription is still active assertTrue(records.isSubscriptionActive(patient1)); } function testSubscriptionExpiry() public { _setupVerifiedDoctorWithSubscription(); - // Grant access (pay subscription) vm.prank(patient1); records.grantAccess(doctor1); - // Fast forward past subscription period vm.warp(block.timestamp + 31 days); - // Subscription should be expired assertFalse(records.isSubscriptionActive(patient1)); } function testRateHealthWorker() public { - // Setup confirmed booking first _setupBooking(); vm.prank(patient1); escrow.confirmService(1); @@ -475,7 +426,6 @@ contract MedFiTest is Test { vm.prank(patient1); records.rateHealthWorker(doctor1, rating); - // Check rating uint256 averageRating = records.getAverageRating(doctor1); assertEq(averageRating, rating); @@ -493,7 +443,6 @@ contract MedFiTest is Test { vm.prank(patient1); records.rateHealthWorker(doctor1, 5); - // Try to rate again - should revert vm.prank(patient1); vm.expectRevert(MedFiRecords.AlreadyRated.selector); records.rateHealthWorker(doctor1, 4); @@ -504,7 +453,6 @@ contract MedFiTest is Test { vm.prank(admin); registry.verifyProfessional(doctor1); - // Try to rate without confirmed booking - should revert vm.prank(patient1); vm.expectRevert(MedFiRecords.ExceedsConfirmedBookings.selector); records.rateHealthWorker(doctor1, 5); @@ -513,20 +461,8 @@ contract MedFiTest is Test { // ===== HELPER FUNCTIONS ===== function _setupVerifiedDoctor() internal { - // Register and verify hospital - vm.prank(hospital1); - registry.registerHospital(); - - vm.prank(admin); - registry.verifyHospital(hospital1); - - // Hospital approves affiliation - vm.prank(hospital1); - registry.approveAffiliation(doctor1); - - // Doctor registers vm.prank(doctor1); - registry.registerProfessional(hospital1); + registry.registerProfessional(); } function _setupVerifiedDoctorWithSubscription() internal { @@ -534,9 +470,8 @@ contract MedFiTest is Test { vm.prank(admin); registry.verifyProfessional(doctor1); - // Patient approves USDC for subscription vm.prank(patient1); - usdc.approve(address(records), STORAGE_FEE * 10); // Approve for multiple operations + usdc.approve(address(records), STORAGE_FEE * 10); } function _setupBooking() internal { @@ -544,7 +479,7 @@ contract MedFiTest is Test { vm.prank(admin); registry.verifyProfessional(doctor1); - uint256 bookingAmount = 100 * 10 ** 6; // 100 USDC + uint256 bookingAmount = 100 * 10 ** 6; vm.prank(patient1); usdc.approve(address(escrow), bookingAmount); diff --git a/test/mock/MockUSDC.sol b/test/mock/MockUSDC.sol index 73fc86d..1e2e96b 100644 --- a/test/mock/MockUSDC.sol +++ b/test/mock/MockUSDC.sol @@ -19,7 +19,6 @@ contract MockUSDC is ERC20, Ownable { /** * @notice Constructor to initialize the mock USDC token - * @param msg.sender The initial owner of the contract */ constructor() ERC20("Mock USD Coin", "USDC") Ownable(msg.sender) { _mint(msg.sender, INITIAL_SUPPLY);