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

Creation of contract #9

Open
wants to merge 4 commits into
base: main
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
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "foundry_contracts/lib/forge-std"]
path = foundry_contracts/lib/forge-std
url = https://github.com/foundry-rs/forge-std
45 changes: 45 additions & 0 deletions foundry_contracts/.github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: CI

on:
push:
pull_request:
workflow_dispatch:

env:
FOUNDRY_PROFILE: ci

jobs:
check:
strategy:
fail-fast: true

name: Foundry project
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly

- name: Show Forge version
run: |
forge --version

- name: Run Forge fmt
run: |
forge fmt --check
id: fmt

- name: Run Forge build
run: |
forge build --sizes
id: build

- name: Run Forge tests
run: |
forge test -vvv
id: test
17 changes: 17 additions & 0 deletions foundry_contracts/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Compiler files
cache/
out/

#lib
lib/

# Ignores development broadcast logs
!/broadcast
/broadcast/*/31337/
/broadcast/**/dry-run/

# Docs
docs/

# Dotenv file
.env
56 changes: 56 additions & 0 deletions foundry_contracts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
## QuizApp Contract

The `QuizApp` contract allows users to create quizzes, participate in them, and claim rewards based on their answers.

### Key Features

- **Create Quizzes**: Creators can create quizzes by providing hash id of quiz questions and options store on ipfs/arweave through frontend interaction.
- **Participate in Quizzes**: Users can participate in quizzes and submit their answers in form of an array.
- **Manage Quizzes**: Quiz creators can end quizzes and set correct answers.
- **Claim Rewards**: Users can claim rewards based on the correctness of their answers.


### Usage Example

1. Deploy the `QuizApp` contract.
2. Create a quiz using `createQuiz`.
3. Users can participate using `participateInQuiz`.
4. Quiz owners can end the quiz using `endQuiz` and set correct answers with `tellCorrectAnswers`.
5. Users can check their scores and claim rewards using `checkAndClaim`.


## Documentation of Foundry

https://book.getfoundry.sh/

## Usage

## Install Openzeppelin Contracts
```shell
$ forge install OpenZeppelin/openzeppelin-contracts --no-commit
```


### Build

```shell
$ forge build
```

### Compile

```shell
$ forge compile
```

### Deploy

```shell
$ forge script script/Deploy.s.sol
```

### Test

```shell
$ forge test
```
7 changes: 7 additions & 0 deletions foundry_contracts/foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
remappings=["@openzeppelin/contracts=lib/openzeppelin-contracts/contracts"]

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
1 change: 1 addition & 0 deletions foundry_contracts/lib/forge-std
Submodule forge-std added at 8f24d6
17 changes: 17 additions & 0 deletions foundry_contracts/script/Deploy.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.27;

import {Script} from "forge-std/Script.sol";
import {QuizApp} from "../src/QuizApp.sol";

contract Deploy is Script {
QuizApp quizapp;

function run() public returns(QuizApp) {
vm.startBroadcast();
quizapp = new QuizApp();
vm.stopBroadcast();
return quizapp;

}
}
147 changes: 147 additions & 0 deletions foundry_contracts/src/QuizApp.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;

import {ScoreToken} from "./ScoreToken.sol";

