-
Notifications
You must be signed in to change notification settings - Fork 68
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
Changes from all commits
63d8afe
2e74694
f8018c8
8451116
f72d0bb
67df9d1
78929b7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
# 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. | ||
|
||
- Setup the file by adding this header | ||
```solidity | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.8.26; | ||
``` | ||
|
||
- Create a contract named `VotingSystem` | ||
- Declare a structure named `Proposal` | ||
>💡 The structure should have 2 variables, `name` and `voSteCount`. 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`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. explique que c'est un constructeur ce qu'il doivent faire et qu'il prend en param un array de string (c'est moins ambigu) |
||
>💡 A constructor is an optional function that is executed upon contract deployment, you must create one to complete the previous task. | ||
|
||
### ✔️ **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**: | ||
|
||
- [Header](https://docs.soliditylang.org/en/latest/layout-of-source-files.html) | ||
- [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. | ||
>💡 On a successful deployment, contract address appear next to the `deployed to` field. | ||
|
||
- 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/) | ||
- [Deploy a Smart Contract local on Anvil with Foundry in 2 min](https://youtu.be/e5QmJaamdPE) | ||
|
||
## 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` containing a boolean named `voted` and an uint `id`. | ||
- Code the `vote` function. It should take the id of the proposal 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. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
VITE_ADDRESS="" | ||
VITE_PRIVATE_KEY_ACCOUNT="" |
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' | ||
} | ||
} |
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 |
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" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/// <reference types="vite/client" /> |
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> |
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" | ||
} | ||
} |
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>No 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; | ||
} | ||
Comment on lines
+86
to
+92
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fait gaffe a la couleur du text. pour moi la couleur des nom des proposals sont noir sur un fond gris donc on voit rien |
||
#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> |
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"}] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
explique plus. Dit qu'il faut créer un contrat nommer
VotingSystem
et qu'il faut mettre le header du fichier