From c65b4a531d076616dd92d66768847c232993d57e Mon Sep 17 00:00:00 2001 From: Hillzo Date: Fri, 22 Nov 2024 23:15:04 +1100 Subject: [PATCH 1/3] Added Readme file and Personalized Medicine Smart Contract --- medical-records/.gitattributes | 3 + medical-records/.gitignore | 13 ++ medical-records/.vscode/settings.json | 4 + medical-records/.vscode/tasks.json | 19 ++ medical-records/Clarinet.toml | 19 ++ medical-records/README.md | 138 ++++++++++++ medical-records/Testnet.toml | 7 + medical-records/contracts/health-records.clar | 198 ++++++++++++++++++ medical-records/package.json | 24 +++ medical-records/settings/Devnet.toml | 151 +++++++++++++ medical-records/tests/health-records.test.ts | 21 ++ medical-records/tsconfig.json | 26 +++ medical-records/vitest.config.js | 42 ++++ 13 files changed, 665 insertions(+) create mode 100644 medical-records/.gitattributes create mode 100644 medical-records/.gitignore create mode 100644 medical-records/.vscode/settings.json create mode 100644 medical-records/.vscode/tasks.json create mode 100644 medical-records/Clarinet.toml create mode 100644 medical-records/README.md create mode 100644 medical-records/Testnet.toml create mode 100644 medical-records/contracts/health-records.clar create mode 100644 medical-records/package.json create mode 100644 medical-records/settings/Devnet.toml create mode 100644 medical-records/tests/health-records.test.ts create mode 100644 medical-records/tsconfig.json create mode 100644 medical-records/vitest.config.js diff --git a/medical-records/.gitattributes b/medical-records/.gitattributes new file mode 100644 index 0000000..da6a065 --- /dev/null +++ b/medical-records/.gitattributes @@ -0,0 +1,3 @@ +tests/** linguist-vendored +vitest.config.js linguist-vendored +* text=lf diff --git a/medical-records/.gitignore b/medical-records/.gitignore new file mode 100644 index 0000000..76c2842 --- /dev/null +++ b/medical-records/.gitignore @@ -0,0 +1,13 @@ + +**/settings/Mainnet.toml +**/settings/Testnet.toml +.cache/** +history.txt + +logs +*.log +npm-debug.log* +coverage +*.info +costs-reports.json +node_modules diff --git a/medical-records/.vscode/settings.json b/medical-records/.vscode/settings.json new file mode 100644 index 0000000..3062519 --- /dev/null +++ b/medical-records/.vscode/settings.json @@ -0,0 +1,4 @@ + +{ + "files.eol": "\n" +} diff --git a/medical-records/.vscode/tasks.json b/medical-records/.vscode/tasks.json new file mode 100644 index 0000000..4dec0ff --- /dev/null +++ b/medical-records/.vscode/tasks.json @@ -0,0 +1,19 @@ + +{ + "version": "2.0.0", + "tasks": [ + { + "label": "check contracts", + "group": "test", + "type": "shell", + "command": "clarinet check" + }, + { + "type": "npm", + "script": "test", + "group": "test", + "problemMatcher": [], + "label": "npm test" + } + ] +} diff --git a/medical-records/Clarinet.toml b/medical-records/Clarinet.toml new file mode 100644 index 0000000..dc57216 --- /dev/null +++ b/medical-records/Clarinet.toml @@ -0,0 +1,19 @@ +[project] +name = 'medical-records' +description = '' +authors = [] +telemetry = true +cache_dir = '.\.cache' +requirements = [] +[contracts.health-records] +path = 'contracts/health-records.clar' +clarity_version = 2 +epoch = 2.5 +[repl.analysis] +passes = ['check_checker'] + +[repl.analysis.check_checker] +strict = false +trusted_sender = false +trusted_caller = false +callee_filter = false diff --git a/medical-records/README.md b/medical-records/README.md new file mode 100644 index 0000000..65bfd23 --- /dev/null +++ b/medical-records/README.md @@ -0,0 +1,138 @@ +# Personalized Medicine Smart Contract + +## Overview +The Personalized Medicine Smart Contract is a blockchain-based solution built on Clarity for managing medical records, prescriptions, and healthcare provider authorizations. This system enables secure, transparent, and efficient management of personalized medical data while maintaining patient privacy and regulatory compliance. + +## Features +- **Patient Record Management** + - Comprehensive medical history storage + - Genetic profile data management + - Current prescription tracking + - Healthcare provider authorization control + +- **Healthcare Provider Management** + - Provider registration and verification + - Specialization and licensing documentation + - Active status tracking + - Authorization-based access control + +- **Prescription System** + - Secure prescription creation and management + - Dosage and medication tracking + - Time-bound prescription validity + - Active/inactive status management + +## Technical Architecture + +### Data Structures + +#### 1. Patient Medical Records +```clarity +{ + comprehensive-medical-history: (string-ascii 256), + genetic-profile-data: (string-ascii 256), + current-prescriptions: (list 10 uint), + approved-healthcare-providers: (list 5 principal) +} +``` + +#### 2. Healthcare Provider Registry +```clarity +{ + medical-specialization: (string-ascii 64), + medical-license-identifier: (string-ascii 32), + provider-active-status: bool +} +``` + +#### 3. Prescription Records +```clarity +{ + patient-wallet-address: principal, + prescribing-provider: principal, + prescribed-medication: (string-ascii 64), + medication-dosage-instructions: (string-ascii 32), + prescription-start-timestamp: uint, + prescription-end-timestamp: uint, + prescription-active-status: bool +} +``` + +## Functions + +### Patient Management + +#### `register-new-patient` +- **Description**: Registers a new patient in the system +- **Parameters**: + - comprehensive-medical-history: (string-ascii 256) + - genetic-profile-data: (string-ascii 256) +- **Returns**: Success/Error response +- **Access**: Public + +#### `authorize-healthcare-provider` +- **Description**: Authorizes a healthcare provider to access patient records +- **Parameters**: + - provider-wallet-address: principal +- **Returns**: Success/Error response +- **Access**: Public (patient only) + +### Healthcare Provider Management + +#### `register-healthcare-provider` +- **Description**: Registers a new healthcare provider +- **Parameters**: + - medical-specialization: (string-ascii 64) + - medical-license-identifier: (string-ascii 32) +- **Returns**: Success/Error response +- **Access**: Public + +#### `verify-provider-credentials` +- **Description**: Verifies the active status of a healthcare provider +- **Parameters**: + - provider-wallet-address: principal +- **Returns**: Boolean +- **Access**: Read-only + +### Prescription Management + +#### `create-new-prescription` +- **Description**: Creates a new prescription for a patient +- **Parameters**: + - patient-wallet-address: principal + - prescribed-medication: (string-ascii 64) + - medication-dosage-instructions: (string-ascii 32) + - prescription-start-timestamp: uint + - prescription-end-timestamp: uint +- **Returns**: Success/Error response +- **Access**: Public (authorized providers only) + +## Error Codes +- `ERR-UNAUTHORIZED-ACCESS (u1)`: Unauthorized access attempt +- `ERR-DUPLICATE-PATIENT-RECORD (u2)`: Patient already registered +- `ERR-PATIENT-RECORD-NOT-FOUND (u3)`: Patient record doesn't exist +- `ERR-INVALID-PRESCRIPTION-DATA (u4)`: Invalid prescription parameters +- `ERR-DUPLICATE-HEALTHCARE-PROVIDER (u5)`: Provider already registered +- `ERR-HEALTHCARE-PROVIDER-NOT-FOUND (u6)`: Provider not found in registry + +## Security Considerations +1. **Access Control** + - Only authorized healthcare providers can access patient records + - Patients control provider authorization + - Prescription management restricted to authorized providers + +2. **Data Privacy** + - Sensitive data stored with appropriate access controls + - Limited list sizes to prevent overflow attacks + - Status tracking for all entities + +3. **Input Validation** + - Comprehensive error checking + - Date validation for prescriptions + - Authorization verification + +## Contributing +1. Fork the repository +2. Create feature branch +3. Commit changes +4. Create pull request \ No newline at end of file diff --git a/medical-records/Testnet.toml b/medical-records/Testnet.toml new file mode 100644 index 0000000..b9cfb45 --- /dev/null +++ b/medical-records/Testnet.toml @@ -0,0 +1,7 @@ +[network] +name = "testnet" +stacks_node_rpc_address = "https://api.testnet.hiro.so" +deployment_fee_rate = 10 + +[accounts.deployer] +mnemonic = "" diff --git a/medical-records/contracts/health-records.clar b/medical-records/contracts/health-records.clar new file mode 100644 index 0000000..377f31f --- /dev/null +++ b/medical-records/contracts/health-records.clar @@ -0,0 +1,198 @@ +;; Personalized Medicine Contract +;; Handles patient medical records, prescriptions, and healthcare provider authorizations + +;; Error codes +(define-constant ERR-UNAUTHORIZED-ACCESS (err u1)) +(define-constant ERR-DUPLICATE-PATIENT-RECORD (err u2)) +(define-constant ERR-PATIENT-RECORD-NOT-FOUND (err u3)) +(define-constant ERR-INVALID-PRESCRIPTION-DATA (err u4)) +(define-constant ERR-DUPLICATE-HEALTHCARE-PROVIDER (err u5)) +(define-constant ERR-HEALTHCARE-PROVIDER-NOT-FOUND (err u6)) + +;; Data structures +(define-map patient-medical-records + { patient-wallet-address: principal } + { + comprehensive-medical-history: (string-ascii 256), + genetic-profile-data: (string-ascii 256), + current-prescriptions: (list 10 uint), + approved-healthcare-providers: (list 5 principal) + } +) + +(define-map healthcare-provider-registry + { provider-wallet-address: principal } + { + medical-specialization: (string-ascii 64), + medical-license-identifier: (string-ascii 32), + provider-active-status: bool + } +) + +(define-map prescription-records + { prescription-identifier: uint } + { + patient-wallet-address: principal, + prescribing-provider: principal, + prescribed-medication: (string-ascii 64), + medication-dosage-instructions: (string-ascii 32), + prescription-start-timestamp: uint, + prescription-end-timestamp: uint, + prescription-active-status: bool + } +) + +;; Authorization verification +(define-private (verify-provider-authorization (patient-wallet-address principal) (provider-wallet-address principal)) + (let ((patient-record (get-patient-medical-record patient-wallet-address))) + (match patient-record + record (is-some (index-of (get approved-healthcare-providers record) provider-wallet-address)) + false + ) + ) +) + +;; Patient management functions +(define-public (register-new-patient (comprehensive-medical-history (string-ascii 256)) (genetic-profile-data (string-ascii 256))) + (let ((requesting-wallet tx-sender)) + (if (is-some (get-patient-medical-record requesting-wallet)) + ERR-DUPLICATE-PATIENT-RECORD + (ok (map-set patient-medical-records + { patient-wallet-address: requesting-wallet } + { + comprehensive-medical-history: comprehensive-medical-history, + genetic-profile-data: genetic-profile-data, + current-prescriptions: (list), + approved-healthcare-providers: (list) + } + )) + ) + ) +) + +(define-read-only (get-patient-medical-record (patient-wallet-address principal)) + (map-get? patient-medical-records { patient-wallet-address: patient-wallet-address }) +) + +(define-public (authorize-healthcare-provider (provider-wallet-address principal)) + (let ( + (requesting-wallet tx-sender) + (patient-record (get-patient-medical-record requesting-wallet)) + ) + (match patient-record + existing-record ( + (ok (map-set patient-medical-records + { patient-wallet-address: requesting-wallet } + (merge existing-record + { approved-healthcare-providers: (unwrap! (as-max-len? (append (get approved-healthcare-providers existing-record) provider-wallet-address) u5) ERR-UNAUTHORIZED-ACCESS) } + ) + )) + ) + ERR-PATIENT-RECORD-NOT-FOUND + ) + ) +) + +;; Healthcare provider functions +(define-public (register-healthcare-provider (medical-specialization (string-ascii 64)) (medical-license-identifier (string-ascii 32))) + (let ((requesting-wallet tx-sender)) + (if (is-some (map-get? healthcare-provider-registry { provider-wallet-address: requesting-wallet })) + ERR-DUPLICATE-HEALTHCARE-PROVIDER + (ok (map-set healthcare-provider-registry + { provider-wallet-address: requesting-wallet } + { + medical-specialization: medical-specialization, + medical-license-identifier: medical-license-identifier, + provider-active-status: true + } + )) + ) + ) +) + +(define-read-only (get-provider-details (provider-wallet-address principal)) + (map-get? healthcare-provider-registry { provider-wallet-address: provider-wallet-address }) +) + +;; Prescription management functions +(define-data-var global-prescription-counter uint u0) + +(define-private (generate-prescription-identifier) + (let ((current-counter (var-get global-prescription-counter))) + (var-set global-prescription-counter (+ current-counter u1)) + current-counter + ) +) + +(define-public (create-new-prescription + (patient-wallet-address principal) + (prescribed-medication (string-ascii 64)) + (medication-dosage-instructions (string-ascii 32)) + (prescription-start-timestamp uint) + (prescription-end-timestamp uint) +) + (let ( + (prescribing-provider tx-sender) + (prescription-identifier (generate-prescription-identifier)) + ) + (asserts! (verify-provider-authorization patient-wallet-address prescribing-provider) ERR-UNAUTHORIZED-ACCESS) + (asserts! (< prescription-start-timestamp prescription-end-timestamp) ERR-INVALID-PRESCRIPTION-DATA) + + (ok (map-set prescription-records + { prescription-identifier: prescription-identifier } + { + patient-wallet-address: patient-wallet-address, + prescribing-provider: prescribing-provider, + prescribed-medication: prescribed-medication, + medication-dosage-instructions: medication-dosage-instructions, + prescription-start-timestamp: prescription-start-timestamp, + prescription-end-timestamp: prescription-end-timestamp, + prescription-active-status: true + } + )) + ) +) + +(define-read-only (get-prescription-details (prescription-identifier uint)) + (map-get? prescription-records { prescription-identifier: prescription-identifier }) +) + +(define-public (deactivate-existing-prescription (prescription-identifier uint)) + (let ( + (requesting-wallet tx-sender) + (prescription-record (get-prescription-details prescription-identifier)) + ) + (match prescription-record + existing-prescription ( + (asserts! (or + (is-eq requesting-wallet (get prescribing-provider existing-prescription)) + (is-eq requesting-wallet (get patient-wallet-address existing-prescription)) + ) ERR-UNAUTHORIZED-ACCESS) + (ok (map-set prescription-records + { prescription-identifier: prescription-identifier } + (merge existing-prescription { prescription-active-status: false }) + )) + ) + ERR-INVALID-PRESCRIPTION-DATA + ) + ) +) + +;; Utility functions +(define-read-only (get-patient-active-prescriptions (patient-wallet-address principal)) + (filter prescription-records + (lambda (prescription) + (and + (is-eq (get patient-wallet-address prescription) patient-wallet-address) + (get prescription-active-status prescription) + ) + ) + ) +) + +(define-read-only (verify-provider-credentials (provider-wallet-address principal)) + (match (get-provider-details provider-wallet-address) + provider-record (get provider-active-status provider-record) + false + ) +) \ No newline at end of file diff --git a/medical-records/package.json b/medical-records/package.json new file mode 100644 index 0000000..cb586b4 --- /dev/null +++ b/medical-records/package.json @@ -0,0 +1,24 @@ + +{ + "name": "medical-records-tests", + "version": "1.0.0", + "description": "Run unit tests on this project.", + "type": "module", + "private": true, + "scripts": { + "test": "vitest run", + "test:report": "vitest run -- --coverage --costs", + "test:watch": "chokidar \"tests/**/*.ts\" \"contracts/**/*.clar\" -c \"npm run test:report\"" + }, + "author": "", + "license": "ISC", + "dependencies": { + "@hirosystems/clarinet-sdk": "^2.3.2", + "@stacks/transactions": "^6.12.0", + "chokidar-cli": "^3.0.0", + "typescript": "^5.3.3", + "vite": "^5.1.4", + "vitest": "^1.3.1", + "vitest-environment-clarinet": "^2.0.0" + } +} diff --git a/medical-records/settings/Devnet.toml b/medical-records/settings/Devnet.toml new file mode 100644 index 0000000..7d865b9 --- /dev/null +++ b/medical-records/settings/Devnet.toml @@ -0,0 +1,151 @@ +[network] +name = "devnet" +deployment_fee_rate = 10 + +[accounts.deployer] +mnemonic = "twice kind fence tip hidden tilt action fragile skin nothing glory cousin green tomorrow spring wrist shed math olympic multiply hip blue scout claw" +balance = 100_000_000_000_000 +# secret_key: 753b7cc01a1a2e86221266a154af739463fce51219d97e4f856cd7200c3bd2a601 +# stx_address: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM +# btc_address: mqVnk6NPRdhntvfm4hh9vvjiRkFDUuSYsH + +[accounts.wallet_1] +mnemonic = "sell invite acquire kitten bamboo drastic jelly vivid peace spawn twice guilt pave pen trash pretty park cube fragile unaware remain midnight betray rebuild" +balance = 100_000_000_000_000 +# secret_key: 7287ba251d44a4d3fd9276c88ce34c5c52a038955511cccaf77e61068649c17801 +# stx_address: ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5 +# btc_address: mr1iPkD9N3RJZZxXRk7xF9d36gffa6exNC + +[accounts.wallet_2] +mnemonic = "hold excess usual excess ring elephant install account glad dry fragile donkey gaze humble truck breeze nation gasp vacuum limb head keep delay hospital" +balance = 100_000_000_000_000 +# secret_key: 530d9f61984c888536871c6573073bdfc0058896dc1adfe9a6a10dfacadc209101 +# stx_address: ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG +# btc_address: muYdXKmX9bByAueDe6KFfHd5Ff1gdN9ErG + +[accounts.wallet_3] +mnemonic = "cycle puppy glare enroll cost improve round trend wrist mushroom scorpion tower claim oppose clever elephant dinosaur eight problem before frozen dune wagon high" +balance = 100_000_000_000_000 +# secret_key: d655b2523bcd65e34889725c73064feb17ceb796831c0e111ba1a552b0f31b3901 +# stx_address: ST2JHG361ZXG51QTKY2NQCVBPPRRE2KZB1HR05NNC +# btc_address: mvZtbibDAAA3WLpY7zXXFqRa3T4XSknBX7 + +[accounts.wallet_4] +mnemonic = "board list obtain sugar hour worth raven scout denial thunder horse logic fury scorpion fold genuine phrase wealth news aim below celery when cabin" +balance = 100_000_000_000_000 +# secret_key: f9d7206a47f14d2870c163ebab4bf3e70d18f5d14ce1031f3902fbbc894fe4c701 +# stx_address: ST2NEB84ASENDXKYGJPQW86YXQCEFEX2ZQPG87ND +# btc_address: mg1C76bNTutiCDV3t9nWhZs3Dc8LzUufj8 + +[accounts.wallet_5] +mnemonic = "hurry aunt blame peanut heavy update captain human rice crime juice adult scale device promote vast project quiz unit note reform update climb purchase" +balance = 100_000_000_000_000 +# secret_key: 3eccc5dac8056590432db6a35d52b9896876a3d5cbdea53b72400bc9c2099fe801 +# stx_address: ST2REHHS5J3CERCRBEPMGH7921Q6PYKAADT7JP2VB +# btc_address: mweN5WVqadScHdA81aATSdcVr4B6dNokqx + +[accounts.wallet_6] +mnemonic = "area desk dutch sign gold cricket dawn toward giggle vibrant indoor bench warfare wagon number tiny universe sand talk dilemma pottery bone trap buddy" +balance = 100_000_000_000_000 +# secret_key: 7036b29cb5e235e5fd9b09ae3e8eec4404e44906814d5d01cbca968a60ed4bfb01 +# stx_address: ST3AM1A56AK2C1XAFJ4115ZSV26EB49BVQ10MGCS0 +# btc_address: mzxXgV6e4BZSsz8zVHm3TmqbECt7mbuErt + +[accounts.wallet_7] +mnemonic = "prevent gallery kind limb income control noise together echo rival record wedding sense uncover school version force bleak nuclear include danger skirt enact arrow" +balance = 100_000_000_000_000 +# secret_key: b463f0df6c05d2f156393eee73f8016c5372caa0e9e29a901bb7171d90dc4f1401 +# stx_address: ST3PF13W7Z0RRM42A8VZRVFQ75SV1K26RXEP8YGKJ +# btc_address: n37mwmru2oaVosgfuvzBwgV2ysCQRrLko7 + +[accounts.wallet_8] +mnemonic = "female adjust gallery certain visit token during great side clown fitness like hurt clip knife warm bench start reunion globe detail dream depend fortune" +balance = 100_000_000_000_000 +# secret_key: 6a1a754ba863d7bab14adbbc3f8ebb090af9e871ace621d3e5ab634e1422885e01 +# stx_address: ST3NBRSFKX28FQ2ZJ1MAKX58HKHSDGNV5N7R21XCP +# btc_address: n2v875jbJ4RjBnTjgbfikDfnwsDV5iUByw + +[accounts.faucet] +mnemonic = "shadow private easily thought say logic fault paddle word top book during ignore notable orange flight clock image wealth health outside kitten belt reform" +balance = 100_000_000_000_000 +# secret_key: de433bdfa14ec43aa1098d5be594c8ffb20a31485ff9de2923b2689471c401b801 +# stx_address: STNHKEPYEPJ8ET55ZZ0M5A34J0R3N5FM2CMMMAZ6 +# btc_address: mjSrB3wS4xab3kYqFktwBzfTdPg367ZJ2d + +[devnet] +disable_stacks_explorer = false +disable_stacks_api = false +# disable_subnet_api = false +# disable_bitcoin_explorer = true +# working_dir = "tmp/devnet" +# stacks_node_events_observers = ["host.docker.internal:8002"] +# miner_mnemonic = "fragile loan twenty basic net assault jazz absorb diet talk art shock innocent float punch travel gadget embrace caught blossom hockey surround initial reduce" +# miner_derivation_path = "m/44'/5757'/0'/0/0" +# faucet_mnemonic = "shadow private easily thought say logic fault paddle word top book during ignore notable orange flight clock image wealth health outside kitten belt reform" +# faucet_derivation_path = "m/44'/5757'/0'/0/0" +# orchestrator_port = 20445 +# bitcoin_node_p2p_port = 18444 +# bitcoin_node_rpc_port = 18443 +# bitcoin_node_username = "devnet" +# bitcoin_node_password = "devnet" +# bitcoin_controller_block_time = 30_000 +# stacks_node_rpc_port = 20443 +# stacks_node_p2p_port = 20444 +# stacks_api_port = 3999 +# stacks_api_events_port = 3700 +# bitcoin_explorer_port = 8001 +# stacks_explorer_port = 8000 +# postgres_port = 5432 +# postgres_username = "postgres" +# postgres_password = "postgres" +# postgres_database = "postgres" +# bitcoin_node_image_url = "quay.io/hirosystems/bitcoind:26.0" +# stacks_node_image_url = "quay.io/hirosystems/stacks-node:devnet-2.5" +# stacks_signer_image_url = "quay.io/hirosystems/stacks-signer:devnet-2.5" +# stacks_api_image_url = "hirosystems/stacks-blockchain-api:master" +# stacks_explorer_image_url = "hirosystems/explorer:latest" +# bitcoin_explorer_image_url = "quay.io/hirosystems/bitcoin-explorer:devnet" +# postgres_image_url = "postgres:alpine" +# enable_subnet_node = true +# subnet_node_image_url = "hirosystems/stacks-subnets:0.8.1" +# subnet_leader_mnemonic = "twice kind fence tip hidden tilt action fragile skin nothing glory cousin green tomorrow spring wrist shed math olympic multiply hip blue scout claw" +# subnet_leader_derivation_path = "m/44'/5757'/0'/0/0" +# subnet_contract_id = "ST173JK7NZBA4BS05ZRATQH1K89YJMTGEH1Z5J52E.subnet-v3-0-1" +# subnet_node_rpc_port = 30443 +# subnet_node_p2p_port = 30444 +# subnet_events_ingestion_port = 30445 +# subnet_node_events_observers = ["host.docker.internal:8002"] +# subnet_api_image_url = "hirosystems/stacks-blockchain-api:master" +# subnet_api_postgres_database = "subnet_api" + +# For testing in epoch 2.1 / using Clarity2 +# epoch_2_0 = 100 +# epoch_2_05 = 100 +# epoch_2_1 = 101 +# epoch_2_2 = 102 +# epoch_2_3 = 103 +# epoch_2_4 = 104 +# epoch_2_5 = 108 + + +# Send some stacking orders +[[devnet.pox_stacking_orders]] +start_at_cycle = 1 +duration = 12 +wallet = "wallet_1" +slots = 2 +btc_address = "mr1iPkD9N3RJZZxXRk7xF9d36gffa6exNC" + +[[devnet.pox_stacking_orders]] +start_at_cycle = 1 +duration = 12 +wallet = "wallet_2" +slots = 1 +btc_address = "muYdXKmX9bByAueDe6KFfHd5Ff1gdN9ErG" + +[[devnet.pox_stacking_orders]] +start_at_cycle = 1 +duration = 12 +wallet = "wallet_3" +slots = 1 +btc_address = "mvZtbibDAAA3WLpY7zXXFqRa3T4XSknBX7" diff --git a/medical-records/tests/health-records.test.ts b/medical-records/tests/health-records.test.ts new file mode 100644 index 0000000..f2d4bb8 --- /dev/null +++ b/medical-records/tests/health-records.test.ts @@ -0,0 +1,21 @@ + +import { describe, expect, it } from "vitest"; + +const accounts = simnet.getAccounts(); +const address1 = accounts.get("wallet_1")!; + +/* + The test below is an example. To learn more, read the testing documentation here: + https://docs.hiro.so/clarinet/feature-guides/test-contract-with-clarinet-sdk +*/ + +describe("example tests", () => { + it("ensures simnet is well initalised", () => { + expect(simnet.blockHeight).toBeDefined(); + }); + + // it("shows an example", () => { + // const { result } = simnet.callReadOnlyFn("counter", "get-counter", [], address1); + // expect(result).toBeUint(0); + // }); +}); diff --git a/medical-records/tsconfig.json b/medical-records/tsconfig.json new file mode 100644 index 0000000..1bdaf36 --- /dev/null +++ b/medical-records/tsconfig.json @@ -0,0 +1,26 @@ + +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ESNext"], + "skipLibCheck": true, + + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + + "strict": true, + "noImplicitAny": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": [ + "node_modules/@hirosystems/clarinet-sdk/vitest-helpers/src", + "tests" + ] +} diff --git a/medical-records/vitest.config.js b/medical-records/vitest.config.js new file mode 100644 index 0000000..c6a8506 --- /dev/null +++ b/medical-records/vitest.config.js @@ -0,0 +1,42 @@ + +/// + +import { defineConfig } from "vite"; +import { vitestSetupFilePath, getClarinetVitestsArgv } from "@hirosystems/clarinet-sdk/vitest"; + +/* + In this file, Vitest is configured so that it works seamlessly with Clarinet and the Simnet. + + The `vitest-environment-clarinet` will initialise the clarinet-sdk + and make the `simnet` object available globally in the test files. + + `vitestSetupFilePath` points to a file in the `@hirosystems/clarinet-sdk` package that does two things: + - run `before` hooks to initialize the simnet and `after` hooks to collect costs and coverage reports. + - load custom vitest matchers to work with Clarity values (such as `expect(...).toBeUint()`) + + The `getClarinetVitestsArgv()` will parse options passed to the command `vitest run --` + - vitest run -- --manifest ./Clarinet.toml # pass a custom path + - vitest run -- --coverage --costs # collect coverage and cost reports +*/ + +export default defineConfig({ + test: { + environment: "clarinet", // use vitest-environment-clarinet + pool: "forks", + poolOptions: { + threads: { singleThread: true }, + forks: { singleFork: true }, + }, + setupFiles: [ + vitestSetupFilePath, + // custom setup files can be added here + ], + environmentOptions: { + clarinet: { + ...getClarinetVitestsArgv(), + // add or override options + }, + }, + }, +}); + From a2cbb7a5d6df82fa08ec772e9e1a2694f9f083c0 Mon Sep 17 00:00:00 2001 From: Hillzo Date: Sat, 23 Nov 2024 02:37:29 +1100 Subject: [PATCH 2/3] Fixed all errors in the smart contract --- medical-records/contracts/health-records.clar | 110 ++++++++++++------ 1 file changed, 74 insertions(+), 36 deletions(-) diff --git a/medical-records/contracts/health-records.clar b/medical-records/contracts/health-records.clar index 377f31f..4d2a382 100644 --- a/medical-records/contracts/health-records.clar +++ b/medical-records/contracts/health-records.clar @@ -8,6 +8,7 @@ (define-constant ERR-INVALID-PRESCRIPTION-DATA (err u4)) (define-constant ERR-DUPLICATE-HEALTHCARE-PROVIDER (err u5)) (define-constant ERR-HEALTHCARE-PROVIDER-NOT-FOUND (err u6)) +(define-constant ERR-PRESCRIPTION-LIST-OVERFLOW (err u7)) ;; Data structures (define-map patient-medical-records @@ -42,6 +43,10 @@ } ) +;; Global variables +(define-data-var global-prescription-counter uint u0) +(define-data-var all-prescription-ids (list 100 uint) (list)) ;; Tracks all prescription IDs + ;; Authorization verification (define-private (verify-provider-authorization (patient-wallet-address principal) (provider-wallet-address principal)) (let ((patient-record (get-patient-medical-record patient-wallet-address))) @@ -80,14 +85,19 @@ (patient-record (get-patient-medical-record requesting-wallet)) ) (match patient-record - existing-record ( + existing-record (ok (map-set patient-medical-records { patient-wallet-address: requesting-wallet } (merge existing-record - { approved-healthcare-providers: (unwrap! (as-max-len? (append (get approved-healthcare-providers existing-record) provider-wallet-address) u5) ERR-UNAUTHORIZED-ACCESS) } + { approved-healthcare-providers: + (unwrap! + (as-max-len? + (append (get approved-healthcare-providers existing-record) provider-wallet-address) + u5) + ERR-UNAUTHORIZED-ACCESS) + } ) )) - ) ERR-PATIENT-RECORD-NOT-FOUND ) ) @@ -115,8 +125,6 @@ ) ;; Prescription management functions -(define-data-var global-prescription-counter uint u0) - (define-private (generate-prescription-identifier) (let ((current-counter (var-get global-prescription-counter))) (var-set global-prescription-counter (+ current-counter u1)) @@ -134,22 +142,31 @@ (let ( (prescribing-provider tx-sender) (prescription-identifier (generate-prescription-identifier)) + ) + (begin + (asserts! (verify-provider-authorization patient-wallet-address prescribing-provider) ERR-UNAUTHORIZED-ACCESS) + (asserts! (< prescription-start-timestamp prescription-end-timestamp) ERR-INVALID-PRESCRIPTION-DATA) + + ;; Add prescription record + (map-set prescription-records + { prescription-identifier: prescription-identifier } + { + patient-wallet-address: patient-wallet-address, + prescribing-provider: prescribing-provider, + prescribed-medication: prescribed-medication, + medication-dosage-instructions: medication-dosage-instructions, + prescription-start-timestamp: prescription-start-timestamp, + prescription-end-timestamp: prescription-end-timestamp, + prescription-active-status: true + } + ) + + ;; Add prescription ID to global list + (match (as-max-len? (append (var-get all-prescription-ids) prescription-identifier) u100) + success (ok (var-set all-prescription-ids success)) + ERR-PRESCRIPTION-LIST-OVERFLOW + ) ) - (asserts! (verify-provider-authorization patient-wallet-address prescribing-provider) ERR-UNAUTHORIZED-ACCESS) - (asserts! (< prescription-start-timestamp prescription-end-timestamp) ERR-INVALID-PRESCRIPTION-DATA) - - (ok (map-set prescription-records - { prescription-identifier: prescription-identifier } - { - patient-wallet-address: patient-wallet-address, - prescribing-provider: prescribing-provider, - prescribed-medication: prescribed-medication, - medication-dosage-instructions: medication-dosage-instructions, - prescription-start-timestamp: prescription-start-timestamp, - prescription-end-timestamp: prescription-end-timestamp, - prescription-active-status: true - } - )) ) ) @@ -161,32 +178,53 @@ (let ( (requesting-wallet tx-sender) (prescription-record (get-prescription-details prescription-identifier)) - ) + ) (match prescription-record - existing-prescription ( - (asserts! (or - (is-eq requesting-wallet (get prescribing-provider existing-prescription)) - (is-eq requesting-wallet (get patient-wallet-address existing-prescription)) - ) ERR-UNAUTHORIZED-ACCESS) - (ok (map-set prescription-records - { prescription-identifier: prescription-identifier } - (merge existing-prescription { prescription-active-status: false }) - )) - ) + existing-prescription + (begin + (asserts! (or + (is-eq requesting-wallet (get prescribing-provider existing-prescription)) + (is-eq requesting-wallet (get patient-wallet-address existing-prescription)) + ) ERR-UNAUTHORIZED-ACCESS) + (ok (map-set prescription-records + { prescription-identifier: prescription-identifier } + (merge existing-prescription { prescription-active-status: false }) + )) + ) ERR-INVALID-PRESCRIPTION-DATA ) ) ) -;; Utility functions +;; function to correctly use fold (define-read-only (get-patient-active-prescriptions (patient-wallet-address principal)) - (filter prescription-records - (lambda (prescription) - (and + (ok (fold filter-active-prescription-fold (var-get all-prescription-ids) (list))) +) + +(define-private (filter-active-prescription-fold + (prescription-id uint) + (filtered-list (list 100 uint)) +) + (let ((patient-wallet-address tx-sender)) + (if (is-active-prescription patient-wallet-address prescription-id) + (unwrap! (as-max-len? (append filtered-list prescription-id) u100) filtered-list) + filtered-list + ) + ) +) + +(define-private (is-active-prescription (patient-wallet-address principal) (prescription-identifier uint)) + (is-active-prescription-for-patient prescription-identifier patient-wallet-address) +) + +(define-private (is-active-prescription-for-patient (prescription-identifier uint) (patient-wallet-address principal)) + (match (get-prescription-details prescription-identifier) + prescription + (and (is-eq (get patient-wallet-address prescription) patient-wallet-address) (get prescription-active-status prescription) ) - ) + false ) ) From fbde1a67cd12ae5d2cadbd30fd85f3e2c7f8527a Mon Sep 17 00:00:00 2001 From: Hillzo Date: Sat, 23 Nov 2024 02:54:32 +1100 Subject: [PATCH 3/3] Fixed all warnings and security issues --- medical-records/contracts/health-records.clar | 171 ++++++++++-------- 1 file changed, 98 insertions(+), 73 deletions(-) diff --git a/medical-records/contracts/health-records.clar b/medical-records/contracts/health-records.clar index 4d2a382..8bea15c 100644 --- a/medical-records/contracts/health-records.clar +++ b/medical-records/contracts/health-records.clar @@ -9,6 +9,9 @@ (define-constant ERR-DUPLICATE-HEALTHCARE-PROVIDER (err u5)) (define-constant ERR-HEALTHCARE-PROVIDER-NOT-FOUND (err u6)) (define-constant ERR-PRESCRIPTION-LIST-OVERFLOW (err u7)) +(define-constant ERR-INVALID-INPUT (err u8)) +(define-constant ERR-PROVIDER-ALREADY-AUTHORIZED (err u9)) +(define-constant ERR-MAX-PROVIDERS-REACHED (err u10)) ;; Data structures (define-map patient-medical-records @@ -45,7 +48,32 @@ ;; Global variables (define-data-var global-prescription-counter uint u0) -(define-data-var all-prescription-ids (list 100 uint) (list)) ;; Tracks all prescription IDs +(define-data-var all-prescription-ids (list 100 uint) (list)) + +;; Helper functions for input validation +(define-private (is-valid-ascii-string (input (string-ascii 256))) + (and + (is-eq (len input) (len (concat input ""))) + (>= (len input) u1) + (<= (len input) u256) + ) +) + +(define-private (is-valid-ascii-string-64 (input (string-ascii 64))) + (and + (is-eq (len input) (len (concat input ""))) + (>= (len input) u1) + (<= (len input) u64) + ) +) + +(define-private (is-valid-ascii-string-32 (input (string-ascii 32))) + (and + (is-eq (len input) (len (concat input ""))) + (>= (len input) u1) + (<= (len input) u32) + ) +) ;; Authorization verification (define-private (verify-provider-authorization (patient-wallet-address principal) (provider-wallet-address principal)) @@ -60,18 +88,18 @@ ;; Patient management functions (define-public (register-new-patient (comprehensive-medical-history (string-ascii 256)) (genetic-profile-data (string-ascii 256))) (let ((requesting-wallet tx-sender)) - (if (is-some (get-patient-medical-record requesting-wallet)) - ERR-DUPLICATE-PATIENT-RECORD - (ok (map-set patient-medical-records - { patient-wallet-address: requesting-wallet } - { - comprehensive-medical-history: comprehensive-medical-history, - genetic-profile-data: genetic-profile-data, - current-prescriptions: (list), - approved-healthcare-providers: (list) - } - )) - ) + (asserts! (is-valid-ascii-string comprehensive-medical-history) ERR-INVALID-INPUT) + (asserts! (is-valid-ascii-string genetic-profile-data) ERR-INVALID-INPUT) + (asserts! (is-none (get-patient-medical-record requesting-wallet)) ERR-DUPLICATE-PATIENT-RECORD) + (ok (map-set patient-medical-records + { patient-wallet-address: requesting-wallet } + { + comprehensive-medical-history: comprehensive-medical-history, + genetic-profile-data: genetic-profile-data, + current-prescriptions: (list), + approved-healthcare-providers: (list) + } + )) ) ) @@ -84,21 +112,21 @@ (requesting-wallet tx-sender) (patient-record (get-patient-medical-record requesting-wallet)) ) - (match patient-record - existing-record - (ok (map-set patient-medical-records - { patient-wallet-address: requesting-wallet } - (merge existing-record - { approved-healthcare-providers: - (unwrap! - (as-max-len? - (append (get approved-healthcare-providers existing-record) provider-wallet-address) - u5) - ERR-UNAUTHORIZED-ACCESS) - } - ) - )) - ERR-PATIENT-RECORD-NOT-FOUND + (asserts! (is-some patient-record) ERR-PATIENT-RECORD-NOT-FOUND) + (let ((existing-record (unwrap-panic patient-record))) + (asserts! (< (len (get approved-healthcare-providers existing-record)) u5) ERR-MAX-PROVIDERS-REACHED) + (asserts! (is-none (index-of (get approved-healthcare-providers existing-record) provider-wallet-address)) ERR-PROVIDER-ALREADY-AUTHORIZED) + (ok (map-set patient-medical-records + { patient-wallet-address: requesting-wallet } + (merge existing-record + { approved-healthcare-providers: + (unwrap! (as-max-len? + (append (get approved-healthcare-providers existing-record) provider-wallet-address) + u5 + ) ERR-MAX-PROVIDERS-REACHED) + } + ) + )) ) ) ) @@ -106,17 +134,17 @@ ;; Healthcare provider functions (define-public (register-healthcare-provider (medical-specialization (string-ascii 64)) (medical-license-identifier (string-ascii 32))) (let ((requesting-wallet tx-sender)) - (if (is-some (map-get? healthcare-provider-registry { provider-wallet-address: requesting-wallet })) - ERR-DUPLICATE-HEALTHCARE-PROVIDER - (ok (map-set healthcare-provider-registry - { provider-wallet-address: requesting-wallet } - { - medical-specialization: medical-specialization, - medical-license-identifier: medical-license-identifier, - provider-active-status: true - } - )) - ) + (asserts! (is-valid-ascii-string-64 medical-specialization) ERR-INVALID-INPUT) + (asserts! (is-valid-ascii-string-32 medical-license-identifier) ERR-INVALID-INPUT) + (asserts! (is-none (get-provider-details requesting-wallet)) ERR-DUPLICATE-HEALTHCARE-PROVIDER) + (ok (map-set healthcare-provider-registry + { provider-wallet-address: requesting-wallet } + { + medical-specialization: medical-specialization, + medical-license-identifier: medical-license-identifier, + provider-active-status: true + } + )) ) ) @@ -143,29 +171,29 @@ (prescribing-provider tx-sender) (prescription-identifier (generate-prescription-identifier)) ) - (begin - (asserts! (verify-provider-authorization patient-wallet-address prescribing-provider) ERR-UNAUTHORIZED-ACCESS) - (asserts! (< prescription-start-timestamp prescription-end-timestamp) ERR-INVALID-PRESCRIPTION-DATA) + (asserts! (verify-provider-authorization patient-wallet-address prescribing-provider) ERR-UNAUTHORIZED-ACCESS) + (asserts! (< prescription-start-timestamp prescription-end-timestamp) ERR-INVALID-PRESCRIPTION-DATA) + (asserts! (is-valid-ascii-string-64 prescribed-medication) ERR-INVALID-INPUT) + (asserts! (is-valid-ascii-string-32 medication-dosage-instructions) ERR-INVALID-INPUT) - ;; Add prescription record - (map-set prescription-records - { prescription-identifier: prescription-identifier } - { - patient-wallet-address: patient-wallet-address, - prescribing-provider: prescribing-provider, - prescribed-medication: prescribed-medication, - medication-dosage-instructions: medication-dosage-instructions, - prescription-start-timestamp: prescription-start-timestamp, - prescription-end-timestamp: prescription-end-timestamp, - prescription-active-status: true - } - ) + ;; Add prescription record + (map-set prescription-records + { prescription-identifier: prescription-identifier } + { + patient-wallet-address: patient-wallet-address, + prescribing-provider: prescribing-provider, + prescribed-medication: prescribed-medication, + medication-dosage-instructions: medication-dosage-instructions, + prescription-start-timestamp: prescription-start-timestamp, + prescription-end-timestamp: prescription-end-timestamp, + prescription-active-status: true + } + ) - ;; Add prescription ID to global list - (match (as-max-len? (append (var-get all-prescription-ids) prescription-identifier) u100) - success (ok (var-set all-prescription-ids success)) - ERR-PRESCRIPTION-LIST-OVERFLOW - ) + ;; Add prescription ID to global list + (match (as-max-len? (append (var-get all-prescription-ids) prescription-identifier) u100) + success (ok (var-set all-prescription-ids success)) + ERR-PRESCRIPTION-LIST-OVERFLOW ) ) ) @@ -179,19 +207,16 @@ (requesting-wallet tx-sender) (prescription-record (get-prescription-details prescription-identifier)) ) - (match prescription-record - existing-prescription - (begin - (asserts! (or - (is-eq requesting-wallet (get prescribing-provider existing-prescription)) - (is-eq requesting-wallet (get patient-wallet-address existing-prescription)) - ) ERR-UNAUTHORIZED-ACCESS) - (ok (map-set prescription-records - { prescription-identifier: prescription-identifier } - (merge existing-prescription { prescription-active-status: false }) - )) - ) - ERR-INVALID-PRESCRIPTION-DATA + (asserts! (is-some prescription-record) ERR-INVALID-PRESCRIPTION-DATA) + (let ((existing-prescription (unwrap-panic prescription-record))) + (asserts! (or + (is-eq requesting-wallet (get prescribing-provider existing-prescription)) + (is-eq requesting-wallet (get patient-wallet-address existing-prescription)) + ) ERR-UNAUTHORIZED-ACCESS) + (ok (map-set prescription-records + { prescription-identifier: prescription-identifier } + (merge existing-prescription { prescription-active-status: false }) + )) ) ) )