contract QuizApp {

ScoreToken scoreToken;
uint256 public quizCount;

struct Quiz {
uint256 id; // id of quiz
address by; // address of user who started quiz
string storageId; // storage id of Quiz on ipfs/arweave
uint256 totalQues; // total ques in a quiz
bool isActive; // whether quiz is active or not
uint256[] correctOptions;
}

// id to Quiz
mapping(uint256 => Quiz) public quizzes;
// address of user to quiz participation
mapping(address => mapping(uint256 => bool)) public participation;
// option chosen by a user in a quiz
mapping(address => mapping(uint256 => uint256[])) public userChosenOptions;
// address of user to quiz rewards claimed
mapping(address => mapping(uint256 => bool)) public claimed;

// Events emitted
event QuizCreated(uint quizId, string storageId, uint256 totalQues);
event QuizParticipated(address participant, uint256 quizId, uint256[] selectedOptions);
event QuizHasEnded(uint quizId);

// Custom errors
error QuizNotActive();
error QuizActive();
error AlreadyParticipated();
error CorrectOptionsNotSet();
error NotParticipated();
error AlreadyClaimed();
error AlreadySetCorrectOptions();
error QuizEnded();
error QuizNotEnded();
error NotQuizOwner();
error NotAllAnswersProvided();

// Modifiers for checking quiz participation and status
modifier quizActive(Quiz memory quiz) {
require(quiz.isActive, QuizNotActive());
_;
}

modifier quizNotActive(Quiz memory quiz) {
require(!quiz.isActive, QuizActive());
_;
}

modifier hasNotParticipated(uint256 quizId) {
require(!participation[msg.sender][quizId], AlreadyParticipated());
_;
}

modifier correctOptionsSet(Quiz memory quiz) {
require(quiz.correctOptions.length != 0, CorrectOptionsNotSet());
_;
}

modifier hasParticipated(uint256 quizId) {
require(participation[msg.sender][quizId], NotParticipated());
_;
}

modifier hasNotClaimed(uint256 quizId) {
require(!claimed[msg.sender][quizId], AlreadyClaimed());
_;
}

modifier quizEnded(uint quizId) {
require(!quizzes[quizId].isActive, QuizNotEnded());
_;
}
modifier quizNotEnded(uint quizId) {
require(quizzes[quizId].isActive, QuizEnded());
_;
}

modifier onlyQuizOwner(uint quizId) {
require(quizzes[quizId].by == msg.sender, NotQuizOwner());
_;
}

constructor(){
scoreToken = new ScoreToken();
}

// create a quiz using hash id as the storage id provided by ipfs/arweave and total ques
function createQuiz(
string memory storageId,
uint256 totalQues
) external {
quizzes[quizCount] = Quiz(quizCount, msg.sender, storageId, totalQues, true, new uint256[](0));
quizCount++;
emit QuizCreated(quizCount, storageId, totalQues);
}

// users can participate and answer quiz ques
function participateInQuiz(uint quizId, uint256[] memory selectedOptions) external quizActive(quizzes[quizId]) hasNotParticipated(quizId) {
require(selectedOptions.length == quizzes[quizId].totalQues, NotAllAnswersProvided());
participation[msg.sender][quizId] = true;
userChosenOptions[msg.sender][quizId] = selectedOptions;
emit QuizParticipated(msg.sender, quizId, selectedOptions);
}

// quiz creator can end participation in the quiz
function endQuiz(uint quizId) external quizNotEnded(quizId) onlyQuizOwner(quizId) {
quizzes[quizId].isActive = false;
emit QuizHasEnded(quizId);
}

// quiz creator enters the correct answer sequence
function tellCorrectAnswers(uint256 quizId, uint256[] memory correctOptions) public quizEnded(quizId) onlyQuizOwner(quizId) {
Quiz memory quiz = quizzes[quizId];
require(quiz.correctOptions.length == 0, AlreadySetCorrectOptions());
quizzes[quizId].correctOptions = correctOptions;
}

// users can match their answers and claim their reward based on correct answers
function checkAndClaim(uint256 quizId) external quizNotActive(quizzes[quizId])correctOptionsSet(quizzes[quizId]) hasParticipated(quizId) hasNotClaimed(quizId) {
Quiz memory quiz = quizzes[quizId];
uint256[] memory chosenOptions = userChosenOptions[msg.sender][quizId];
uint256[] memory correctOptions = quiz.correctOptions;
uint256 score = 0;
for (uint256 i = 0; i < quiz.totalQues; i++) {
if (chosenOptions[i] == correctOptions[i]) {
score++;
}
}
scoreToken.mint(msg.sender, score);
claimed[msg.sender][quizId] = true;
}

// users can viwe the score tokens they have till now
function viewTokens() public view returns (uint256){
return scoreToken.bal(msg.sender);
}

}
19 changes: 19 additions & 0 deletions foundry_contracts/src/ScoreToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract ScoreToken is ERC20, Ownable {
constructor()
ERC20("ScoreToken", "ST")
Ownable(msg.sender)
{}
function mint(address to, uint256 amount) onlyOwner public {
_mint(to, amount);
}
function bal(address to) onlyOwner public view returns (uint256) {
return balanceOf(to);
}
}
58 changes: 58 additions & 0 deletions foundry_contracts/test/Unit.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.27;

import {Test} from "forge-std/Test.sol";
import {QuizApp} from "../src/QuizApp.sol";
import {Deploy} from "../script/Deploy.s.sol";

contract Unit is Test {
QuizApp quizApp;
Deploy deploy;

function setUp() public {
deploy = new Deploy();
quizApp = deploy.run();
}

function testCreateQuiz() public {
quizApp.createQuiz("quiz1", 5);
(uint256 id, address by, string memory storageId, uint256 totalQues, bool isActive ) = quizApp.quizzes(0);
assertEq(id, 0);
assertEq(by, address(this));
assertEq(storageId, "quiz1");
assertEq(totalQues, 5);
assertTrue(isActive);
}

function testParticipateInQuiz() public {
quizApp.createQuiz("quiz1", 3);
uint256[] memory selectedOptions = new uint256[](3);
selectedOptions[0] = 1;
selectedOptions[1] = 2;
selectedOptions[2] = 3;
quizApp.participateInQuiz(0, selectedOptions);
assertTrue(quizApp.participation(address(this), 0));
}

function testClaimReward() public {
quizApp.createQuiz("quiz1", 3);

uint256[] memory selectedOptions = new uint256[](3);
selectedOptions[0] = 1;
selectedOptions[1] = 2;
selectedOptions[2] = 3;
quizApp.participateInQuiz(0, selectedOptions);

uint256[] memory correctOptions = new uint256[](3);
correctOptions[0] = 1;
correctOptions[1] = 2;
correctOptions[2] = 3;
quizApp.endQuiz(0);

quizApp.tellCorrectAnswers(0, correctOptions);

quizApp.checkAndClaim(0);
assertEq(quizApp.viewTokens(),3);
}

}