-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
redeploy with Oracle and WorldIdRegister separated
- Loading branch information
Showing
10 changed files
with
434 additions
and
484 deletions.
There are no files selected for viewing
58 changes: 58 additions & 0 deletions
58
packages/foundry/broadcast/Deploy.s.sol/480/run-1731797301.json
Large diffs are not rendered by default.
Oops, something went wrong.
45 changes: 21 additions & 24 deletions
45
packages/foundry/broadcast/Deploy.s.sol/480/run-latest.json
Large diffs are not rendered by default.
Oops, something went wrong.
312 changes: 312 additions & 0 deletions
312
packages/foundry/contracts/HumanOracleWithWorldIdRegister.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,312 @@ | ||
//SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.28; | ||
|
||
import {IWorldID} from "../lib/world-id-onchain-template/contracts/src/interfaces/IWorldID.sol"; | ||
import {WorldIdRegister} from "./WorldIdRegister.sol"; | ||
import {ByteHasher} from "./ByteHasher.sol"; | ||
// import "forge-std/console.sol"; | ||
|
||
contract HumanOracleWithWorldIdRegister is WorldIdRegister { | ||
|
||
// ==================== | ||
// ====== Structs ===== | ||
// ==================== | ||
|
||
struct Option { | ||
uint256 totalStake; | ||
mapping (address => uint256) userStakes; | ||
} | ||
|
||
struct Stake { | ||
Option[] answers; | ||
uint256 totalStake; | ||
mapping (address => bool) hasUserClaimed; | ||
} | ||
|
||
struct Vote { | ||
uint256 id; | ||
string question; | ||
string[] answers; | ||
uint256 startBlock; | ||
uint256 durationInBlocks; | ||
} | ||
|
||
// ==================== | ||
// ==== Variables ===== | ||
// ==================== | ||
|
||
// public | ||
mapping (uint256 => Stake) public stakesForVoteIds; | ||
Vote[] votes; | ||
|
||
// private | ||
mapping (uint256 => bool) private registeredNullifierHashes; | ||
|
||
|
||
// ==================== | ||
// ====== Events ====== | ||
// ==================== | ||
|
||
event VoteCreated(uint256 indexed voteId, string question, uint256 startBlock, uint256 durationInBlocks); | ||
|
||
event VoteSubmitted(address indexed user, uint256 indexed voteId, uint256 answerIndex, uint256 stakeAmount); | ||
|
||
event RewardClaimed(address indexed user, uint256 indexed voteId, uint256 rewardAmount); | ||
|
||
// ==================== | ||
// ==== Modifiers ===== | ||
// ==================== | ||
|
||
modifier hasNotVoted(uint256 voteId) { | ||
uint256 answerCount = getStakeAnswerCount(voteId); | ||
address userAddr = address(msg.sender); | ||
for (uint i = 0; i < answerCount; i++) { | ||
bool hasVoted = hasUserVotedForStakeAnswer(userAddr, voteId, i); | ||
if (hasVoted == true) { | ||
revert("user already voted"); | ||
} | ||
} | ||
_; | ||
} | ||
|
||
modifier voteActive(uint256 voteId) { | ||
require(getVoteStartBlock(voteId) <= block.number, "vote has not started"); | ||
require(getVoteStartBlock(voteId) + getVoteDurationInBlocks(voteId) >= block.number, "vote has ended"); | ||
_; | ||
} | ||
|
||
modifier voteEnded(uint256 voteId) { | ||
require(getVoteStartBlock(voteId) + getVoteDurationInBlocks(voteId) < block.number, "vote still active"); | ||
_; | ||
} | ||
|
||
modifier userExists() { | ||
if (users[address(msg.sender)].nullifierHash == 0) { | ||
revert("user not existing"); | ||
} | ||
_; | ||
} | ||
|
||
// security measurement | ||
// modifier userOldEnough(uint256 voteId) { | ||
// if (users[address(msg.sender)].createdAtBlock > getVoteStartBlock(voteId)) { | ||
// revert("user was created after voting begun"); | ||
// } | ||
// _; | ||
// } | ||
|
||
// ==================== | ||
// === Constructor ==== | ||
// ==================== | ||
|
||
constructor(address _worldIdAddr, uint256 _groupId, string memory _appId, string memory _action) WorldIdRegister(_worldIdAddr, _groupId, _appId, _action) { | ||
} | ||
|
||
// ==================== | ||
// ==== Functions ===== | ||
// ==================== | ||
|
||
// external | ||
|
||
function submitVotingDecisionWithStake(uint256 voteId, uint256 answerIndex, uint256 amount) userExists() hasNotVoted(voteId) voteActive(voteId) external { | ||
require(amount <= 5, "max staking amount is 5"); | ||
address userAddr = address(msg.sender); | ||
stakeForAnswer(userAddr, voteId, answerIndex, amount); | ||
emit VoteSubmitted(userAddr, voteId, answerIndex, amount); | ||
} | ||
|
||
function claimRewardForVote(uint256 voteId) voteEnded(voteId) external returns (uint256) { | ||
address userAddr = address(msg.sender); | ||
require(!hasUserClaimedForVote(userAddr, voteId), "user already claimed"); | ||
setUserHasClaimedToTrueForVote(userAddr, voteId); | ||
uint256 payout = getStakeResolvedUserAmount(userAddr, voteId); | ||
emit RewardClaimed(userAddr, voteId, payout); | ||
return payout; | ||
} | ||
|
||
function isUserRegistered(address userAddr) external view returns (bool) { | ||
if (users[userAddr].nullifierHash != 0) { | ||
return true; | ||
} else { | ||
return false; | ||
} | ||
} | ||
|
||
function createVote(string calldata question, string[] calldata answers, uint256 startBlock, uint256 durationInBlocks, uint256 bounty) external { | ||
uint256 voteId = votes.length; | ||
Vote memory newVote = Vote({ | ||
id: voteId, | ||
question: question, | ||
answers: answers, | ||
startBlock: startBlock, | ||
durationInBlocks: durationInBlocks | ||
}); | ||
votes.push(newVote); | ||
|
||
createNewStake(voteId, bounty); | ||
|
||
emit VoteCreated(voteId, getVoteQuestion(voteId), getVoteStartBlock(voteId), getVoteDurationInBlocks(voteId)); | ||
} | ||
|
||
function getVotingPage(uint256 voteId) external view returns ( | ||
string memory question, | ||
string[] memory answers, | ||
uint256 totalStake, | ||
uint256[] memory stakePerAnswer | ||
) { | ||
uint256 answerCount = getStakeAnswerCount(voteId); | ||
totalStake = getStakeTotalStake(voteId); | ||
stakePerAnswer = new uint256[](answerCount); | ||
|
||
for (uint i = 0; i < answerCount; i++) { | ||
stakePerAnswer[i] = getStakeAnswerStake(voteId, i); | ||
} | ||
|
||
return (getVoteQuestion(voteId), getVoteAnswers(voteId), totalStake, stakePerAnswer); | ||
} | ||
|
||
function getVotingList() external view returns ( | ||
uint256[] memory ids, | ||
string[] memory questions, | ||
uint256[] memory totalStakes | ||
) { | ||
uint256 voteCount = getVoteCount(); | ||
ids = new uint256[](voteCount); | ||
questions = new string[](voteCount); | ||
totalStakes = new uint256[](voteCount); | ||
|
||
for (uint i = 0; i < voteCount; i++) { | ||
ids[i] = i; | ||
questions[i] = getVoteQuestion(i); | ||
totalStakes[i] = getStakeTotalStake(i); | ||
} | ||
|
||
return (ids, questions, totalStakes); | ||
} | ||
|
||
function isVotingOver(uint256 voteId) external view returns (bool) { | ||
if (block.number > getVoteEndBlock(voteId)) { | ||
return true; | ||
} else { | ||
return false; | ||
} | ||
} | ||
|
||
function hasUserVotedForVote(address userAddr, uint256 voteId) external view returns (bool) { | ||
uint256 answerCount = getStakeAnswerCount(voteId); | ||
for (uint i = 0; i < answerCount; i++) { | ||
if (hasUserVotedForStakeAnswer(userAddr, voteId, i)) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
function hasUserClaimedForVote(address userAddr, uint256 voteId) public view returns (bool) { | ||
return stakesForVoteIds[voteId].hasUserClaimed[userAddr]; | ||
} | ||
|
||
function getUserPayoutForVote(address userAddr, uint256 voteId) public view returns (uint256 payout) { | ||
return getStakeResolvedUserAmount(userAddr, voteId); | ||
} | ||
|
||
// internal | ||
|
||
// stake related | ||
function createNewStake(uint256 voteId, uint256 initialStake) internal { | ||
Stake storage newStake = stakesForVoteIds[voteId]; | ||
newStake.totalStake = initialStake; | ||
uint256 answerCount = votes[voteId].answers.length; | ||
uint256 initialStakePerAnswer = initialStake / answerCount; | ||
for (uint i = 0; i < answerCount; i++) { | ||
newStake.answers.push(); | ||
newStake.answers[i].totalStake = initialStakePerAnswer; | ||
} | ||
} | ||
|
||
function stakeForAnswer(address userAddr, uint256 voteId, uint256 answerIndex, uint256 amount) internal { | ||
stakesForVoteIds[voteId].answers[answerIndex].userStakes[userAddr] = amount; | ||
stakesForVoteIds[voteId].answers[answerIndex].totalStake += amount; | ||
stakesForVoteIds[voteId].totalStake += amount; | ||
} | ||
|
||
function setUserHasClaimedToTrueForVote(address userAddr, uint256 voteId) internal { | ||
stakesForVoteIds[voteId].hasUserClaimed[userAddr] = true; | ||
} | ||
|
||
function getStakeResolvedUserAmount(address userAddr, uint256 voteId) internal view returns (uint256 amount) { | ||
uint256 highestStakeAnswerIndex = getStakeHighestAnswerIndex(voteId); | ||
if (!hasUserVotedForStakeAnswer(userAddr, voteId, highestStakeAnswerIndex)) { | ||
return 0; | ||
} | ||
uint256 userStake = getUserStakeOfStakeAnswer(userAddr, voteId, highestStakeAnswerIndex); | ||
uint256 totalStake = getStakeTotalStake(voteId); | ||
uint256 answerStake = getStakeAnswerStake(voteId, highestStakeAnswerIndex); | ||
uint256 userPayout = totalStake / answerStake * userStake; | ||
return userPayout; | ||
} | ||
|
||
function getStakeHighestAnswerIndex(uint256 voteId) internal view returns (uint256 answerIndex) { | ||
uint256 answerCount = getStakeAnswerCount(voteId); | ||
uint256 highestAmount = 0; | ||
uint256 highestIndex = 0; | ||
|
||
for (uint i = 0; i < answerCount; i++) { | ||
uint256 answerStake = getStakeAnswerStake(voteId, i); | ||
if (highestAmount < answerStake) { | ||
highestAmount = answerStake; | ||
highestIndex = i; | ||
} | ||
} | ||
return highestIndex; | ||
} | ||
|
||
function getStakeAnswerStake(uint256 voteId, uint256 answerIndex) internal view returns (uint256 stake) { | ||
return stakesForVoteIds[voteId].answers[answerIndex].totalStake; | ||
} | ||
|
||
function getStakeAnswerCount(uint256 voteId) internal view returns (uint256 count) { | ||
return stakesForVoteIds[voteId].answers.length; | ||
} | ||
|
||
function getStakeTotalStake(uint256 voteId) internal view returns (uint256 stake) { | ||
return stakesForVoteIds[voteId].totalStake; | ||
} | ||
|
||
function hasUserVotedForStakeAnswer(address userAddr, uint256 voteId, uint256 answerIndex) internal view returns (bool voted) { | ||
if (stakesForVoteIds[voteId].answers[answerIndex].userStakes[userAddr] != 0) { | ||
return true; | ||
} else { | ||
return false; | ||
} | ||
} | ||
|
||
function getUserStakeOfStakeAnswer(address userAddr, uint256 voteId, uint256 answerIndex) internal view returns (uint256 amount) { | ||
return stakesForVoteIds[voteId].answers[answerIndex].userStakes[userAddr]; | ||
} | ||
|
||
// vote related | ||
function getVoteCount() internal view returns (uint256 count) { | ||
return votes.length; | ||
} | ||
|
||
function getVoteQuestion(uint256 voteId) internal view returns (string memory question) { | ||
return votes[voteId].question; | ||
} | ||
|
||
function getVoteAnswers(uint256 voteId) internal view returns (string[] memory answers) { | ||
return votes[voteId].answers; | ||
} | ||
|
||
function getVoteStartBlock(uint256 voteId) internal view returns (uint256 startBlock) { | ||
return votes[voteId].startBlock; | ||
} | ||
|
||
function getVoteDurationInBlocks(uint256 voteId) internal view returns (uint256 durationInBlocks) { | ||
return votes[voteId].durationInBlocks; | ||
} | ||
|
||
function getVoteEndBlock(uint256 voteId) internal view returns (uint256 endBlock) { | ||
return getVoteStartBlock(voteId) + getVoteDurationInBlocks(voteId); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
22 changes: 22 additions & 0 deletions
22
packages/foundry/script/DeployHumanOracleWithWorldIdRegister.s.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
//SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.19; | ||
|
||
import "../contracts/HumanOracleWithWorldIdRegister.sol"; | ||
import "./DeployHelpers.s.sol"; | ||
|
||
contract DeployHumanOracleWithWorldIdRegister is ScaffoldETHDeploy { | ||
|
||
address public worldIdAddr = 0x17B354dD2595411ff79041f930e491A4Df39A278; | ||
uint256 public groupId = 1; | ||
string public appId = "app_22ea9fb73d53333c2997e8f16e60cc6b"; | ||
string public action = "registration"; | ||
|
||
function run() external ScaffoldEthDeployerRunner { | ||
HumanOracleWithWorldIdRegister humanOracle = new HumanOracleWithWorldIdRegister(worldIdAddr, groupId, appId, action); | ||
console.logString( | ||
string.concat( | ||
"HumanOracleWithWorldIdRegister deployed at: ", vm.toString(address(humanOracle)) | ||
) | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.