Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create Voting system workshop #102

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
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
131 changes: 131 additions & 0 deletions p2p/7.Voting-system/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Workshop 6 - Voting System

✔️ Write a decentralized voting system

✔️ Deploy your smart contract on a local testnet using [Anvil](https://book.getfoundry.sh/anvil/) shipped with [Foundry](https://book.getfoundry.sh/)

✔️ Display your smart contract on a dApp

## Introduction

A smart contract is a self-executing program that runs on a blockchain, automatically enforcing the terms of an agreement when predefined conditions are met. With smart contracts, it's possible to build decentralized systems, such as a transparent and secure voting system, where the rules are coded into the contract, and the results are immutable and verifiable by everyone.

In this workshop we'll learn the basics of solidity with a classic use case: a voting system. At once, we'll use Foundry coupled with Anvil to deploy our contract on a local testnet and gradually integrate it on a front-end template.

## Step 0: Initialization

All the required information to install the workshop's dependencies are given in the [setup.md](./setup.md). To launch the dApp :

- Clone the "dApp" folder, afterward:

```bash
cd dApp
npm install
npm run dev
```

If everything went well, you should read "No proposals yet.", now let's code !

## Step 1 : Create the contract

### 📑 **Description**:

First, we need to create the core smart contract that will power our decentralized voting system. For now, you're just going to focus on the base of the smart contract: the constructor.

### 📌 **Tasks**:

Remove the files from `script/`, `src/` and `test` directories. Create `VotingSystem.sol` in the `src/` folder.

- Create a structure named `Proposal`
>💡 The structure should have 2 variables, `name` and `voteCount`. I let you guess their types.

- On deployment, the contract should take an array of strings as parameters and store them in a `public` array of `Proposals`.

### ✔️ **Validation**:

After implementing this correctly, paste the `VotingSystem.t.sol` file from the `util/` folder from the repository and write:

```bash
forge test
```

if the two tests are good, you can move on the second step !

### 📚 **Documentation**:

- [Constructors](https://docs.soliditylang.org/en/v0.8.27/contracts.html#constructors)

## Step 2: Deploy and integrate it

### 📑 **Description**:

In this step you'll focus on the deployment and the integration part of your smart contract. For quick and easy deployment, you'll use [Anvil](https://book.getfoundry.sh/anvil/) to create a local testnet.

### 📌 **Tasks**:

- Using Anvil and forge, deploy your smart contract on a local testnet.
- Paste the address of your deployed contract into the environment file at the root of the dApp folder named `VITE_ADDRESS`.

### ✔️ **Validation**:

Once you have done this, you should now be able to see the parameters you entered when deploying the contract in the dApp.

### 📚 **Documentation**:

- [Anvil](https://book.getfoundry.sh/anvil/)

## Step 4: Code the contract

### 📑 **Description**:

Crucial step! I'll leave you to code the rest of the contract yourself, so you can see the results bit by bit. Feel free to redeploy your contract whenever you like with forge!

### 📌 **Tasks**:

- Create a structure `Voter`

- Code the `vote` function. It should take an id in parameter.
- Check if the voter had already voted and if the id is correct.
- Add a `voteCount` for the proposal.

> 💡 These functions require a wallet address. If you've did the previous step correctly, you have account available to use. Put one of the private keys in the `.env` file.

- Code the `winningProposal` function, returning the proposal with the most `voteCount`.

### ✔️ **Validation**:

If you can see all the proposals, vote for one and the winner is highlighted in green, congratulations, you have just made your first smart contract !

## To go further

You've just created a simple voting system smart contract ! If you want to go further you can add some feature to your contract and styling the dApp !

## Authors

| [<img src="https://github.com/Sacharbon.png?size=85" width=85><br><sub>Sacha Dujardin</sub>](https://github.com/Sacharbon) |
| :------------------------------------------------------------------------------------------------------------------------: |
<h2 align=center>
Organization
</h2>

<p align='center'>
<a href="https://www.linkedin.com/company/pocinnovation/mycompany/">
<img src="https://img.shields.io/badge/LinkedIn-0077B5?style=for-the-badge&logo=linkedin&logoColor=white">
</a>
<a href="https://www.instagram.com/pocinnovation/">
<img src="https://img.shields.io/badge/Instagram-E4405F?style=for-the-badge&logo=instagram&logoColor=white">
</a>
<a href="https://twitter.com/PoCInnovation">
<img src="https://img.shields.io/badge/Twitter-1DA1F2?style=for-the-badge&logo=twitter&logoColor=white">
</a>
<a href="https://discord.com/invite/Yqq2ADGDS7">
<img src="https://img.shields.io/badge/Discord-7289DA?style=for-the-badge&logo=discord&logoColor=white">
</a>
</p>
<p align=center>
<a href="https://www.poc-innovation.fr/">
<img src="https://img.shields.io/badge/WebSite-1a2b6d?style=for-the-badge&logo=GitHub Sponsors&logoColor=white">
</a>
</p>

> 🚀 Don't hesitate to follow us on our different networks, and put a star 🌟 on `PoC's` repositories.
2 changes: 2 additions & 0 deletions p2p/7.Voting-system/dApp/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
VITE_ADDRESS=""
VITE_PRIVATE_KEY_ACCOUNT=""
15 changes: 15 additions & 0 deletions p2p/7.Voting-system/dApp/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')

module.exports = {
root: true,
'extends': [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-typescript',
'@vue/eslint-config-prettier/skip-formatting'
],
parserOptions: {
ecmaVersion: 'latest'
}
}
30 changes: 30 additions & 0 deletions p2p/7.Voting-system/dApp/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
.DS_Store
dist
dist-ssr
coverage
*.local

/cypress/videos/
/cypress/screenshots/

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

*.tsbuildinfo
8 changes: 8 additions & 0 deletions p2p/7.Voting-system/dApp/.prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": false,
"tabWidth": 2,
"singleQuote": true,
"printWidth": 100,
"trailingComma": "none"
}
1 change: 1 addition & 0 deletions p2p/7.Voting-system/dApp/env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="vite/client" />
13 changes: 13 additions & 0 deletions p2p/7.Voting-system/dApp/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
35 changes: 35 additions & 0 deletions p2p/7.Voting-system/dApp/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "front",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "run-p type-check \"build-only {@}\" --",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --build --force",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"format": "prettier --write src/"
},
"dependencies": {
"viem": "^2.20.0",
"vue": "^3.4.29"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.8.0",
"@tsconfig/node20": "^20.1.4",
"@types/node": "^20.14.5",
"@vitejs/plugin-vue": "^5.0.5",
"@vue/eslint-config-prettier": "^9.0.0",
"@vue/eslint-config-typescript": "^13.0.0",
"@vue/tsconfig": "^0.5.1",
"eslint": "^8.57.0",
"eslint-plugin-vue": "^9.23.0",
"npm-run-all2": "^6.2.0",
"prettier": "^3.2.5",
"typescript": "~5.4.0",
"vite": "^5.3.1",
"vue-tsc": "^2.0.21"
}
}
Binary file added p2p/7.Voting-system/dApp/public/favicon.ico
Binary file not shown.
114 changes: 114 additions & 0 deletions p2p/7.Voting-system/dApp/src/App.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<script setup lang="ts">
import { win, getProposals, vote } from "./interact"
import { ref, onMounted } from 'vue'
import Card from "@/components/Card.vue";

const winner = ref<number>(-1 )
const proposals = ref()
const loading = ref(true)
const isVoting = ref(false)
const selectedIndex = ref<number>(-1)
const voteStatus = ref<string>('')

function delay(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}

const refreshData = async () => {
proposals.value = await getProposals()
winner.value = await win()
loading.value = false
console.log(proposals.value)
}

onMounted(async () => {
if (import.meta.env.VITE_ADDRESS) {
await refreshData()
}
})

const toVote = (index: number) => {
isVoting.value = true;
selectedIndex.value = index;
}

const voting = async () => {
try {
await vote(selectedIndex.value)
voteStatus.value = "You have successfully voted !"
await delay(2000)
voteStatus.value = ""
isVoting.value = false
loading.value = true
await refreshData()
} catch (error) {
if (error.message.includes('Already voted.')) {
voteStatus.value = "You have already voted."
} else {
voteStatus.value = "Internal network error."
}
}
}
</script>

<template>
<header>
<h1>Voting system</h1>
</header>
<div id="container">
<h2 v-if="isVoting">Do you want to vote ?</h2>
<div id="cards" v-if="!loading" v-for="(proposal, index) in proposals">
<Card
v-if="!isVoting || (isVoting && selectedIndex === index)"
:name="proposal[0]"
:voteCount="proposal[1]"
:index="<number>index"
:toVote="toVote"
:winnerIndex="winner"
/>
</div>
<p v-else>Not proposals yet.</p>
<div id="choice" v-if="isVoting">
<button @click="voting">yes</button>
<button @click="isVoting = false">no</button>
<p>{{ voteStatus }}</p>
</div>
</div>
</template>

<style scoped>
header {
display: flex;
justify-content: center;
align-items: center;
height: 10vh;
}
h1 {
font-size: 3rem;
font-weight: bold;
}
h2 {
font-size: 1.8rem;
}
#container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 90vh;
}
#cards {
display: flex;
width: 100vw;
justify-content: center;
}
button {
padding: 10px;
padding-inline: 20px;
margin: 10px;
font-size: 1rem;
}
#choice {
text-align: center;
}
</style>
1 change: 1 addition & 0 deletions p2p/7.Voting-system/dApp/src/abi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const abi = [{"inputs":[{"internalType":"string[]","name":"proposalName","type":"string[]"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"chairperson","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"proposals","outputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"voteCount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"proposalId","type":"uint256"}],"name":"vote","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"voters","outputs":[{"internalType":"bool","name":"hasVoted","type":"bool"},{"internalType":"uint256","name":"votedProposalId","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"winningProposal","outputs":[{"internalType":"uint256","name":"winningProposalId","type":"uint256"}],"stateMutability":"view","type":"function"}]
Loading
Loading