-
Notifications
You must be signed in to change notification settings - Fork 0
/
freedom-tribunal.sol
135 lines (127 loc) · 5.33 KB
/
freedom-tribunal.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
// SPDX-License-Identifier: GNU AFFERO GENERAL PUBLIC LICENSE Version 3
// Incentive System for Truth Exploration, Respect & Freedom.
// Can be utilized for decentralized content moderation.
// Any project can become a community guarded project via the Freedom Tribunal.
// The Freedom Tribunal leverages FREI (https://polygonscan.com/token/0x099471b71c9d8b0c6b616ee9a7c645e22ca9cff7)
// as decentralized currency to incentivize voting.
pragma solidity 0.8.19;
import "https://raw.githubusercontent.com/moniquebaumann/freedom-cash/v0.0.1/freedom-cash-interface.sol";
import "https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/v4.9.4/contracts/token/ERC20/IERC20.sol";
contract FreedomTribunal {
uint256 public voteCounter;
mapping(bytes32 => IAsset) public assets;
mapping(uint256 => IVote) public votes;
mapping(uint256 => bytes32) public voteToAssetHash;
address public FREI = 0x099471B71c9D8b0C6b616eE9A7C645e22cA9cfF7;
struct IAsset{
uint256 upVoteScore;
uint256 downVoteScore;
uint256 reconciliationFrom;
bool reconciled;
}
struct IVote {
address payable from;
uint256 amount;
bool up;
uint256 rewardAmount;
bool claimed;
}
error Patience();
error Nonsense();
error HashAlreadyRegistered();
error NothingToClaim();
function isRegistered(bytes32 hash) public view returns(bool) {
return assets[hash].reconciliationFrom != 0;
}
function addAsset(bytes32 hash, uint256 votingPeriodMinLength) public {
if (assets[hash].reconciliationFrom != 0) { revert HashAlreadyRegistered(); }
IAsset memory asset = IAsset(0, 0, block.timestamp + votingPeriodMinLength, false);
assets[hash] = asset;
}
function appreciateAsset(bytes32 hash, uint256 appreciationAmountFC) public payable {
if(assets[hash].reconciled) { revert Nonsense(); }
voteCounter++;
IFreedomCash(FREI).buyFreedomCash{value: msg.value}(address(this), appreciationAmountFC);
assets[hash].upVoteScore += appreciationAmountFC;
IVote memory vote = IVote(payable (msg.sender), appreciationAmountFC, true, 0, false);
votes[voteCounter] = vote;
voteToAssetHash[voteCounter] = hash;
}
function depreciateAsset(bytes32 hash, uint256 depreciationAmountFC) public payable {
if(assets[hash].reconciled) { revert Nonsense(); }
voteCounter++;
IFreedomCash(FREI).buyFreedomCash{value: msg.value}(address(this), depreciationAmountFC);
assets[hash].downVoteScore += depreciationAmountFC;
IVote memory vote = IVote(payable(msg.sender), depreciationAmountFC, false, 0, false);
votes[voteCounter] = vote;
voteToAssetHash[voteCounter] = hash;
}
function reconcile(bytes32 hash) public {
if(assets[hash].reconciled) { revert Nonsense(); }
if(assets[hash].upVoteScore == 0 && assets[hash].downVoteScore == 0) { revert Nonsense(); }
if (block.timestamp < assets[hash].reconciliationFrom) { revert Patience(); }
if (assets[hash].upVoteScore >= assets[hash].downVoteScore) {
uint256 sumOfLosingVotes = getSumOfLosingVotes(hash, true);
if (sumOfLosingVotes > 0) {
uint256 numberOfWinningVotes = getNumberOfWinningVotes(hash, true);
distributeRewards(hash, true, sumOfLosingVotes, numberOfWinningVotes);
}
} else {
uint256 sumOfLosingVotes = getSumOfLosingVotes(hash, false);
if (sumOfLosingVotes > 0) {
uint256 numberOfWinningVotes = getNumberOfWinningVotes(hash, false);
distributeRewards(hash, false, sumOfLosingVotes, numberOfWinningVotes);
}
}
assets[hash].reconciled = true;
}
function getClaimableRewards(address receiver) public view returns(uint256 sum) {
for (uint256 i = 1; i <= voteCounter; i++) {
if (receiver == votes[i].from && !votes[i].claimed && assets[voteToAssetHash[i]].reconciled) {
sum += votes[i].rewardAmount;
}
}
}
function claimRewards() public {
uint256 amount = getClaimableRewards(msg.sender);
if(amount == 0){ revert NothingToClaim(); }
for (uint256 i = 1; i <= voteCounter; i++) {
if (msg.sender == votes[i].from) {
if (!votes[i].claimed && assets[voteToAssetHash[i]].reconciled && votes[i].rewardAmount > 0) {
votes[i].claimed = true;
}
}
}
IERC20(FREI).transfer(msg.sender, amount);
}
function getNumberOfWinningVotes(bytes32 hash, bool up) public view returns (uint256 counter) {
for (uint256 i = 1; i <= voteCounter; i++) {
if (hash == voteToAssetHash[i]) {
if(up && votes[i].up) {
counter++;
} else if(!up && !votes[i].up) {
counter++;
}
}
}
}
function getSumOfLosingVotes(bytes32 hash, bool up) public view returns (uint256 sum) {
for (uint256 i = 1; i <= voteCounter; i++) {
if (hash == voteToAssetHash[i]) {
if((up && !votes[i].up) || (!up && votes[i].up)) {
sum += votes[i].amount;
}
}
}
}
function distributeRewards(bytes32 hash, bool toUpvoters, uint256 sumOfLosingVotes, uint256 numberOfWinningVotes) internal {
uint256 rewardPerWinner = sumOfLosingVotes / numberOfWinningVotes;
for (uint256 i = 1; i <= voteCounter; i++) {
if (voteToAssetHash[i] == hash) {
if((votes[i].up && toUpvoters) || (!votes[i].up && !toUpvoters)){
votes[i].rewardAmount = rewardPerWinner + votes[i].amount;
}
}
}
}
}