Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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 .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
PORT=3000
NETWORK=testnet
CONTRACT_ADDRESS=your_contract_address
CONTRACT_NAME=echovote
PRIVATE_KEY=your_private_key
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ coverage
*.info
costs-reports.json
node_modules
.vscode
.vscode
.env
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ We welcome contributions of all kinds to EchoVote! Whether it's improving docume

### 1. Fork the repository

- Navigate to the [EchoVote repository](https://github.com/nicholas-source/echovote).
- Navigate to the [EchoVote repository](https://github.com/nicholas-source/echo_vote).
- Click the "Fork" button in the top-right corner to create your copy of the repository.

### 2. Clone your fork

- Clone your forked repository to your local machine:
```bash
git clone https://github.com/nicholas-source/echovote.git
git clone https://github.com/nicholas-source/echo_vote.git
cd echovote
```

Expand Down
30 changes: 14 additions & 16 deletions Clarinet.toml
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
[project]
name = "echo_vote"
description = ""
name = 'echo_vote'
description = ''
authors = []
telemetry = true
cache_dir = "./.cache"

# [contracts.counter]
# path = "contracts/counter.clar"

cache_dir = './.cache'
requirements = []
[contracts.echovote]
path = 'contracts/echovote.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
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

### `LICENSE`

```md
Expand All @@ -23,3 +22,4 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
179 changes: 179 additions & 0 deletions contracts/echovote.clar
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
;; EchoVote: A decentralized voting system
;; This contract allows for creating, voting on, and managing proposals

;; Constants
(define-constant contract-owner tx-sender)
(define-constant err-unauthorized (err u100))
(define-constant err-already-voted (err u101))
(define-constant err-proposal-not-active (err u102))
(define-constant err-invalid-vote (err u103))
(define-constant err-invalid-title (err u104))
(define-constant err-invalid-description (err u105))
(define-constant err-invalid-start-block (err u106))
(define-constant err-invalid-end-block (err u107))
(define-constant err-proposal-ended (err u108))
(define-constant err-proposal-not-found (err u404))

;; Data variables
(define-data-var proposal-count uint u0)

;; Data maps
(define-map proposals
uint
{
title: (string-utf8 256),
description: (string-utf8 1024),
creator: principal,
start-block: uint,
end-block: uint,
is-active: bool,
total-votes: uint
}
)

(define-map votes
{ proposal-id: uint, voter: principal }
uint
)

(define-map vote-counts
{ proposal-id: uint, option: uint }
uint
)

;; Private functions

;; Validate text length
(define-private (validate-text (text (string-utf8 256)))
(let ((length (len text)))
(and (> length u0) (<= length u256))
)
)

;; Validate long text length
(define-private (validate-long-text (text (string-utf8 1024)))
(let ((length (len text)))
(and (> length u0) (<= length u1024))
)
)

;; Validate block range
(define-private (validate-blocks (start-block uint) (end-block uint))
(let ((current-block block-height))
(and
(> start-block current-block)
(> end-block start-block)
)
)
)

;; Increment vote count for a specific option
(define-private (increment-vote-count (proposal-id uint) (vote-option uint))
(let
(
(current-count (default-to u0 (map-get? vote-counts { proposal-id: proposal-id, option: vote-option })))
(new-count (+ current-count u1))
)
(map-set vote-counts { proposal-id: proposal-id, option: vote-option } new-count)
(increment-total-votes proposal-id)
)
)

;; Increment total votes for a proposal
(define-private (increment-total-votes (proposal-id uint))
(match (map-get? proposals proposal-id)
proposal (begin
(map-set proposals proposal-id
(merge proposal { total-votes: (+ (get total-votes proposal) u1) })
)
true
)
false
)
)

;; Public functions

;; Create a new proposal
(define-public (create-proposal (title (string-utf8 256)) (description (string-utf8 1024)) (start-block uint) (end-block uint))
(let
(
(new-proposal-id (+ (var-get proposal-count) u1))
)
(asserts! (is-eq tx-sender contract-owner) err-unauthorized)
(asserts! (validate-text title) err-invalid-title)
(asserts! (validate-long-text description) err-invalid-description)
(asserts! (validate-blocks start-block end-block) err-invalid-start-block)
(map-set proposals new-proposal-id
{
title: title,
description: description,
creator: tx-sender,
start-block: start-block,
end-block: end-block,
is-active: true,
total-votes: u0
}
)
(var-set proposal-count new-proposal-id)
(ok new-proposal-id)
)
)

;; Cast a vote on a proposal
(define-public (vote (proposal-id uint) (vote-option uint))
(let
(
(proposal (unwrap! (map-get? proposals proposal-id) err-proposal-not-active))
(current-block block-height)
)
(asserts! (>= current-block (get start-block proposal)) err-proposal-not-active)
(asserts! (<= current-block (get end-block proposal)) err-proposal-ended)
(asserts! (get is-active proposal) err-proposal-not-active)
(asserts! (is-none (map-get? votes { proposal-id: proposal-id, voter: tx-sender })) err-already-voted)
(asserts! (and (>= vote-option u1) (<= vote-option u5)) err-invalid-vote)

(map-set votes { proposal-id: proposal-id, voter: tx-sender } vote-option)
(increment-vote-count proposal-id vote-option)
(ok true)
)
)

;; End an active proposal
(define-public (end-proposal (proposal-id uint))
(let
(
(proposal (unwrap! (map-get? proposals proposal-id) err-proposal-not-active))
(current-block block-height)
)
(asserts! (is-eq tx-sender contract-owner) err-unauthorized)
(asserts! (>= current-block (get end-block proposal)) err-proposal-not-active)
(map-set proposals proposal-id (merge proposal { is-active: false }))
(ok true)
)
)

;; Read-only functions

;; Get proposal details
(define-read-only (get-proposal (proposal-id uint))
(map-get? proposals proposal-id)
)

;; Get a user's vote for a proposal
(define-read-only (get-vote (proposal-id uint) (voter principal))
(map-get? votes { proposal-id: proposal-id, voter: voter })
)

;; Get vote count for a specific option
(define-read-only (get-vote-count (proposal-id uint) (vote-option uint))
(default-to u0 (map-get? vote-counts { proposal-id: proposal-id, option: vote-option }))
)

;; Get total votes for a proposal
(define-read-only (get-total-votes (proposal-id uint))
(match (map-get? proposals proposal-id)
proposal (ok (get total-votes proposal))
(err err-proposal-not-found)
)
)
Loading