Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Research/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

**/settings/Mainnet.toml
**/settings/Testnet.toml
.requirements/
history.txt
5 changes: 5 additions & 0 deletions Research/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

{
"deno.enable": true,
"files.eol": "\n"
}
18 changes: 18 additions & 0 deletions Research/.vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

{
"version": "2.0.0",
"tasks": [
{
"label": "check contracts",
"group": "test",
"type": "shell",
"command": "clarinet check"
},
{
"label": "test contracts",
"group": "test",
"type": "shell",
"command": "clarinet test"
}
]
}
23 changes: 23 additions & 0 deletions Research/Clarinet.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[project]
name = "Research"
authors = []
description = ""
telemetry = true
requirements = []
cache_dir = "/home/runner/Clar/Hillz/Research/./.requirements"
boot_contracts = ["pox", "costs-v2", "bns"]
[contracts.academic-research]
path = "contracts/academic-research.clar"

[repl]
costs_version = 2
parser_version = 2

[repl.analysis]
passes = ["check_checker"]

[repl.analysis.check_checker]
strict = false
trusted_sender = false
trusted_caller = false
callee_filter = false
305 changes: 305 additions & 0 deletions Research/contracts/academic-research.clar
Original file line number Diff line number Diff line change
@@ -0,0 +1,305 @@
;; Academic Research Funding Smart Contract

;; Define constants
(define-constant CONTRACT_OWNER tx-sender)
(define-constant ERR_NOT_AUTHORIZED (err u100))
(define-constant ERR_INVALID_AMOUNT (err u101))
(define-constant ERR_PROPOSAL_NOT_FOUND (err u102))
(define-constant ERR_INSUFFICIENT_FUNDS (err u103))
(define-constant ERR_INVALID_STATUS (err u104))
(define-constant ERR_DEADLINE_PASSED (err u105))
(define-constant ERR_INVALID_REVIEW (err u106))
(define-constant ERR_ALREADY_REVIEWED (err u107))
(define-constant ERR_NOT_ENOUGH_REVIEWS (err u108))
(define-constant ERR_INVALID_DEADLINE (err u109))
(define-constant ERR_INVALID_MILESTONES (err u110))
(define-constant ERR_INSUFFICIENT_REPUTATION (err u111))
(define-constant ERR_ACTIVE_PROPOSAL_EXISTS (err u112))
(define-constant ERR_EVENT_EMISSION_FAILED (err u113))
(define-constant ERR_INVALID_MILESTONE_INDEX (err u114))
(define-constant ERR_MAP_UPDATE_FAILED (err u115))
(define-constant MAX_EVENT_LENGTH u500)
(define-constant MAX_DESCRIPTION_LENGTH u500)
(define-constant ERR_INVALID_MILESTONE (err u116))

;; Define data maps
(define-map Proposals
{ proposal-id: uint }
{
researcher: principal,
title: (string-ascii 100),
description: (string-utf8 1000),
requested-amount: uint,
status: (string-ascii 20),
funded-amount: uint,
milestones: (list 5 (string-ascii 100)),
deadline: uint,
review-count: uint,
average-rating: uint,
escrow-amount: uint
}
)

(define-map ResearcherBalance principal uint)
(define-map ResearcherReputation principal uint)
(define-map Reviews { proposal-id: uint, reviewer: principal } { rating: uint, comment: (string-utf8 500) })
(define-map Votes { proposal-id: uint, voter: principal } uint)
(define-map ActiveResearcherProposals principal uint)

(define-map Events
{ event-id: uint }
{
event-type: (string-ascii 20),
proposal-id: uint,
data: (string-utf8 500)
}
)

;; Define variables
(define-data-var proposal-count uint u0)
(define-data-var total-funds uint u0)
(define-data-var min-reputation-for-proposal uint u10)
(define-data-var last-event-id uint u0)

;; Helper function
(define-private (update-proposal (proposal-id uint) (proposal {researcher: principal, title: (string-ascii 100), description: (string-utf8 1000), requested-amount: uint, status: (string-ascii 20), funded-amount: uint, milestones: (list 5 (string-ascii 100)), deadline: uint, review-count: uint, average-rating: uint, escrow-amount: uint}))
(if (map-set Proposals { proposal-id: proposal-id } proposal)
(ok true)
(err ERR_MAP_UPDATE_FAILED)))

(define-private (update-milestone-at-index
(milestones (list 5 (string-ascii 100)))
(index uint)
(new-milestone (string-ascii 100))
)
(let
((len (len milestones)))
(asserts! (< index len) ERR_INVALID_MILESTONE_INDEX)
(ok (get result (fold update-milestone-fold
milestones
{
current-index: u0,
target-index: index,
new-milestone: new-milestone,
result: (list)
}
)))
)
)

