Skip to content

Commit 31fcc9c

Browse files
committed
update: smart contract integration complete
1 parent 078eda7 commit 31fcc9c

File tree

16 files changed

+2724
-473
lines changed

16 files changed

+2724
-473
lines changed

README.md

+6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
Questify is a blockchain-based community platform where users can ask and answer questions in various science and technology categories. The platform incentivize knowledge sharing by rewarding users with tokens for receiving likes on their answers.
44

5+
## Important Links
6+
7+
- **Reward Token (ERC20):** [View on Blockscout](https://edu-chain-testnet.blockscout.com/token/0xF92F1f010c23Feb9cAFedC429243A248Fb843e65)
8+
9+
- **Questify Contract Address:** [View on Blockscout](https://edu-chain-testnet.blockscout.com/address/0x7b50Ee0B4fb0E715fe560E6e932a2Ed806f6D639)
10+
511
## Features
612

713
- **Ask Questions:** Post questions across different categories in science and technology.

a.sol

+347
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.0;
3+
4+
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
5+
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
6+
7+
contract Questify is ReentrancyGuard {
8+
address public owner;
9+
IERC20 public rewardToken;
10+
11+
struct Question {
12+
uint id;
13+
address author;
14+
string title;
15+
string content;
16+
string category;
17+
uint upvotes;
18+
uint downvotes;
19+
uint timestamp;
20+
}
21+
22+
struct Answer {
23+
uint id;
24+
uint questionId;
25+
string content;
26+
address author;
27+
uint upvotes;
28+
uint downvotes;
29+
uint nextMilestone;
30+
uint timestamp;
31+
}
32+
33+
struct UserStats {
34+
uint totalEarned; // Total rewards earned
35+
uint totalWithdrawn; // Total rewards withdrawn
36+
uint currentBalance; // Current rewards available for withdrawal
37+
}
38+
39+
uint public questionCounter;
40+
uint public answerCounter;
41+
uint public rewardPerMilestone = 10 * 1e18;
42+
43+
mapping(uint => Question) public questions;
44+
mapping(uint => Answer) public answers;
45+
mapping(uint => uint[]) public questionToAnswers;
46+
mapping(address => mapping(uint => bool)) public hasVotedQuestion;
47+
mapping(address => mapping(uint => bool)) public hasVotedAnswer;
48+
mapping(address => UserStats) public userStats;
49+
50+
event QuestionPosted(uint id, address author, string title);
51+
event AnswerPosted(uint id, uint questionId, address author);
52+
event AnswerVoted(uint id, address voter, bool isUpvote);
53+
event QuestionVoted(uint id, address voter, bool isUpvote);
54+
event RewardAllocated(address user, uint amount, uint milestone);
55+
event TokensWithdrawn(address user, uint amount);
56+
event RewardPoolFunded(uint amount);
57+
event OwnershipTransferred(
58+
address indexed previousOwner,
59+
address indexed newOwner
60+
);
61+
62+
modifier onlyOwner() {
63+
require(msg.sender == owner, "Only owner can perform this action");
64+
_;
65+
}
66+
67+
constructor(address _rewardToken) {
68+
owner = msg.sender;
69+
rewardToken = IERC20(_rewardToken);
70+
}
71+
72+
// Post a question
73+
function postQuestion(
74+
string memory _title,
75+
string memory _category,
76+
string memory _content
77+
) public {
78+
questionCounter++;
79+
questions[questionCounter] = Question({
80+
id: questionCounter,
81+
author: msg.sender,
82+
title: _title,
83+
content: _content,
84+
category: _category,
85+
upvotes: 0,
86+
downvotes: 0,
87+
timestamp: block.timestamp
88+
});
89+
emit QuestionPosted(questionCounter, msg.sender, _title);
90+
}
91+
92+
// Post an answer to a specific question
93+
function postAnswer(uint _questionId, string memory _content) public {
94+
require(questions[_questionId].id != 0, "The question does not exist");
95+
96+
answerCounter++;
97+
answers[answerCounter] = Answer({
98+
id: answerCounter,
99+
questionId: _questionId,
100+
content: _content,
101+
author: msg.sender,
102+
upvotes: 0,
103+
downvotes: 0,
104+
nextMilestone: 10,
105+
timestamp: block.timestamp
106+
});
107+
questionToAnswers[_questionId].push(answerCounter);
108+
emit AnswerPosted(answerCounter, _questionId, msg.sender);
109+
}
110+
111+
function voteQuestion(uint _questionId, bool isUpvote) public {
112+
require(questions[_questionId].id != 0, "Question does not exist");
113+
require(
114+
!hasVotedQuestion[msg.sender][_questionId],
115+
"Already voted on this question"
116+
);
117+
118+
hasVotedQuestion[msg.sender][_questionId] = true;
119+
if (isUpvote) {
120+
questions[_questionId].upvotes++;
121+
} else {
122+
questions[_questionId].downvotes++;
123+
}
124+
emit QuestionVoted(_questionId, msg.sender, isUpvote);
125+
}
126+
127+
function voteAnswer(uint _answerId, bool isUpvote) public {
128+
require(answers[_answerId].id != 0, "Answer does not exist");
129+
require(
130+
!hasVotedAnswer[msg.sender][_answerId],
131+
"Already voted on this answer"
132+
);
133+
134+
hasVotedAnswer[msg.sender][_answerId] = true;
135+
if (isUpvote) {
136+
checkAndAllocateReward(_answerId);
137+
answers[_answerId].upvotes++;
138+
} else {
139+
answers[_answerId].downvotes++;
140+
}
141+
emit AnswerVoted(_answerId, msg.sender, isUpvote);
142+
}
143+
144+
// Withdraw available tokens
145+
function withdrawTokens() public nonReentrant {
146+
uint rewards = userStats[msg.sender].currentBalance;
147+
require(rewards > 0, "No rewards available for withdrawal");
148+
require(
149+
rewardToken.balanceOf(address(this)) >= rewards,
150+
"Not enough tokens in the reward pool"
151+
);
152+
153+
userStats[msg.sender].currentBalance = 0;
154+
userStats[msg.sender].totalWithdrawn += rewards;
155+
rewardToken.transfer(msg.sender, rewards);
156+
157+
emit TokensWithdrawn(msg.sender, rewards);
158+
}
159+
160+
// Fund the reward pool
161+
function fundRewardPool(uint _amount) public onlyOwner {
162+
require(
163+
rewardToken.transferFrom(msg.sender, address(this), _amount),
164+
"Funding the reward pool failed"
165+
);
166+
emit RewardPoolFunded(_amount);
167+
}
168+
169+
// Fetch all questions (Gasless Read Function)
170+
function getAllQuestions() public view returns (Question[] memory) {
171+
Question[] memory allQuestions = new Question[](questionCounter);
172+
for (uint i = 1; i <= questionCounter; i++) {
173+
allQuestions[i - 1] = questions[i];
174+
}
175+
return allQuestions;
176+
}
177+
178+
function getQuestionsPaginated(
179+
uint start,
180+
uint limit
181+
) public view returns (Question[] memory) {
182+
require(start > 0 && start <= questionCounter, "Invalid start index");
183+
184+
uint end = start + limit - 1;
185+
if (end > questionCounter) end = questionCounter;
186+
187+
Question[] memory paginatedQuestions = new Question[](end - start + 1);
188+
for (uint i = start; i <= end; i++) {
189+
paginatedQuestions[i - start] = questions[i];
190+
}
191+
return paginatedQuestions;
192+
}
193+
194+
// Fetch all answers for a specific question (Gasless Read Function)
195+
function getAnswersForQuestion(
196+
uint _questionId
197+
) public view returns (Answer[] memory) {
198+
uint[] memory answerIds = questionToAnswers[_questionId];
199+
Answer[] memory questionAnswers = new Answer[](answerIds.length);
200+
201+
for (uint i = 0; i < answerIds.length; i++) {
202+
questionAnswers[i] = answers[answerIds[i]];
203+
}
204+
return questionAnswers;
205+
}
206+
207+
function getQuestionDetails(
208+
uint _questionId
209+
) public view returns (Question memory, Answer[] memory) {
210+
require(questions[_questionId].id != 0, "Question does not exist");
211+
uint[] memory answerIds = questionToAnswers[_questionId];
212+
Answer[] memory questionAnswers = new Answer[](answerIds.length);
213+
for (uint i = 0; i < answerIds.length; i++) {
214+
questionAnswers[i] = answers[answerIds[i]];
215+
}
216+
return (questions[_questionId], questionAnswers);
217+
}
218+
219+
function getQuestionsByCategory(
220+
string memory _category
221+
) public view returns (Question[] memory) {
222+
uint count;
223+
for (uint i = 1; i <= questionCounter; i++) {
224+
if (
225+
keccak256(abi.encodePacked(questions[i].category)) ==
226+
keccak256(abi.encodePacked(_category))
227+
) {
228+
count++;
229+
}
230+
}
231+
Question[] memory filteredQuestions = new Question[](count);
232+
uint index = 0;
233+
for (uint i = 1; i <= questionCounter; i++) {
234+
if (
235+
keccak256(abi.encodePacked(questions[i].category)) ==
236+
keccak256(abi.encodePacked(_category))
237+
) {
238+
filteredQuestions[index++] = questions[i];
239+
}
240+
}
241+
return filteredQuestions;
242+
}
243+
244+
// Fetch user stats (Gasless Read Function)
245+
function getUserStats(
246+
address _user
247+
)
248+
public
249+
view
250+
returns (uint totalEarned, uint totalWithdrawn, uint currentBalance)
251+
{
252+
UserStats memory stats = userStats[_user];
253+
return (stats.totalEarned, stats.totalWithdrawn, stats.currentBalance);
254+
}
255+
256+
function getRewardPoolBalance() public view returns (uint256) {
257+
return rewardToken.balanceOf(address(this));
258+
}
259+
260+
function getQuestionsByRange(
261+
uint start,
262+
uint end
263+
) public view returns (Question[] memory) {
264+
require(
265+
start > 0 && end >= start && end <= questionCounter,
266+
"Invalid range"
267+
);
268+
Question[] memory questionsRange = new Question[](end - start + 1);
269+
for (uint i = start; i <= end; i++) {
270+
questionsRange[i - start] = questions[i];
271+
}
272+
return questionsRange;
273+
}
274+
275+
function getTotalAllocatedRewards() public view returns (uint256) {
276+
uint256 totalRewards = 0;
277+
for (uint i = 1; i <= answerCounter; i++) {
278+
totalRewards += (answers[i].upvotes / 10) * 10; // Summing up rewards by milestones
279+
}
280+
return totalRewards;
281+
}
282+
283+
function transferOwnership(address newOwner) external onlyOwner {
284+
require(newOwner != address(0), "New owner cannot be zero address");
285+
emit OwnershipTransferred(owner, newOwner);
286+
owner = newOwner;
287+
}
288+
289+
function getNetVotesForQuestion(
290+
uint _questionId
291+
) public view returns (int) {
292+
require(questions[_questionId].id != 0, "Question does not exist");
293+
return
294+
int(questions[_questionId].upvotes) -
295+
int(questions[_questionId].downvotes);
296+
}
297+
298+
function getNetVotesForAnswer(uint _answerId) public view returns (int) {
299+
require(answers[_answerId].id != 0, "Answer does not exist");
300+
return
301+
int(answers[_answerId].upvotes) - int(answers[_answerId].downvotes);
302+
}
303+
304+
function checkAndAllocateReward(uint _answerId) internal {
305+
Answer storage ans = answers[_answerId];
306+
if (ans.upvotes >= ans.nextMilestone) {
307+
uint rewardAmount = rewardPerMilestone;
308+
userStats[ans.author].currentBalance += rewardAmount;
309+
userStats[ans.author].totalEarned += rewardAmount;
310+
ans.nextMilestone += 10; // Move to the next milestone
311+
312+
emit RewardAllocated(ans.author, rewardAmount, ans.nextMilestone);
313+
}
314+
}
315+
316+
function rewardTopQuestion(uint _questionId) public onlyOwner {
317+
require(questions[_questionId].id != 0, "Question does not exist");
318+
require(
319+
questions[_questionId].upvotes >= 50,
320+
"Minimum 50 upvotes required"
321+
);
322+
323+
uint rewardAmount = 50 * 1e18; // Example: 50 tokens
324+
userStats[questions[_questionId].author].currentBalance += rewardAmount;
325+
userStats[questions[_questionId].author].totalEarned += rewardAmount;
326+
327+
emit RewardAllocated(questions[_questionId].author, rewardAmount, 0);
328+
}
329+
330+
function setRewardPerMilestone(uint _newReward) external onlyOwner {
331+
rewardPerMilestone = _newReward;
332+
}
333+
334+
function hasUserVotedOnQuestion(
335+
address _user,
336+
uint _questionId
337+
) public view returns (bool) {
338+
return hasVotedQuestion[_user][_questionId];
339+
}
340+
341+
function hasUserVotedOnAnswer(
342+
address _user,
343+
uint _answerId
344+
) public view returns (bool) {
345+
return hasVotedAnswer[_user][_answerId];
346+
}
347+
}

0 commit comments

Comments
 (0)