;; 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 stacks-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 (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 (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)
)
)