(define-private (update-milestone-fold
(milestone (string-ascii 100))
(state {
current-index: uint,
target-index: uint,
new-milestone: (string-ascii 100),
result: (list 5 (string-ascii 100))
})
)
(let
(
(updated-result (unwrap-panic (as-max-len?
(append (get result state)
(if (is-eq (get current-index state) (get target-index state))
(get new-milestone state)
milestone))
u5)))
)
(merge state {
current-index: (+ (get current-index state) u1),
result: updated-result
})
)
)

;; Private functions

(define-private (emit-event (event-type (string-ascii 20)) (proposal-id uint) (data (string-utf8 500)))
(let
((event-id (+ (var-get last-event-id) u1))
(truncated-data (unwrap-panic (as-max-len? data u500))))
(if (map-set Events
{ event-id: event-id }
{
event-type: event-type,
proposal-id: proposal-id,
data: truncated-data
})
(begin
(var-set last-event-id event-id)
(ok event-id))
(err ERR_EVENT_EMISSION_FAILED))))

;; Public functions

(define-public (submit-proposal (title (string-ascii 100)) (description (string-utf8 1000)) (requested-amount uint) (milestones (list 5 (string-ascii 100))) (deadline uint))
(let
(
(proposal-id (+ (var-get proposal-count) u1))
(researcher-reputation (default-to u0 (map-get? ResearcherReputation tx-sender)))
(truncated-description (unwrap-panic (as-max-len? description u500)))
)
(asserts! (> requested-amount u0) ERR_INVALID_AMOUNT)
(asserts! (> deadline block-height) ERR_INVALID_DEADLINE)
(asserts! (> (len milestones) u0) ERR_INVALID_MILESTONES)
(asserts! (>= researcher-reputation (var-get min-reputation-for-proposal)) ERR_INSUFFICIENT_REPUTATION)
(asserts! (is-none (map-get? ActiveResearcherProposals tx-sender)) ERR_ACTIVE_PROPOSAL_EXISTS)

(match (update-proposal proposal-id
{
researcher: tx-sender,
title: title,
description: description,
requested-amount: requested-amount,
status: "pending",
funded-amount: u0,
milestones: milestones,
deadline: deadline,
review-count: u0,
average-rating: u0,
escrow-amount: u0
})
update-success (begin
(var-set proposal-count proposal-id)
(map-set ActiveResearcherProposals tx-sender proposal-id)
(match (emit-event "proposal-submitted" proposal-id truncated-description)
emit-success (ok proposal-id)
emit-error emit-error))
update-error update-error))
)

;; Public functions

(define-public (fund-proposal (proposal-id uint) (amount uint))
(let
(
(proposal (unwrap! (map-get? Proposals { proposal-id: proposal-id }) (err ERR_PROPOSAL_NOT_FOUND)))
(current-balance (stx-get-balance tx-sender))
)
(asserts! (>= current-balance amount) (err ERR_INSUFFICIENT_FUNDS))
(asserts! (is-eq (get status proposal) "approved") (err ERR_INVALID_STATUS))
(asserts! (<= (get deadline proposal) block-height) (err ERR_DEADLINE_PASSED))
(match (stx-transfer? amount tx-sender (as-contract tx-sender))
success
(let
((new-funded-amount (+ (get funded-amount proposal) amount))
(new-status (if (>= new-funded-amount (get requested-amount proposal))
"funded"
"partially-funded")))
(var-set total-funds (+ (var-get total-funds) amount))
(match (update-proposal proposal-id
(merge proposal {
status: new-status,
funded-amount: new-funded-amount,
escrow-amount: (+ (get escrow-amount proposal) amount)
}))
update-success
(let
((researcher-balance (default-to u0 (map-get? ResearcherBalance (get researcher proposal)))))
(if (map-set ResearcherBalance
(get researcher proposal)
(+ researcher-balance amount))
(match (emit-event "proposal-funded" proposal-id u"Proposal funded")
emit-success (ok true)
emit-error (err ERR_EVENT_EMISSION_FAILED))
(err ERR_MAP_UPDATE_FAILED)))
update-error (err ERR_MAP_UPDATE_FAILED)))
error (err ERR_INSUFFICIENT_FUNDS))
)
)

