From a9b8db4dfa80263606e66652e4e3a2cdae3496e6 Mon Sep 17 00:00:00 2001 From: blessychoco Date: Sat, 28 Sep 2024 01:23:37 +0100 Subject: [PATCH 1/7] modified fund-proposal function --- research-chain/contracts/sci-bloom.clar | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/research-chain/contracts/sci-bloom.clar b/research-chain/contracts/sci-bloom.clar index 490760b..1b87bf1 100644 --- a/research-chain/contracts/sci-bloom.clar +++ b/research-chain/contracts/sci-bloom.clar @@ -57,19 +57,20 @@ (define-public (fund-proposal (proposal-id uint) (amount uint)) (let ( - (proposal (unwrap! (map-get? proposals { proposal-id: proposal-id }) (err ERR_PROPOSAL_NOT_FOUND))) + (proposal (unwrap-panic (map-get? proposals { proposal-id: proposal-id }))) (new-funding (+ (get current-funding proposal) amount)) ) (asserts! (> amount u0) ERR_INVALID_AMOUNT) (asserts! (get is-active proposal) ERR_ALREADY_FUNDED) + (asserts! (< new-funding (get funding-goal proposal)) (err u104)) (try! (stx-transfer? amount tx-sender (as-contract tx-sender))) (map-set proposals { proposal-id: proposal-id } - (merge proposal { current-funding: new-funding, is-active: (< new-funding (get funding-goal proposal)) }) + (merge proposal { current-funding: new-funding }) ) (map-set fundings { proposal-id: proposal-id, funder: tx-sender } - { amount: (default-to u0 (get amount (map-get? fundings { proposal-id: proposal-id, funder: tx-sender }))) } + { amount: (+ amount (default-to u0 (get amount (map-get? fundings { proposal-id: proposal-id, funder: tx-sender })))) } ) (ok true) ) @@ -79,10 +80,11 @@ (define-public (withdraw-funds (proposal-id uint)) (let ( - (proposal (unwrap! (map-get? proposals { proposal-id: proposal-id }) (err ERR_PROPOSAL_NOT_FOUND))) + (proposal (unwrap-panic (map-get? proposals { proposal-id: proposal-id }))) ) (asserts! (is-eq (get researcher proposal) tx-sender) ERR_NOT_AUTHORIZED) (asserts! (>= (get current-funding proposal) (get funding-goal proposal)) ERR_INVALID_AMOUNT) + (asserts! (get is-active proposal) ERR_ALREADY_FUNDED) (try! (as-contract (stx-transfer? (get current-funding proposal) tx-sender (get researcher proposal)))) (map-set proposals { proposal-id: proposal-id } From 8c3fb44bc41c045718122a86e459438b1948cd0c Mon Sep 17 00:00:00 2001 From: blessychoco Date: Sat, 28 Sep 2024 01:27:07 +0100 Subject: [PATCH 2/7] Resolved unchecked data warnings --- research-chain/contracts/sci-bloom.clar | 120 +++++++++++++++--------- 1 file changed, 76 insertions(+), 44 deletions(-) diff --git a/research-chain/contracts/sci-bloom.clar b/research-chain/contracts/sci-bloom.clar index 1b87bf1..b33a912 100644 --- a/research-chain/contracts/sci-bloom.clar +++ b/research-chain/contracts/sci-bloom.clar @@ -7,6 +7,10 @@ (define-constant ERR_INVALID_AMOUNT (err u101)) (define-constant ERR_PROPOSAL_NOT_FOUND (err u102)) (define-constant ERR_ALREADY_FUNDED (err u103)) +(define-constant ERR_INVALID_TITLE (err u104)) +(define-constant ERR_INVALID_DESCRIPTION (err u105)) +(define-constant ERR_INVALID_FUNDING_GOAL (err u106)) +(define-constant ERR_INVALID_PROPOSAL_ID (err u107)) ;; Define data maps (define-map proposals @@ -29,68 +33,96 @@ ;; Define variables (define-data-var proposal-counter uint u0) +;; Helper functions for input validation +(define-private (is-valid-title (title (string-ascii 100))) + (and (> (len title) u0) (<= (len title) u100)) +) + +(define-private (is-valid-description (description (string-ascii 1000))) + (and (> (len description) u0) (<= (len description) u1000)) +) + +(define-private (is-valid-funding-goal (funding-goal uint)) + (> funding-goal u0) +) + +(define-private (is-valid-proposal-id (proposal-id uint)) + (<= proposal-id (var-get proposal-counter)) +) + ;; Public functions ;; Submit a new research proposal (define-public (submit-proposal (title (string-ascii 100)) (description (string-ascii 1000)) (funding-goal uint)) - (let - ( - (proposal-id (+ (var-get proposal-counter) u1)) - ) - (map-set proposals - { proposal-id: proposal-id } - { - researcher: tx-sender, - title: title, - description: description, - funding-goal: funding-goal, - current-funding: u0, - is-active: true - } + (begin + (asserts! (is-valid-title title) ERR_INVALID_TITLE) + (asserts! (is-valid-description description) ERR_INVALID_DESCRIPTION) + (asserts! (is-valid-funding-goal funding-goal) ERR_INVALID_FUNDING_GOAL) + (let + ( + (proposal-id (+ (var-get proposal-counter) u1)) + ) + (map-set proposals + { proposal-id: proposal-id } + { + researcher: tx-sender, + title: title, + description: description, + funding-goal: funding-goal, + current-funding: u0, + is-active: true + } + ) + (var-set proposal-counter proposal-id) + (ok proposal-id) ) - (var-set proposal-counter proposal-id) - (ok proposal-id) ) ) ;; Fund a research proposal (define-public (fund-proposal (proposal-id uint) (amount uint)) - (let - ( - (proposal (unwrap-panic (map-get? proposals { proposal-id: proposal-id }))) - (new-funding (+ (get current-funding proposal) amount)) - ) + (begin + (asserts! (is-valid-proposal-id proposal-id) ERR_INVALID_PROPOSAL_ID) (asserts! (> amount u0) ERR_INVALID_AMOUNT) - (asserts! (get is-active proposal) ERR_ALREADY_FUNDED) - (asserts! (< new-funding (get funding-goal proposal)) (err u104)) - (try! (stx-transfer? amount tx-sender (as-contract tx-sender))) - (map-set proposals - { proposal-id: proposal-id } - (merge proposal { current-funding: new-funding }) - ) - (map-set fundings - { proposal-id: proposal-id, funder: tx-sender } - { amount: (+ amount (default-to u0 (get amount (map-get? fundings { proposal-id: proposal-id, funder: tx-sender })))) } + (let + ( + (proposal (unwrap-panic (map-get? proposals { proposal-id: proposal-id }))) + (new-funding (+ (get current-funding proposal) amount)) + ) + (asserts! (get is-active proposal) ERR_ALREADY_FUNDED) + (asserts! (<= new-funding (get funding-goal proposal)) ERR_INVALID_AMOUNT) + (try! (stx-transfer? amount tx-sender (as-contract tx-sender))) + (map-set proposals + { proposal-id: proposal-id } + (merge proposal { current-funding: new-funding }) + ) + (map-set fundings + { proposal-id: proposal-id, funder: tx-sender } + { amount: (+ amount (default-to u0 (get amount (map-get? fundings { proposal-id: proposal-id, funder: tx-sender })))) } + ) + (ok true) ) - (ok true) ) ) ;; Withdraw funds for a fully funded proposal (only by the researcher) (define-public (withdraw-funds (proposal-id uint)) - (let - ( - (proposal (unwrap-panic (map-get? proposals { proposal-id: proposal-id }))) - ) - (asserts! (is-eq (get researcher proposal) tx-sender) ERR_NOT_AUTHORIZED) - (asserts! (>= (get current-funding proposal) (get funding-goal proposal)) ERR_INVALID_AMOUNT) - (asserts! (get is-active proposal) ERR_ALREADY_FUNDED) - (try! (as-contract (stx-transfer? (get current-funding proposal) tx-sender (get researcher proposal)))) - (map-set proposals - { proposal-id: proposal-id } - (merge proposal { current-funding: u0, is-active: false }) + (begin + (asserts! (is-valid-proposal-id proposal-id) ERR_INVALID_PROPOSAL_ID) + (let + ( + (proposal (unwrap-panic (map-get? proposals { proposal-id: proposal-id }))) + ) + (asserts! (is-eq (get researcher proposal) tx-sender) ERR_NOT_AUTHORIZED) + (asserts! (>= (get current-funding proposal) (get funding-goal proposal)) ERR_INVALID_AMOUNT) + (asserts! (get is-active proposal) ERR_ALREADY_FUNDED) + (try! (as-contract (stx-transfer? (get current-funding proposal) tx-sender (get researcher proposal)))) + (map-set proposals + { proposal-id: proposal-id } + (merge proposal { current-funding: u0, is-active: false }) + ) + (ok true) ) - (ok true) ) ) From d2b1fc693d6fc48a919012a648460f5bbe34949f Mon Sep 17 00:00:00 2001 From: blessychoco Date: Sat, 28 Sep 2024 01:50:23 +0100 Subject: [PATCH 3/7] Implemented proposal statuses --- research-chain/contracts/sci-bloom.clar | 50 +++++++++++++++++++++---- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/research-chain/contracts/sci-bloom.clar b/research-chain/contracts/sci-bloom.clar index b33a912..7b8319c 100644 --- a/research-chain/contracts/sci-bloom.clar +++ b/research-chain/contracts/sci-bloom.clar @@ -11,6 +11,12 @@ (define-constant ERR_INVALID_DESCRIPTION (err u105)) (define-constant ERR_INVALID_FUNDING_GOAL (err u106)) (define-constant ERR_INVALID_PROPOSAL_ID (err u107)) +(define-constant ERR_INVALID_STATUS (err u108)) + +;; Define proposal statuses +(define-constant STATUS_OPEN u0) +(define-constant STATUS_FUNDED u1) +(define-constant STATUS_CLOSED u2) ;; Define data maps (define-map proposals @@ -21,7 +27,7 @@ description: (string-ascii 1000), funding-goal: uint, current-funding: uint, - is-active: bool + status: uint } ) @@ -50,6 +56,10 @@ (<= proposal-id (var-get proposal-counter)) ) +(define-private (is-valid-status (status uint)) + (or (is-eq status STATUS_OPEN) (is-eq status STATUS_FUNDED) (is-eq status STATUS_CLOSED)) +) + ;; Public functions ;; Submit a new research proposal @@ -70,7 +80,7 @@ description: description, funding-goal: funding-goal, current-funding: u0, - is-active: true + status: STATUS_OPEN } ) (var-set proposal-counter proposal-id) @@ -89,12 +99,15 @@ (proposal (unwrap-panic (map-get? proposals { proposal-id: proposal-id }))) (new-funding (+ (get current-funding proposal) amount)) ) - (asserts! (get is-active proposal) ERR_ALREADY_FUNDED) + (asserts! (is-eq (get status proposal) STATUS_OPEN) ERR_ALREADY_FUNDED) (asserts! (<= new-funding (get funding-goal proposal)) ERR_INVALID_AMOUNT) (try! (stx-transfer? amount tx-sender (as-contract tx-sender))) (map-set proposals { proposal-id: proposal-id } - (merge proposal { current-funding: new-funding }) + (merge proposal { + current-funding: new-funding, + status: (if (is-eq new-funding (get funding-goal proposal)) STATUS_FUNDED STATUS_OPEN) + }) ) (map-set fundings { proposal-id: proposal-id, funder: tx-sender } @@ -114,12 +127,30 @@ (proposal (unwrap-panic (map-get? proposals { proposal-id: proposal-id }))) ) (asserts! (is-eq (get researcher proposal) tx-sender) ERR_NOT_AUTHORIZED) - (asserts! (>= (get current-funding proposal) (get funding-goal proposal)) ERR_INVALID_AMOUNT) - (asserts! (get is-active proposal) ERR_ALREADY_FUNDED) + (asserts! (is-eq (get status proposal) STATUS_FUNDED) ERR_INVALID_STATUS) (try! (as-contract (stx-transfer? (get current-funding proposal) tx-sender (get researcher proposal)))) (map-set proposals { proposal-id: proposal-id } - (merge proposal { current-funding: u0, is-active: false }) + (merge proposal { current-funding: u0, status: STATUS_CLOSED }) + ) + (ok true) + ) + ) +) + +;; Update proposal status (only by CONTRACT_OWNER) +(define-public (update-proposal-status (proposal-id uint) (new-status uint)) + (begin + (asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_NOT_AUTHORIZED) + (asserts! (is-valid-proposal-id proposal-id) ERR_INVALID_PROPOSAL_ID) + (asserts! (is-valid-status new-status) ERR_INVALID_STATUS) + (let + ( + (proposal (unwrap-panic (map-get? proposals { proposal-id: proposal-id }))) + ) + (map-set proposals + { proposal-id: proposal-id } + (merge proposal { status: new-status }) ) (ok true) ) @@ -141,4 +172,9 @@ ;; Get funding amount for a specific proposal and funder (define-read-only (get-funding (proposal-id uint) (funder principal)) (map-get? fundings { proposal-id: proposal-id, funder: funder }) +) + +;; Get proposal status +(define-read-only (get-proposal-status (proposal-id uint)) + (get status (unwrap-panic (map-get? proposals { proposal-id: proposal-id }))) ) \ No newline at end of file From aa510be32c2ff724d211afc8b0a2cc8a760388a5 Mon Sep 17 00:00:00 2001 From: blessychoco Date: Sat, 28 Sep 2024 02:02:27 +0100 Subject: [PATCH 4/7] Reviewer and voting system --- research-chain/contracts/sci-bloom.clar | 125 ++++++++++++++++++++++-- 1 file changed, 116 insertions(+), 9 deletions(-) diff --git a/research-chain/contracts/sci-bloom.clar b/research-chain/contracts/sci-bloom.clar index 7b8319c..60f5d11 100644 --- a/research-chain/contracts/sci-bloom.clar +++ b/research-chain/contracts/sci-bloom.clar @@ -1,5 +1,5 @@ -;; ScienceBloom: Decentralized Scientific Research Funding -;; This contract allows researchers to submit proposals and receive funding +;; ResearchChain: Decentralized Scientific Research Funding +;; This contract allows researchers to submit proposals, reviewers to vote, and funders to support approved projects ;; Define constants (define-constant CONTRACT_OWNER tx-sender) @@ -12,11 +12,21 @@ (define-constant ERR_INVALID_FUNDING_GOAL (err u106)) (define-constant ERR_INVALID_PROPOSAL_ID (err u107)) (define-constant ERR_INVALID_STATUS (err u108)) +(define-constant ERR_ALREADY_VOTED (err u109)) +(define-constant ERR_NOT_REVIEWER (err u110)) ;; Define proposal statuses -(define-constant STATUS_OPEN u0) -(define-constant STATUS_FUNDED u1) -(define-constant STATUS_CLOSED u2) +(define-constant STATUS_SUBMITTED u0) +(define-constant STATUS_UNDER_REVIEW u1) +(define-constant STATUS_APPROVED u2) +(define-constant STATUS_REJECTED u3) +(define-constant STATUS_OPEN u4) +(define-constant STATUS_FUNDED u5) +(define-constant STATUS_CLOSED u6) + +;; Define vote options +(define-constant VOTE_APPROVE u1) +(define-constant VOTE_REJECT u0) ;; Define data maps (define-map proposals @@ -27,7 +37,9 @@ description: (string-ascii 1000), funding-goal: uint, current-funding: uint, - status: uint + status: uint, + approve-votes: uint, + reject-votes: uint } ) @@ -36,8 +48,19 @@ { amount: uint } ) +(define-map reviewers + { reviewer: principal } + { is-active: bool } +) + +(define-map votes + { proposal-id: uint, reviewer: principal } + { vote: uint } +) + ;; Define variables (define-data-var proposal-counter uint u0) +(define-data-var required-votes uint u3) ;; Helper functions for input validation (define-private (is-valid-title (title (string-ascii 100))) @@ -57,7 +80,11 @@ ) (define-private (is-valid-status (status uint)) - (or (is-eq status STATUS_OPEN) (is-eq status STATUS_FUNDED) (is-eq status STATUS_CLOSED)) + (and (>= status STATUS_SUBMITTED) (<= status STATUS_CLOSED)) +) + +(define-private (is-reviewer (account principal)) + (default-to false (get is-active (map-get? reviewers { reviewer: account }))) ) ;; Public functions @@ -80,7 +107,9 @@ description: description, funding-goal: funding-goal, current-funding: u0, - status: STATUS_OPEN + status: STATUS_SUBMITTED, + approve-votes: u0, + reject-votes: u0 } ) (var-set proposal-counter proposal-id) @@ -89,6 +118,46 @@ ) ) +;; Vote on a proposal (only for reviewers) +(define-public (vote-on-proposal (proposal-id uint) (vote uint)) + (begin + (asserts! (is-reviewer tx-sender) ERR_NOT_REVIEWER) + (asserts! (is-valid-proposal-id proposal-id) ERR_INVALID_PROPOSAL_ID) + (asserts! (or (is-eq vote VOTE_APPROVE) (is-eq vote VOTE_REJECT)) ERR_INVALID_STATUS) + (let + ( + (proposal (unwrap-panic (map-get? proposals { proposal-id: proposal-id }))) + (existing-vote (map-get? votes { proposal-id: proposal-id, reviewer: tx-sender })) + ) + (asserts! (is-eq (get status proposal) STATUS_UNDER_REVIEW) ERR_INVALID_STATUS) + (asserts! (is-none existing-vote) ERR_ALREADY_VOTED) + (map-set votes { proposal-id: proposal-id, reviewer: tx-sender } { vote: vote }) + (if (is-eq vote VOTE_APPROVE) + (map-set proposals { proposal-id: proposal-id } + (merge proposal { approve-votes: (+ (get approve-votes proposal) u1) })) + (map-set proposals { proposal-id: proposal-id } + (merge proposal { reject-votes: (+ (get reject-votes proposal) u1) })) + ) + (let + ( + (updated-proposal (unwrap-panic (map-get? proposals { proposal-id: proposal-id }))) + (total-votes (+ (get approve-votes updated-proposal) (get reject-votes updated-proposal))) + ) + (if (>= total-votes (var-get required-votes)) + (if (> (get approve-votes updated-proposal) (get reject-votes updated-proposal)) + (map-set proposals { proposal-id: proposal-id } + (merge updated-proposal { status: STATUS_APPROVED })) + (map-set proposals { proposal-id: proposal-id } + (merge updated-proposal { status: STATUS_REJECTED })) + ) + true + ) + ) + (ok true) + ) + ) +) + ;; Fund a research proposal (define-public (fund-proposal (proposal-id uint) (amount uint)) (begin @@ -99,7 +168,7 @@ (proposal (unwrap-panic (map-get? proposals { proposal-id: proposal-id }))) (new-funding (+ (get current-funding proposal) amount)) ) - (asserts! (is-eq (get status proposal) STATUS_OPEN) ERR_ALREADY_FUNDED) + (asserts! (is-eq (get status proposal) STATUS_OPEN) ERR_INVALID_STATUS) (asserts! (<= new-funding (get funding-goal proposal)) ERR_INVALID_AMOUNT) (try! (stx-transfer? amount tx-sender (as-contract tx-sender))) (map-set proposals @@ -157,6 +226,34 @@ ) ) +;; Add a reviewer (only by CONTRACT_OWNER) +(define-public (add-reviewer (reviewer principal)) + (begin + (asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_NOT_AUTHORIZED) + (map-set reviewers { reviewer: reviewer } { is-active: true }) + (ok true) + ) +) + +;; Remove a reviewer (only by CONTRACT_OWNER) +(define-public (remove-reviewer (reviewer principal)) + (begin + (asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_NOT_AUTHORIZED) + (map-delete reviewers { reviewer: reviewer }) + (ok true) + ) +) + +;; Set required votes (only by CONTRACT_OWNER) +(define-public (set-required-votes (new-required-votes uint)) + (begin + (asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_NOT_AUTHORIZED) + (asserts! (> new-required-votes u0) ERR_INVALID_AMOUNT) + (var-set required-votes new-required-votes) + (ok true) + ) +) + ;; Read-only functions ;; Get proposal details @@ -177,4 +274,14 @@ ;; Get proposal status (define-read-only (get-proposal-status (proposal-id uint)) (get status (unwrap-panic (map-get? proposals { proposal-id: proposal-id }))) +) + +;; Check if an account is a reviewer +(define-read-only (is-active-reviewer (account principal)) + (is-reviewer account) +) + +;; Get required votes +(define-read-only (get-required-votes) + (var-get required-votes) ) \ No newline at end of file From 8f915888afb4472e96c3f2fbd7d3429cb21e427d Mon Sep 17 00:00:00 2001 From: blessychoco Date: Sat, 28 Sep 2024 09:30:02 +0100 Subject: [PATCH 5/7] Handled unchecked data warning and added README.md --- research-chain/README.md | 106 ++++++++++++++++++++++++ research-chain/contracts/sci-bloom.clar | 3 + 2 files changed, 109 insertions(+) create mode 100644 research-chain/README.md diff --git a/research-chain/README.md b/research-chain/README.md new file mode 100644 index 0000000..d4a3b58 --- /dev/null +++ b/research-chain/README.md @@ -0,0 +1,106 @@ +# ResearchChain Smart Contract + +## Overview + +ResearchChain is a decentralized scientific research funding platform implemented as a smart contract on the Stacks blockchain. It allows researchers to submit proposals, reviewers to evaluate them, and funders to support approved projects. + +## Features + +- Proposal submission by researchers +- Review and voting system for quality control +- Funding mechanism for approved proposals +- Withdrawal process for funded projects +- Role-based access control (researchers, reviewers, funders, contract owner) + +## Contract Structure + +The contract consists of several key components: + +1. **Proposals**: Researchers can submit proposals for funding. +2. **Review Process**: Authorized reviewers can vote on proposals. +3. **Funding**: Approved proposals can receive funding from supporters. +4. **Withdrawal**: Researchers can withdraw funds once their proposal is fully funded. + +## Proposal Lifecycle + +1. **Submitted**: A researcher submits a new proposal. +2. **Under Review**: The contract owner moves the proposal to the review phase. +3. **Approved/Rejected**: Based on reviewer votes, the proposal is either approved or rejected. +4. **Open for Funding**: Approved proposals can be opened for funding by the contract owner. +5. **Funded**: The proposal reaches its funding goal. +6. **Closed**: The researcher withdraws the funds, and the proposal is closed. + +## Key Functions + +### For Researchers + +- `submit-proposal`: Submit a new research proposal. +- `withdraw-funds`: Withdraw funds for a fully funded proposal. + +### For Reviewers + +- `vote-on-proposal`: Vote to approve or reject a proposal under review. + +### For Funders + +- `fund-proposal`: Contribute funds to an approved and open proposal. + +### For Contract Owner + +- `update-proposal-status`: Update the status of a proposal. +- `add-reviewer`: Add a new reviewer to the system. +- `remove-reviewer`: Remove a reviewer from the system. +- `set-required-votes`: Set the number of votes required for a proposal decision. + +## How to Use + +1. **Submitting a Proposal**: + Researchers call `submit-proposal` with their proposal details. + +2. **Reviewing a Proposal**: + - The contract owner updates the proposal status to "Under Review". + - Authorized reviewers call `vote-on-proposal` to cast their votes. + - Once the required number of votes is reached, the proposal is automatically approved or rejected. + +3. **Funding a Proposal**: + - The contract owner updates approved proposals to "Open" status. + - Funders can call `fund-proposal` to contribute to open proposals. + +4. **Withdrawing Funds**: + Once a proposal is fully funded, the researcher can call `withdraw-funds` to receive the funds. + +## Error Codes + +- `ERR_NOT_AUTHORIZED (u100)`: User not authorized for the action. +- `ERR_INVALID_AMOUNT (u101)`: Invalid amount specified. +- `ERR_PROPOSAL_NOT_FOUND (u102)`: Proposal ID not found. +- `ERR_ALREADY_FUNDED (u103)`: Proposal already fully funded. +- `ERR_INVALID_TITLE (u104)`: Invalid proposal title. +- `ERR_INVALID_DESCRIPTION (u105)`: Invalid proposal description. +- `ERR_INVALID_FUNDING_GOAL (u106)`: Invalid funding goal. +- `ERR_INVALID_PROPOSAL_ID (u107)`: Invalid proposal ID. +- `ERR_INVALID_STATUS (u108)`: Invalid proposal status. +- `ERR_ALREADY_VOTED (u109)`: Reviewer has already voted on the proposal. +- `ERR_NOT_REVIEWER (u110)`: User is not an authorized reviewer. + +## Security Considerations + +- Only the contract owner can add or remove reviewers and update proposal statuses. +- Reviewers can only vote once per proposal. +- Funds are locked in the contract until the proposal is fully funded and the researcher withdraws them. +- Input validation is performed on all public functions to ensure data integrity. + +## Future Improvements + +- Implement a mechanism for researchers to update their proposals based on feedback. +- Add time limits for the review process and funding period. +- Develop a reputation system for reviewers. +- Implement a refund mechanism for funders if a proposal is rejected or doesn't meet its funding goal. + +## Disclaimer + +This smart contract is provided as-is. Users should review and understand the code before interacting with it on the blockchain. Always test thoroughly on a testnet before deploying to mainnet. + +## Author + +Blessing Eze \ No newline at end of file diff --git a/research-chain/contracts/sci-bloom.clar b/research-chain/contracts/sci-bloom.clar index 60f5d11..ada271d 100644 --- a/research-chain/contracts/sci-bloom.clar +++ b/research-chain/contracts/sci-bloom.clar @@ -14,6 +14,7 @@ (define-constant ERR_INVALID_STATUS (err u108)) (define-constant ERR_ALREADY_VOTED (err u109)) (define-constant ERR_NOT_REVIEWER (err u110)) +(define-constant ERR_INVALID_REVIEWER (err u111)) ;; Define proposal statuses (define-constant STATUS_SUBMITTED u0) @@ -230,6 +231,7 @@ (define-public (add-reviewer (reviewer principal)) (begin (asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_NOT_AUTHORIZED) + (asserts! (not (is-eq reviewer CONTRACT_OWNER)) ERR_INVALID_REVIEWER) (map-set reviewers { reviewer: reviewer } { is-active: true }) (ok true) ) @@ -239,6 +241,7 @@ (define-public (remove-reviewer (reviewer principal)) (begin (asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_NOT_AUTHORIZED) + (asserts! (not (is-eq reviewer CONTRACT_OWNER)) ERR_INVALID_REVIEWER) (map-delete reviewers { reviewer: reviewer }) (ok true) ) From 16b851d6ca67d6f16b00521c46a8636cda0fc3d6 Mon Sep 17 00:00:00 2001 From: blessychoco Date: Sat, 28 Sep 2024 10:00:07 +0100 Subject: [PATCH 6/7] Enabled Refund Mechanism --- research-chain/contracts/sci-bloom.clar | 73 +++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 4 deletions(-) diff --git a/research-chain/contracts/sci-bloom.clar b/research-chain/contracts/sci-bloom.clar index ada271d..9fddc32 100644 --- a/research-chain/contracts/sci-bloom.clar +++ b/research-chain/contracts/sci-bloom.clar @@ -1,5 +1,5 @@ ;; ResearchChain: Decentralized Scientific Research Funding -;; This contract allows researchers to submit proposals, reviewers to vote, and funders to support approved projects +;; This contract allows researchers to submit proposals, reviewers to vote, funders to support approved projects, and implement a refund mechanism ;; Define constants (define-constant CONTRACT_OWNER tx-sender) @@ -15,6 +15,8 @@ (define-constant ERR_ALREADY_VOTED (err u109)) (define-constant ERR_NOT_REVIEWER (err u110)) (define-constant ERR_INVALID_REVIEWER (err u111)) +(define-constant ERR_NOT_FUNDER (err u112)) +(define-constant ERR_REFUND_NOT_AVAILABLE (err u113)) ;; Define proposal statuses (define-constant STATUS_SUBMITTED u0) @@ -24,6 +26,7 @@ (define-constant STATUS_OPEN u4) (define-constant STATUS_FUNDED u5) (define-constant STATUS_CLOSED u6) +(define-constant STATUS_REFUNDABLE u7) ;; Define vote options (define-constant VOTE_APPROVE u1) @@ -40,7 +43,8 @@ current-funding: uint, status: uint, approve-votes: uint, - reject-votes: uint + reject-votes: uint, + deadline: uint } ) @@ -62,6 +66,7 @@ ;; Define variables (define-data-var proposal-counter uint u0) (define-data-var required-votes uint u3) +(define-data-var funding-period uint u43200) ;; Default to 30 days (in blocks, assuming 1 block every 60 seconds) ;; Helper functions for input validation (define-private (is-valid-title (title (string-ascii 100))) @@ -81,7 +86,7 @@ ) (define-private (is-valid-status (status uint)) - (and (>= status STATUS_SUBMITTED) (<= status STATUS_CLOSED)) + (and (>= status STATUS_SUBMITTED) (<= status STATUS_REFUNDABLE)) ) (define-private (is-reviewer (account principal)) @@ -99,6 +104,7 @@ (let ( (proposal-id (+ (var-get proposal-counter) u1)) + (deadline (+ block-height (var-get funding-period))) ) (map-set proposals { proposal-id: proposal-id } @@ -110,7 +116,8 @@ current-funding: u0, status: STATUS_SUBMITTED, approve-votes: u0, - reject-votes: u0 + reject-votes: u0, + deadline: deadline } ) (var-set proposal-counter proposal-id) @@ -257,6 +264,54 @@ ) ) +;; Set funding period (only by CONTRACT_OWNER) +(define-public (set-funding-period (new-funding-period uint)) + (begin + (asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_NOT_AUTHORIZED) + (asserts! (> new-funding-period u0) ERR_INVALID_AMOUNT) + (var-set funding-period new-funding-period) + (ok true) + ) +) + +;; Check if a proposal is eligible for refund +(define-private (is-refund-eligible (proposal { proposal-id: uint })) + (let + ( + (proposal-data (unwrap-panic (map-get? proposals proposal))) + ) + (or + (and + (< (get current-funding proposal-data) (get funding-goal proposal-data)) + (> block-height (get deadline proposal-data)) + ) + (is-eq (get status proposal-data) STATUS_CLOSED) + ) + ) +) + +;; Request a refund for a proposal +(define-public (request-refund (proposal-id uint)) + (let + ( + (proposal (unwrap-panic (map-get? proposals { proposal-id: proposal-id }))) + (funding (unwrap-panic (map-get? fundings { proposal-id: proposal-id, funder: tx-sender }))) + ) + (asserts! (is-refund-eligible { proposal-id: proposal-id }) ERR_REFUND_NOT_AVAILABLE) + (asserts! (> (get amount funding) u0) ERR_NOT_FUNDER) + (try! (as-contract (stx-transfer? (get amount funding) tx-sender tx-sender))) + (map-delete fundings { proposal-id: proposal-id, funder: tx-sender }) + (map-set proposals + { proposal-id: proposal-id } + (merge proposal { + current-funding: (- (get current-funding proposal) (get amount funding)), + status: STATUS_REFUNDABLE + }) + ) + (ok true) + ) +) + ;; Read-only functions ;; Get proposal details @@ -287,4 +342,14 @@ ;; Get required votes (define-read-only (get-required-votes) (var-get required-votes) +) + +;; Get funding period +(define-read-only (get-funding-period) + (var-get funding-period) +) + +;; Check if a proposal is eligible for refund +(define-read-only (check-refund-eligibility (proposal-id uint)) + (is-refund-eligible { proposal-id: proposal-id }) ) \ No newline at end of file From e06b4fd083b77bd33343003739fe6c17c493d82d Mon Sep 17 00:00:00 2001 From: blessychoco Date: Sat, 28 Sep 2024 10:06:27 +0100 Subject: [PATCH 7/7] error handling & updated README file --- research-chain/README.md | 35 ++++++++++++++++++------- research-chain/contracts/sci-bloom.clar | 35 ++++++++++++++----------- 2 files changed, 45 insertions(+), 25 deletions(-) diff --git a/research-chain/README.md b/research-chain/README.md index d4a3b58..dc773cf 100644 --- a/research-chain/README.md +++ b/research-chain/README.md @@ -2,7 +2,7 @@ ## Overview -ResearchChain is a decentralized scientific research funding platform implemented as a smart contract on the Stacks blockchain. It allows researchers to submit proposals, reviewers to evaluate them, and funders to support approved projects. +ResearchChain is a decentralized scientific research funding platform implemented as a smart contract on the Stacks blockchain. It allows researchers to submit proposals, reviewers to evaluate them, and funders to support approved projects. The contract also includes a refund mechanism for certain scenarios. ## Features @@ -10,7 +10,9 @@ ResearchChain is a decentralized scientific research funding platform implemente - Review and voting system for quality control - Funding mechanism for approved proposals - Withdrawal process for funded projects +- Refund mechanism for eligible proposals - Role-based access control (researchers, reviewers, funders, contract owner) +- Configurable voting and funding parameters ## Contract Structure @@ -20,6 +22,7 @@ The contract consists of several key components: 2. **Review Process**: Authorized reviewers can vote on proposals. 3. **Funding**: Approved proposals can receive funding from supporters. 4. **Withdrawal**: Researchers can withdraw funds once their proposal is fully funded. +5. **Refunds**: Funders can request refunds under certain conditions. ## Proposal Lifecycle @@ -29,6 +32,7 @@ The contract consists of several key components: 4. **Open for Funding**: Approved proposals can be opened for funding by the contract owner. 5. **Funded**: The proposal reaches its funding goal. 6. **Closed**: The researcher withdraws the funds, and the proposal is closed. +7. **Refundable**: If a proposal doesn't meet its funding goal within the deadline or is closed, it becomes eligible for refunds. ## Key Functions @@ -44,6 +48,7 @@ The contract consists of several key components: ### For Funders - `fund-proposal`: Contribute funds to an approved and open proposal. +- `request-refund`: Request a refund for an eligible proposal. ### For Contract Owner @@ -51,6 +56,7 @@ The contract consists of several key components: - `add-reviewer`: Add a new reviewer to the system. - `remove-reviewer`: Remove a reviewer from the system. - `set-required-votes`: Set the number of votes required for a proposal decision. +- `set-funding-period`: Set the funding period for proposals. ## How to Use @@ -69,6 +75,9 @@ The contract consists of several key components: 4. **Withdrawing Funds**: Once a proposal is fully funded, the researcher can call `withdraw-funds` to receive the funds. +5. **Requesting Refunds**: + If a proposal doesn't meet its funding goal within the deadline or is closed, funders can call `request-refund` to get their contributions back. + ## Error Codes - `ERR_NOT_AUTHORIZED (u100)`: User not authorized for the action. @@ -82,20 +91,28 @@ The contract consists of several key components: - `ERR_INVALID_STATUS (u108)`: Invalid proposal status. - `ERR_ALREADY_VOTED (u109)`: Reviewer has already voted on the proposal. - `ERR_NOT_REVIEWER (u110)`: User is not an authorized reviewer. +- `ERR_INVALID_REVIEWER (u111)`: Invalid reviewer specified. +- `ERR_NOT_FUNDER (u112)`: User is not a funder of the proposal. +- `ERR_REFUND_NOT_AVAILABLE (u113)`: Refund is not available for the proposal. ## Security Considerations - Only the contract owner can add or remove reviewers and update proposal statuses. - Reviewers can only vote once per proposal. -- Funds are locked in the contract until the proposal is fully funded and the researcher withdraws them. +- Funds are locked in the contract until the proposal is fully funded and the researcher withdraws them, or until a refund is requested. - Input validation is performed on all public functions to ensure data integrity. - -## Future Improvements - -- Implement a mechanism for researchers to update their proposals based on feedback. -- Add time limits for the review process and funding period. -- Develop a reputation system for reviewers. -- Implement a refund mechanism for funders if a proposal is rejected or doesn't meet its funding goal. +- The contract uses a deadline mechanism to determine when proposals become eligible for refunds. + +## Read-Only Functions + +- `get-proposal`: Retrieve details of a specific proposal. +- `get-proposal-count`: Get the total number of proposals. +- `get-funding`: Get the funding amount for a specific proposal and funder. +- `get-proposal-status`: Retrieve the status of a specific proposal. +- `is-active-reviewer`: Check if an account is an active reviewer. +- `get-required-votes`: Get the current number of required votes for proposal decisions. +- `get-funding-period`: Get the current funding period for proposals. +- `check-refund-eligibility`: Check if a proposal is eligible for refunds. ## Disclaimer diff --git a/research-chain/contracts/sci-bloom.clar b/research-chain/contracts/sci-bloom.clar index 9fddc32..59cd42d 100644 --- a/research-chain/contracts/sci-bloom.clar +++ b/research-chain/contracts/sci-bloom.clar @@ -292,23 +292,26 @@ ;; Request a refund for a proposal (define-public (request-refund (proposal-id uint)) - (let - ( - (proposal (unwrap-panic (map-get? proposals { proposal-id: proposal-id }))) - (funding (unwrap-panic (map-get? fundings { proposal-id: proposal-id, funder: tx-sender }))) - ) - (asserts! (is-refund-eligible { proposal-id: proposal-id }) ERR_REFUND_NOT_AVAILABLE) - (asserts! (> (get amount funding) u0) ERR_NOT_FUNDER) - (try! (as-contract (stx-transfer? (get amount funding) tx-sender tx-sender))) - (map-delete fundings { proposal-id: proposal-id, funder: tx-sender }) - (map-set proposals - { proposal-id: proposal-id } - (merge proposal { - current-funding: (- (get current-funding proposal) (get amount funding)), - status: STATUS_REFUNDABLE - }) + (begin + (asserts! (is-valid-proposal-id proposal-id) ERR_INVALID_PROPOSAL_ID) + (let + ( + (proposal (unwrap-panic (map-get? proposals { proposal-id: proposal-id }))) + (funding (unwrap-panic (map-get? fundings { proposal-id: proposal-id, funder: tx-sender }))) + ) + (asserts! (is-refund-eligible { proposal-id: proposal-id }) ERR_REFUND_NOT_AVAILABLE) + (asserts! (> (get amount funding) u0) ERR_NOT_FUNDER) + (try! (as-contract (stx-transfer? (get amount funding) tx-sender tx-sender))) + (map-delete fundings { proposal-id: proposal-id, funder: tx-sender }) + (map-set proposals + { proposal-id: proposal-id } + (merge proposal { + current-funding: (- (get current-funding proposal) (get amount funding)), + status: STATUS_REFUNDABLE + }) + ) + (ok true) ) - (ok true) ) )