From 2eae0e55b7964576f3822e64c0fd789bbd240fc6 Mon Sep 17 00:00:00 2001 From: tundekomolafe Date: Sun, 25 Jan 2026 22:36:10 +0100 Subject: [PATCH] feat: initialize TrustScore protocol contract structure --- Clarinet.toml | 30 +++-- contracts/trustscore.clar | 234 ++++++++++++++++++++++++++++++++++++++ tests/trustscore.test.ts | 21 ++++ 3 files changed, 269 insertions(+), 16 deletions(-) create mode 100644 contracts/trustscore.clar create mode 100644 tests/trustscore.test.ts diff --git a/Clarinet.toml b/Clarinet.toml index 58bd7dc..6fe5fcf 100644 --- a/Clarinet.toml +++ b/Clarinet.toml @@ -1,21 +1,19 @@ [project] -name = "TrustScore" -description = "" +name = 'TrustScore' +description = '' authors = [] telemetry = true -cache_dir = "./.cache" - -# [contracts.counter] -# path = "contracts/counter.clar" - +cache_dir = './.cache' +requirements = [] +[contracts.trustscore] +path = 'contracts/trustscore.clar' +clarity_version = 2 +epoch = 2.5 [repl.analysis] -passes = ["check_checker"] -check_checker = { trusted_sender = false, trusted_caller = false, callee_filter = false } +passes = ['check_checker'] -# Check-checker settings: -# trusted_sender: if true, inputs are trusted after tx_sender has been checked. -# trusted_caller: if true, inputs are trusted after contract-caller has been checked. -# callee_filter: if true, untrusted data may be passed into a private function without a -# warning, if it gets checked inside. This check will also propagate up to the -# caller. -# More informations: https://www.hiro.so/blog/new-safety-checks-in-clarinet +[repl.analysis.check_checker] +strict = false +trusted_sender = false +trusted_caller = false +callee_filter = false diff --git a/contracts/trustscore.clar b/contracts/trustscore.clar new file mode 100644 index 0000000..5449c81 --- /dev/null +++ b/contracts/trustscore.clar @@ -0,0 +1,234 @@ +;; TrustScore Protocol: Decentralized Reputation System +;; A reputation management system where participants can: +;; 1. Register evaluation requests for entities +;; 2. Submit scored assessments during evaluation periods +;; 3. Finalize reputation scores based on weighted assessments +;; 4. Access historical trust metrics + +(define-constant protocol-admin tx-sender) + +;; Error definitions +(define-constant err-access-denied (err u200)) +(define-constant err-evaluation-exists (err u201)) +(define-constant err-evaluation-missing (err u202)) +(define-constant err-period-expired (err u203)) +(define-constant err-period-ongoing (err u204)) +(define-constant err-score-below-threshold (err u205)) +(define-constant err-unauthorized-evaluator (err u206)) +(define-constant err-invalid-entity (err u207)) +(define-constant err-score-finalized (err u208)) +(define-constant err-invalid-timeframe (err u209)) +(define-constant err-invalid-threshold (err u210)) +(define-constant err-admin-only (err u211)) +(define-constant err-period-closed (err u212)) +(define-constant err-empty-entity-name (err u213)) +(define-constant err-empty-description (err u214)) +(define-constant err-empty-metadata (err u215)) + +;; Storage maps +(define-map evaluations + { evaluation-id: uint } + { + entity-owner: principal, + entity-name: (string-ascii 64), + entity-description: (string-ascii 256), + metadata-link: (string-ascii 256), + start-height: uint, + end-height: uint, + min-threshold: uint, + aggregate-score: uint, + top-evaluator: (optional principal), + active-status: bool, + score-locked: bool + } +) + +(define-map evaluator-submissions + { evaluation-id: uint, evaluator: principal } + { score-value: uint, timestamp-height: uint } +) + +;; ID tracking +(define-data-var evaluation-counter uint u1) + +;; Protocol fee in basis points (3% = 300) +(define-data-var protocol-fee-basis uint u300) + +;; Getters + +(define-read-only (fetch-evaluation (evaluation-id uint)) + (map-get? evaluations { evaluation-id: evaluation-id }) +) + +(define-read-only (fetch-evaluator-score (evaluation-id uint) (evaluator principal)) + (map-get? evaluator-submissions { evaluation-id: evaluation-id, evaluator: evaluator }) +) + +(define-read-only (evaluation-registered (evaluation-id uint)) + (is-some (fetch-evaluation evaluation-id)) +) + +(define-read-only (is-evaluation-active (evaluation-id uint)) + (match (fetch-evaluation evaluation-id) + eval-data (and + (get active-status eval-data) + (< block-height (get end-height eval-data)) + ) + false + ) +) + +(define-read-only (evaluation-complete (evaluation-id uint)) + (match (fetch-evaluation evaluation-id) + eval-data (>= block-height (get end-height eval-data)) + false + ) +) + +(define-read-only (current-evaluation-id) + (var-get evaluation-counter) +) + +(define-read-only (fetch-protocol-fee) + (var-get protocol-fee-basis) +) + +(define-read-only (compute-protocol-fee (amount uint)) + (/ (* amount (var-get protocol-fee-basis)) u10000) +) + +;; Internal helpers + +(define-private (compute-net-settlement (amount uint)) + (- amount (compute-protocol-fee amount)) +) + +(define-private (verify-entity-name (name (string-ascii 64))) + (> (len name) u0) +) + +(define-private (verify-description (desc (string-ascii 256))) + (> (len desc) u0) +) + +(define-private (verify-metadata (meta (string-ascii 256))) + (> (len meta) u0) +) + +;; Core functions + +(define-public (register-evaluation + (entity-name (string-ascii 64)) + (entity-description (string-ascii 256)) + (metadata-link (string-ascii 256)) + (timeframe uint) + (min-threshold uint)) + (let ((eval-id (var-get evaluation-counter)) + (start-height block-height) + (end-height (+ block-height timeframe))) + (begin + (asserts! (verify-entity-name entity-name) err-empty-entity-name) + (asserts! (verify-description entity-description) err-empty-description) + (asserts! (verify-metadata metadata-link) err-empty-metadata) + (asserts! (> timeframe u0) err-invalid-timeframe) + (asserts! (> min-threshold u0) err-invalid-threshold) + + (map-set evaluations + { evaluation-id: eval-id } + { + entity-owner: tx-sender, + entity-name: entity-name, + entity-description: entity-description, + metadata-link: metadata-link, + start-height: start-height, + end-height: end-height, + min-threshold: min-threshold, + aggregate-score: u0, + top-evaluator: none, + active-status: true, + score-locked: false + } + ) + + (var-set evaluation-counter (+ eval-id u1)) + + (ok eval-id) + ) + ) +) + +(define-public (submit-score (evaluation-id uint) (score-value uint)) + (let ((eval-data (unwrap! (fetch-evaluation evaluation-id) err-evaluation-missing))) + (begin + (asserts! (get active-status eval-data) err-period-closed) + (asserts! (< block-height (get end-height eval-data)) err-period-expired) + + (asserts! (if (is-some (get top-evaluator eval-data)) + (> score-value (get aggregate-score eval-data)) + (>= score-value (get min-threshold eval-data))) + err-score-below-threshold) + + (map-set evaluator-submissions + { evaluation-id: evaluation-id, evaluator: tx-sender } + { score-value: score-value, timestamp-height: block-height } + ) + + (map-set evaluations + { evaluation-id: evaluation-id } + (merge eval-data { + aggregate-score: score-value, + top-evaluator: (some tx-sender) + }) + ) + + (ok true) + ) + ) +) + +(define-public (terminate-evaluation (evaluation-id uint)) + (let ((eval-data (unwrap! (fetch-evaluation evaluation-id) err-evaluation-missing))) + (begin + (asserts! (is-eq tx-sender (get entity-owner eval-data)) err-invalid-entity) + (asserts! (get active-status eval-data) err-period-closed) + (asserts! (< block-height (get end-height eval-data)) err-period-expired) + + (map-set evaluations + { evaluation-id: evaluation-id } + (merge eval-data { + active-status: false, + end-height: block-height + }) + ) + + (ok true) + ) + ) +) + +(define-public (cancel-evaluation (evaluation-id uint)) + (let ((eval-data (unwrap! (fetch-evaluation evaluation-id) err-evaluation-missing))) + (begin + (asserts! (is-eq tx-sender (get entity-owner eval-data)) err-invalid-entity) + (asserts! (get active-status eval-data) err-period-closed) + (asserts! (is-eq (get aggregate-score eval-data) u0) err-score-below-threshold) + + (map-set evaluations + { evaluation-id: evaluation-id } + (merge eval-data { active-status: false }) + ) + + (ok true) + ) + ) +) + +;; Admin controls + +(define-public (adjust-protocol-fee (new-fee-basis uint)) + (begin + (asserts! (is-eq tx-sender protocol-admin) err-admin-only) + (asserts! (<= new-fee-basis u1000) err-access-denied) + (ok (var-set protocol-fee-basis new-fee-basis)) + ) +) \ No newline at end of file diff --git a/tests/trustscore.test.ts b/tests/trustscore.test.ts new file mode 100644 index 0000000..4bb9cf3 --- /dev/null +++ b/tests/trustscore.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/stacks/clarinet-js-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); + // }); +});