-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpageRank.sol
204 lines (194 loc) · 10 KB
/
pageRank.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
// SPDX-License-Identifier: MIT
// compiler version must be greater than or equal to 0.8.13 and less than 0.9.0
// Generate list of rankings
// users rewarded for nominating a placement which generates more interest in a list and list growth
// Each object in the list has an assosiated amount of votes which indicates its rank in the list
// each day, votes are distributed to users who stake into a list
// 5 "matchups" between members of the list are generated for each voter unless the list is small
// The results of the matchups update the list vote counts
// The goal is that we want to create a "mount rushmore" for things
// doesnt have to be four things, but there needs to be a finite list between which the members compete for placement, only people with a financial interest can vote
pragma solidity >=0.7.0 <0.9.0;
import "@openzeppelin/contracts/utils/Strings.sol";
contract pageRank {
// The first thing we need for rankings on a list is the basic structure for an item on a list
struct listItem {
string listItemName;
uint votes;
}
// secondly we create a collection of these list items to form a Ranked List
struct rankedList {
listItem [] listItems;
// every list has an assosiated owner book containing the owners and how much they have deposited into the list
ownershipStake [] owners;
}
// Each list has owners which share stakes in the list
struct ownershipStake {
address owner;
uint amountStaked;
}
// Array of list names is kept
string [] listNames;
// Every list name/title maps to a cooresponding rankedList
mapping(string => rankedList) formedLists;
// In order to form a list, a user submits the name of the list and initial items desired to be in the list
function formList(string calldata listName, string calldata listHead) public payable {
require(msg.value > 99, "Not enough dough");
require(formedLists[listName].owners.length == 0, "list already exists");
listNames.push(listName);
listItem memory firstListItem;
firstListItem.listItemName = listHead;
firstListItem.votes = 1;
ownershipStake memory firstOwner;
firstOwner.owner = msg.sender;
firstOwner.amountStaked = msg.value;
formedLists[listName].listItems.push(firstListItem);
formedLists[listName].owners.push(firstOwner);
}
function getList(string calldata listName) public returns (rankedList memory returnedList) {
return (formedLists[listName]);
}
// To append the list, the name of the list and the list item are required as well as a payment to the current list owners
event listAppend(string listHasBeenAddedTo);
function appendList(string calldata listToAppend, string calldata newListItem) public payable{
require(msg.value >= totalListValue(formedLists[listToAppend]), "Not enough ETH sent");
// require that newListItem is not already in the list
for (uint i = 0; i < formedLists[listToAppend].listItems.length; i ++){
require(keccak256(abi.encodePacked(formedLists[listToAppend].listItems[i].listItemName)) != keccak256(abi.encodePacked(newListItem)), "item already listed");
}
// inputs data
listItem memory li;
li.listItemName = newListItem;
li.votes = 0;
formedLists[listToAppend].listItems.push(li);
ownershipStake memory oS;
oS.owner = msg.sender;
oS.amountStaked = msg.value;
formedLists[listToAppend].owners.push(oS);
// Spread funds to current list owners
for (uint i = 0; i < formedLists[listToAppend].owners.length; i ++){
formedLists[listToAppend].owners[i].owner.call{value: formedLists[listToAppend].owners[i].amountStaked}("");
}
}
// It is necessary to allow users to withdraw funds from a list
function withdraw(string calldata listName) public {
require(hasStake(listName, msg.sender), "Does not hold ETH in this list");
uint i = 0;
for(; i < formedLists[listName].owners.length; i++){
if(formedLists[listName].owners[i].owner == msg.sender){
msg.sender.call{value: formedLists[listName].owners[i].amountStaked}("");
formedLists[listName].owners[i].amountStaked = 0;
break;
}
}
}
function hasStake(string memory listName, address voter) internal view returns(bool){
for(uint i = 0; i < formedLists[listName].owners.length; i++){
if(formedLists[listName].owners[i].owner == voter &&
formedLists[listName].owners[i].amountStaked > 100){
return true;
}
}
return false;
}
// function that sums up value staked in a list
function totalListValue(rankedList memory list) internal pure returns(uint){
uint sum = list.owners[0].amountStaked;
for(uint i = 1; i < list.owners.length; i ++){
sum += list.owners[i].amountStaked;
}
return sum;
}
function deposit(string calldata listName) payable public {
// TODO: add balance to existing stake if it exists rather than have duplicate address in owners array
// Going to involve changing struct of Ownership stake into a mapping instead of an array for optimization purposes - however this would colfict with desire to iterate through list to find total value
ownershipStake memory newOwner;
newOwner.owner = msg.sender;
newOwner.amountStaked = msg.value;
formedLists[listName].owners.push(newOwner);
}
struct ballot {
uint timeOfCreation;
uint ballotID;
address voter;
string nameOfList;
string [2][5] matchups;
uint [5] selections;
}
function hasVoted (address potentialVoter) internal view returns(bool){
if (createdBallots.length == 0){
return false;
}
for(uint i = createdBallots.length - 1; createdBallots[i].timeOfCreation + 86400 > block.timestamp; i--){
/*
TODO: figure out what is going on here, has Voted should return true if the voter has voted and false otherwise
if(createdBallots[i].voter == potentialVoter){
return true;
}
*/
}
return false;
}
function random(uint number, uint secondNum, uint thirdNum) internal view returns(uint){
return uint(keccak256(abi.encodePacked(block.timestamp * thirdNum,block.difficulty * secondNum,
msg.sender))) % number;
}
// TODO: An event must be logged here. The event allows the JS to find the ballot
function createBallot(string memory listName) public returns(ballot memory){
require(formedLists[listName].listItems.length > 2, "List does not exist or has one or two members");
require(hasStake(listName, msg.sender), "Does not hold a stake");
require(!hasVoted(msg.sender), "Already voted today");
// TODO: Ensure that matchups arent with themselves or repeated - special case for n = 3
ballot memory newBallot;
// distributes five random matchups for the voter to vote on
newBallot.timeOfCreation = block.timestamp;
newBallot.ballotID = createdBallots.length;
newBallot.voter = msg.sender;
newBallot.nameOfList = listName;
for(uint j = 0; j < 5; j ++){
newBallot.matchups [j][0] = formedLists[listName].listItems[random(formedLists[listName].listItems.length, j, 3)].listItemName;
newBallot.matchups [j][1] = formedLists[listName].listItems[random(formedLists[listName].listItems.length, j, 7)].listItemName;
}
createdBallots.push(newBallot);
return(newBallot);
}
// increment votes function
function incrementVotes(ballot memory ballotToBeSubmitted) internal{
for(uint i =0; i < 5; i ++){
for(uint j = 0; j < formedLists[ballotToBeSubmitted.nameOfList].listItems.length; j ++){
if( keccak256(abi.encodePacked(formedLists[ballotToBeSubmitted.nameOfList].listItems[j].listItemName)) ==
keccak256(abi.encodePacked(ballotToBeSubmitted.matchups[i][ballotToBeSubmitted.selections[i]]))){
formedLists[ballotToBeSubmitted.nameOfList].listItems[j].votes++;
}
}
}
}
// ballotInputValid function
function ballotInputValid(ballot memory ballotToBeSubmitted) internal view returns(bool){
require(ballotToBeSubmitted.timeOfCreation + 86400 > block.timestamp, "Expired Ballot");
// outer loop iterates through existing ballots to find name match
for(uint i = createdBallots.length; createdBallots[i].timeOfCreation + 86400 > block.timestamp; i--){
if(keccak256(abi.encodePacked(ballotToBeSubmitted.nameOfList)) == keccak256(abi.encodePacked(createdBallots[i].nameOfList)) &&
createdBallots[i].voter == ballotToBeSubmitted.voter){
// inner loop verifies ballot elements match
for(uint cnt = 0; cnt < 5; cnt++){
require(ballotToBeSubmitted.selections[cnt] == 1 || ballotToBeSubmitted.selections[cnt] == 2, "Invalid vote");
for(uint cnt2 = 0; cnt2 < 1; cnt2 ++){
if(keccak256(abi.encodePacked(ballotToBeSubmitted.matchups[cnt][cnt2])) != keccak256(abi.encodePacked(createdBallots[i].matchups[cnt][cnt2]))){
return false;
}
}
}
return true;
}
}
return false;
}
function vote(ballot memory ballotToBeSubmitted) public {
// If ballot is valid and message is from proper ballot owner, listToBeVotedOn is altered
require(msg.sender == ballotToBeSubmitted.voter, "Wrong Sender");
require(ballotInputValid(ballotToBeSubmitted), "Invalid ballot");
incrementVotes(ballotToBeSubmitted);
}
ballot [] createdBallots;
}