(define-public (approve-proposal (proposal-id uint))
(let
((proposal (unwrap! (map-get? Proposals { proposal-id: proposal-id }) (err ERR_PROPOSAL_NOT_FOUND))))
(asserts! (is-eq tx-sender CONTRACT_OWNER) (err ERR_NOT_AUTHORIZED))
(asserts! (is-eq (get status proposal) "pending") (err ERR_INVALID_STATUS))
(match (update-proposal proposal-id
(merge proposal { status: "approved" }))
update-success
(match (emit-event "proposal-approved" proposal-id u"Proposal approved")
emit-success (ok true)
emit-error (err ERR_EVENT_EMISSION_FAILED))
update-error (err ERR_MAP_UPDATE_FAILED))
)
)

(define-public (submit-review (proposal-id uint) (rating uint) (comment (string-utf8 500)))
(let
((proposal (unwrap! (map-get? Proposals { proposal-id: proposal-id }) (err ERR_PROPOSAL_NOT_FOUND)))
(existing-review (map-get? Reviews { proposal-id: proposal-id, reviewer: tx-sender })))
(asserts! (and (>= rating u1) (<= rating u5)) (err ERR_INVALID_REVIEW))
(asserts! (is-none existing-review) (err ERR_ALREADY_REVIEWED))
(if (map-set Reviews
{ proposal-id: proposal-id, reviewer: tx-sender }
{ rating: rating, comment: comment })
(let
((new-review-count (+ (get review-count proposal) u1))
(new-average-rating (/ (+ (* (get average-rating proposal) (get review-count proposal)) rating) new-review-count)))
(if (map-set Proposals
{ proposal-id: proposal-id }
(merge proposal {
review-count: new-review-count,
average-rating: new-average-rating
}))
(match (emit-event "review-submitted" proposal-id comment)
success (ok true)
error (err ERR_EVENT_EMISSION_FAILED))
(err ERR_MAP_UPDATE_FAILED)))
(err ERR_MAP_UPDATE_FAILED))
)
)

(define-public (release-funds (proposal-id uint))
(let
((proposal (unwrap! (map-get? Proposals { proposal-id: proposal-id }) (err ERR_PROPOSAL_NOT_FOUND))))
(asserts! (is-eq tx-sender CONTRACT_OWNER) (err ERR_NOT_AUTHORIZED))
(asserts! (is-eq (get status proposal) "funded") (err ERR_INVALID_STATUS))
(asserts! (>= (get review-count proposal) u3) (err ERR_NOT_ENOUGH_REVIEWS))
(asserts! (>= (get average-rating proposal) u4) (err ERR_INVALID_REVIEW))
(match (as-contract (stx-transfer? (get escrow-amount proposal) tx-sender (get researcher proposal)))
transfer-result
(if (map-set Proposals
{ proposal-id: proposal-id }
(merge proposal {
status: "completed",
escrow-amount: u0
}))
(if (map-set ResearcherReputation
(get researcher proposal)
(+ (default-to u0 (map-get? ResearcherReputation (get researcher proposal))) u1))
(match (emit-event "funds-released" proposal-id u"Funds released to researcher")
event-result (ok true)
event-error (err ERR_EVENT_EMISSION_FAILED))
(err ERR_MAP_UPDATE_FAILED))
(err ERR_MAP_UPDATE_FAILED))
transfer-error (err ERR_INSUFFICIENT_FUNDS))
)
)

(define-public (update-milestone (proposal-id uint) (milestone-index uint) (new-milestone (string-ascii 100)))
(let
((proposal (unwrap! (map-get? Proposals { proposal-id: proposal-id }) ERR_PROPOSAL_NOT_FOUND))
(event-data (concat "Milestone updated: " new-milestone)))
(asserts! (is-eq tx-sender (get researcher proposal)) ERR_NOT_AUTHORIZED)
(asserts! (is-eq (get status proposal) "approved") ERR_INVALID_STATUS)
(match (update-milestone-at-index (get milestones proposal) milestone-index new-milestone)
updated-milestones
(if (map-set Proposals
{ proposal-id: proposal-id }
(merge proposal { milestones: updated-milestones }))
(match (emit-event "milestone-updated" proposal-id
(unwrap! (as-max-len? (utf8 event-data) u500) ERR_INVALID_MILESTONE))
event-success (ok true)
event-error (err ERR_EVENT_EMISSION_FAILED))
(err ERR_MAP_UPDATE_FAILED))
error error)
)
)

(define-public (set-min-reputation (new-min-reputation uint))
(begin
(asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_NOT_AUTHORIZED)
(var-set min-reputation-for-proposal new-min-reputation)
(ok true)
)
)
Loading