Source Code

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

Functions (7)

FunctionAccessArgs
update-milestone-at-indexprivatemilestones: (list 5 (string-ascii 100
update-milestone-foldprivatemilestone: (string-ascii 100
emit-eventprivateevent-type: (string-ascii 20
submit-proposalpublictitle: (string-ascii 100
approve-proposalpublicproposal-id: uint
submit-reviewpublicproposal-id: uint, rating: uint, comment: (string-utf8 500
set-min-reputationpublicnew-min-reputation: uint