mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
580 lines
21 KiB
Solidity
580 lines
21 KiB
Solidity
pragma solidity >=0.0;
|
||
|
||
import "./announcementTypes.sol";
|
||
import "./module.sol";
|
||
import "./moduleHandler.sol";
|
||
import "./safeMath.sol";
|
||
|
||
contract schellingVars {
|
||
/*
|
||
Common enumerations and structures of the Schelling and Database contract.
|
||
*/
|
||
enum voterStatus {
|
||
base,
|
||
afterPrepareVote,
|
||
afterSendVoteOk,
|
||
afterSendVoteBad
|
||
}
|
||
struct _rounds {
|
||
uint256 totalAboveWeight;
|
||
uint256 totalBelowWeight;
|
||
uint256 reward;
|
||
uint256 blockHeight;
|
||
bool voted;
|
||
}
|
||
struct _voter {
|
||
uint256 roundID;
|
||
bytes32 hash;
|
||
voterStatus status;
|
||
bool voteResult;
|
||
uint256 rewards;
|
||
}
|
||
}
|
||
|
||
contract schellingDB is safeMath, schellingVars {
|
||
/*
|
||
Schelling database contract.
|
||
*/
|
||
address private owner;
|
||
function replaceOwner(address newOwner) external returns(bool) {
|
||
require( owner == address(0x00) || msg.sender == owner );
|
||
owner = newOwner;
|
||
return true;
|
||
}
|
||
modifier isOwner { require( msg.sender == owner ); _; }
|
||
/*
|
||
Constructor
|
||
*/
|
||
constructor() {
|
||
rounds.push();
|
||
rounds.push();
|
||
rounds[0].blockHeight = block.number;
|
||
currentSchellingRound = 1;
|
||
}
|
||
/*
|
||
Funds
|
||
*/
|
||
mapping(address => uint256) private funds;
|
||
function getFunds(address _owner) public view returns(bool, uint256) {
|
||
return (true, funds[_owner]);
|
||
}
|
||
function setFunds(address _owner, uint256 _amount) isOwner external returns(bool) {
|
||
funds[_owner] = _amount;
|
||
return true;
|
||
}
|
||
/*
|
||
Rounds
|
||
*/
|
||
_rounds[] private rounds;
|
||
function getRound(uint256 _id) public view returns(bool, uint256, uint256, uint256, uint256, bool) {
|
||
if ( rounds.length <= _id ) { return (false, 0, 0, 0, 0, false); }
|
||
else { return (true, rounds[_id].totalAboveWeight, rounds[_id].totalBelowWeight, rounds[_id].reward, rounds[_id].blockHeight, rounds[_id].voted); }
|
||
}
|
||
function pushRound(uint256 _totalAboveWeight, uint256 _totalBelowWeight, uint256 _reward, uint256 _blockHeight, bool _voted) isOwner external returns(bool, uint256) {
|
||
rounds.push(_rounds(_totalAboveWeight, _totalBelowWeight, _reward, _blockHeight, _voted));
|
||
return (true, rounds.length);
|
||
}
|
||
function setRound(uint256 _id, uint256 _totalAboveWeight, uint256 _totalBelowWeight, uint256 _reward, uint256 _blockHeight, bool _voted) isOwner external returns(bool) {
|
||
rounds[_id] = _rounds(_totalAboveWeight, _totalBelowWeight, _reward, _blockHeight, _voted);
|
||
return true;
|
||
}
|
||
function getCurrentRound() public view returns(bool, uint256) {
|
||
return (true, rounds.length-1);
|
||
}
|
||
/*
|
||
Voter
|
||
*/
|
||
mapping(address => _voter) private voter;
|
||
function getVoter(address _owner) public view returns(bool success, uint256 roundID,
|
||
bytes32 hash, voterStatus status, bool voteResult, uint256 rewards) {
|
||
roundID = voter[_owner].roundID;
|
||
hash = voter[_owner].hash;
|
||
status = voter[_owner].status;
|
||
voteResult = voter[_owner].voteResult;
|
||
rewards = voter[_owner].rewards;
|
||
success = true;
|
||
}
|
||
function setVoter(address _owner, uint256 _roundID, bytes32 _hash, voterStatus _status, bool _voteResult, uint256 _rewards) isOwner external returns(bool) {
|
||
voter[_owner] = _voter(
|
||
_roundID,
|
||
_hash,
|
||
_status,
|
||
_voteResult,
|
||
_rewards
|
||
);
|
||
return true;
|
||
}
|
||
/*
|
||
Schelling Token emission
|
||
*/
|
||
mapping(uint256 => uint256) private schellingExpansion;
|
||
function getSchellingExpansion(uint256 _id) public view returns(bool, uint256) {
|
||
return (true, schellingExpansion[_id]);
|
||
}
|
||
function setSchellingExpansion(uint256 _id, uint256 _expansion) isOwner external returns(bool) {
|
||
schellingExpansion[_id] = _expansion;
|
||
return true;
|
||
}
|
||
/*
|
||
Current Schelling Round
|
||
*/
|
||
uint256 private currentSchellingRound;
|
||
function setCurrentSchellingRound(uint256 _id) isOwner external returns(bool) {
|
||
currentSchellingRound = _id;
|
||
return true;
|
||
}
|
||
function getCurrentSchellingRound() public view returns(bool, uint256) {
|
||
return (true, currentSchellingRound);
|
||
}
|
||
}
|
||
|
||
contract schelling is module, announcementTypes, schellingVars {
|
||
/*
|
||
Schelling contract
|
||
*/
|
||
/*
|
||
module callbacks
|
||
*/
|
||
function replaceModule(address payable addr) external override returns (bool) {
|
||
require( super.isModuleHandler(msg.sender) );
|
||
require( db.replaceOwner(addr) );
|
||
super._replaceModule(addr);
|
||
return true;
|
||
}
|
||
function transferEvent(address payable from, address payable to, uint256 value) external override returns (bool) {
|
||
/*
|
||
Transaction completed. This function can be called only by the ModuleHandler.
|
||
If this contract is the receiver, the amount will be added to the prize pool of the current round.
|
||
|
||
@from From who
|
||
@to To who
|
||
@value Amount
|
||
@bool Was the transaction successful?
|
||
*/
|
||
require( super.isModuleHandler(msg.sender) );
|
||
if ( to == address(this) ) {
|
||
uint256 currentRound = getCurrentRound();
|
||
schellingVars._rounds memory round = getRound(currentRound);
|
||
round.reward += value;
|
||
setRound(currentRound, round);
|
||
}
|
||
return true;
|
||
}
|
||
modifier isReady {
|
||
(bool _success, bool _active) = super.isActive();
|
||
require( _success && _active );
|
||
_;
|
||
}
|
||
/*
|
||
Schelling database functions.
|
||
*/
|
||
function getFunds(address addr) internal returns (uint256) {
|
||
(bool a, uint256 b) = db.getFunds(addr);
|
||
require( a );
|
||
return b;
|
||
}
|
||
function setFunds(address addr, uint256 amount) internal {
|
||
require( db.setFunds(addr, amount) );
|
||
}
|
||
function setVoter(address owner, _voter memory voter) internal {
|
||
require( db.setVoter(owner,
|
||
voter.roundID,
|
||
voter.hash,
|
||
voter.status,
|
||
voter.voteResult,
|
||
voter.rewards
|
||
) );
|
||
}
|
||
function getVoter(address addr) internal view returns (_voter memory) {
|
||
(bool a, uint256 b, bytes32 c, schellingVars.voterStatus d, bool e, uint256 f) = db.getVoter(addr);
|
||
require( a );
|
||
return _voter(b, c, d, e, f);
|
||
}
|
||
function setRound(uint256 id, _rounds memory round) internal {
|
||
require( db.setRound(id,
|
||
round.totalAboveWeight,
|
||
round.totalBelowWeight,
|
||
round.reward,
|
||
round.blockHeight,
|
||
round.voted
|
||
) );
|
||
}
|
||
function pushRound(_rounds memory round) internal returns (uint256) {
|
||
(bool a, uint256 b) = db.pushRound(
|
||
round.totalAboveWeight,
|
||
round.totalBelowWeight,
|
||
round.reward,
|
||
round.blockHeight,
|
||
round.voted
|
||
);
|
||
require( a );
|
||
return b;
|
||
}
|
||
function getRound(uint256 id) internal returns (_rounds memory) {
|
||
(bool a, uint256 b, uint256 c, uint256 d, uint256 e, bool f) = db.getRound(id);
|
||
require( a );
|
||
return _rounds(b, c, d, e, f);
|
||
}
|
||
function getCurrentRound() internal returns (uint256) {
|
||
(bool a, uint256 b) = db.getCurrentRound();
|
||
require( a );
|
||
return b;
|
||
}
|
||
function setCurrentSchellingRound(uint256 id) internal {
|
||
require( db.setCurrentSchellingRound(id) );
|
||
}
|
||
function getCurrentSchellingRound() internal view returns(uint256) {
|
||
(bool a, uint256 b) = db.getCurrentSchellingRound();
|
||
require( a );
|
||
return b;
|
||
}
|
||
function setSchellingExpansion(uint256 id, uint256 amount) internal {
|
||
require( db.setSchellingExpansion(id, amount) );
|
||
}
|
||
function getSchellingExpansion(uint256 id) internal view returns(uint256) {
|
||
(bool a, uint256 b) = db.getSchellingExpansion(id);
|
||
require( a );
|
||
return b;
|
||
}
|
||
/*
|
||
Schelling module
|
||
*/
|
||
uint256 private roundBlockDelay = 720;
|
||
uint8 private interestCheckRounds = 7;
|
||
uint8 private interestCheckAboves = 4;
|
||
uint256 private interestRate = 300;
|
||
uint256 private interestRateM = 1e3;
|
||
|
||
bytes1 public aboveChar = 0x31;
|
||
bytes1 public belowChar = 0x30;
|
||
schellingDB private db;
|
||
|
||
constructor(address payable _moduleHandler, address _db, bool _forReplace) {
|
||
/*
|
||
Installation function.
|
||
|
||
@_moduleHandler Address of ModuleHandler.
|
||
@_db Address of the database.
|
||
@_forReplace This address will be replaced with the old one or not.
|
||
@_icoExpansionAddress This address can turn schelling runds during ICO.
|
||
*/
|
||
db = schellingDB(_db);
|
||
super.registerModuleHandler(_moduleHandler);
|
||
if ( ! _forReplace ) {
|
||
require( db.replaceOwner(address(this)) );
|
||
}
|
||
}
|
||
function configure(announcementType a, uint256 b) external returns(bool) {
|
||
/*
|
||
Can be called only by the ModuleHandler.
|
||
|
||
@a Sort of configuration
|
||
@b Value
|
||
*/
|
||
require( super.isModuleHandler(msg.sender) );
|
||
if ( a == announcementType.schellingRoundBlockDelay ) { roundBlockDelay = b; }
|
||
else if ( a == announcementType.schellingCheckRounds ) { interestCheckRounds = uint8(b); }
|
||
else if ( a == announcementType.schellingCheckAboves ) { interestCheckAboves = uint8(b); }
|
||
else if ( a == announcementType.schellingRate ) { interestRate = b; }
|
||
else { return false; }
|
||
return true;
|
||
}
|
||
function prepareVote(bytes32 votehash, uint256 roundID) isReady noContract external {
|
||
/*
|
||
Initializing manual vote.
|
||
Only the hash of vote will be sent. (Envelope sending).
|
||
The address must be in default state, that is there are no vote in progress.
|
||
Votes can be sent only on the actually Schelling round.
|
||
|
||
@votehash Hash of the vote
|
||
@roundID Number of Schelling round
|
||
*/
|
||
nextRound();
|
||
|
||
uint256 currentRound = getCurrentRound();
|
||
schellingVars._rounds memory round = getRound(currentRound);
|
||
_voter memory voter;
|
||
uint256 funds;
|
||
|
||
require( roundID == currentRound );
|
||
|
||
voter = getVoter(msg.sender);
|
||
funds = getFunds(msg.sender);
|
||
|
||
require( funds > 0 );
|
||
require( voter.status == voterStatus.base );
|
||
voter.roundID = currentRound;
|
||
voter.hash = votehash;
|
||
voter.status = voterStatus.afterPrepareVote;
|
||
|
||
setVoter(msg.sender, voter);
|
||
round.voted = true;
|
||
|
||
setRound(currentRound, round);
|
||
}
|
||
function sendVote(string calldata vote) isReady noContract external {
|
||
/*
|
||
Check vote (Envelope opening)
|
||
Only the sent “envelopes” can be opened.
|
||
Envelope opening only in the next Schelling round.
|
||
If the vote invalid, the deposit will be lost.
|
||
If the “envelope” was opened later than 1,5 Schelling round, the vote is automatically invalid, and deposit can be lost.
|
||
Lost deposits will be 100% burned.
|
||
|
||
@vote Hash of the content of the vote.
|
||
*/
|
||
nextRound();
|
||
|
||
uint256 currentRound = getCurrentRound();
|
||
_rounds memory round;
|
||
_voter memory voter;
|
||
uint256 funds;
|
||
|
||
bool lostEverything;
|
||
voter = getVoter(msg.sender);
|
||
round = getRound(voter.roundID);
|
||
funds = getFunds(msg.sender);
|
||
|
||
require( voter.status == voterStatus.afterPrepareVote );
|
||
require( voter.roundID < currentRound );
|
||
if ( keccak256(bytes(vote)) == voter.hash ) {
|
||
delete voter.hash;
|
||
if (round.blockHeight+roundBlockDelay/2 >= block.number) {
|
||
if ( bytes(vote)[0] == aboveChar ) {
|
||
voter.status = voterStatus.afterSendVoteOk;
|
||
round.totalAboveWeight += funds;
|
||
voter.voteResult = true;
|
||
} else if ( bytes(vote)[0] == belowChar ) {
|
||
voter.status = voterStatus.afterSendVoteOk;
|
||
round.totalBelowWeight += funds;
|
||
} else { lostEverything = true; }
|
||
} else {
|
||
voter.status = voterStatus.afterSendVoteBad;
|
||
}
|
||
} else { lostEverything = true; }
|
||
if ( lostEverything ) {
|
||
require( moduleHandler(moduleHandlerAddress).burn(address(this), funds) );
|
||
delete funds;
|
||
delete voter.status;
|
||
}
|
||
|
||
setVoter(msg.sender, voter);
|
||
setRound(voter.roundID, round);
|
||
setFunds(msg.sender, funds);
|
||
}
|
||
function checkVote() isReady noContract external {
|
||
/*
|
||
Checking votes.
|
||
Vote checking only after the envelope opening Schelling round.
|
||
Deposit will be lost, if the vote wrong, or invalid.
|
||
The right votes take share of deposits.
|
||
*/
|
||
nextRound();
|
||
|
||
uint256 currentRound = getCurrentRound();
|
||
_rounds memory round;
|
||
_voter memory voter;
|
||
uint256 funds;
|
||
|
||
voter = getVoter(msg.sender);
|
||
round = getRound(voter.roundID);
|
||
funds = getFunds(msg.sender);
|
||
|
||
require( voter.status == voterStatus.afterSendVoteOk ||
|
||
voter.status == voterStatus.afterSendVoteBad );
|
||
if ( round.blockHeight+roundBlockDelay/2 <= block.number ) {
|
||
if ( isWinner(round, voter.voteResult) && voter.status == voterStatus.afterSendVoteOk ) {
|
||
voter.rewards += funds * round.reward / getRoundWeight(round.totalAboveWeight, round.totalBelowWeight);
|
||
} else {
|
||
require( moduleHandler(moduleHandlerAddress).burn(address(this), funds) );
|
||
delete funds;
|
||
}
|
||
delete voter.status;
|
||
delete voter.roundID;
|
||
} else { revert(); }
|
||
|
||
setVoter(msg.sender, voter);
|
||
setFunds(msg.sender, funds);
|
||
}
|
||
function getRewards(address beneficiary) isReady noContract external {
|
||
/*
|
||
Redeem of prize.
|
||
The prizes will be collected here, and with this function can be transferred to the account of the user.
|
||
Optionally there can be an address of a beneficiary added, which address the prize will be sent to. Without beneficiary, the owner is the default address.
|
||
Prize will be sent from the Schelling address without any transaction fee.
|
||
|
||
@beneficiary Address of the beneficiary
|
||
*/
|
||
schellingVars._voter memory voter = getVoter(msg.sender);
|
||
uint256 funds = getFunds(msg.sender);
|
||
|
||
address _beneficiary = msg.sender;
|
||
if (beneficiary != address(0x00)) { _beneficiary = beneficiary; }
|
||
uint256 reward;
|
||
require( voter.rewards > 0 );
|
||
require( voter.status == voterStatus.base );
|
||
reward = voter.rewards;
|
||
delete voter.rewards;
|
||
require( moduleHandler(moduleHandlerAddress).transfer(address(this), _beneficiary, reward, false) );
|
||
|
||
setVoter(msg.sender, voter);
|
||
}
|
||
function checkReward() public view returns (uint256 reward) {
|
||
/*
|
||
Withdraw of the amount of the prize (it’s only information).
|
||
|
||
@reward Prize
|
||
*/
|
||
schellingVars._voter memory voter = getVoter(msg.sender);
|
||
return voter.rewards;
|
||
}
|
||
function nextRound() internal returns (bool) {
|
||
/*
|
||
Inside function, checks the time of the Schelling round and if its needed, creates a new Schelling round.
|
||
*/
|
||
uint256 currentRound = getCurrentRound();
|
||
_rounds memory round = getRound(currentRound);
|
||
_rounds memory newRound;
|
||
_rounds memory prevRound;
|
||
uint256 currentSchellingRound = getCurrentSchellingRound();
|
||
|
||
if ( round.blockHeight+roundBlockDelay > block.number) { return false; }
|
||
|
||
newRound.blockHeight = block.number;
|
||
if ( ! round.voted ) {
|
||
newRound.reward = round.reward;
|
||
}
|
||
uint256 above;
|
||
for ( uint256 a=currentRound ; a>=currentRound-interestCheckRounds ; a-- ) {
|
||
if (a == 0) { break; }
|
||
prevRound = getRound(a);
|
||
if ( prevRound.totalAboveWeight > prevRound.totalBelowWeight ) { above++; }
|
||
}
|
||
uint256 expansion;
|
||
if ( above >= interestCheckAboves ) {
|
||
expansion = getTotalSupply() * interestRate / interestRateM / 100;
|
||
}
|
||
|
||
currentSchellingRound++;
|
||
|
||
pushRound(newRound);
|
||
setSchellingExpansion(currentSchellingRound, expansion);
|
||
require( moduleHandler(moduleHandlerAddress).broadcastSchellingRound(currentSchellingRound, expansion) );
|
||
return true;
|
||
}
|
||
function addFunds(uint256 amount) isReady noContract external {
|
||
/*
|
||
Deposit taking.
|
||
Every participant entry with his own deposit.
|
||
In case of wrong vote only this amount of deposit will be burn.
|
||
The deposit will be sent to the address of Schelling, charged with transaction fee.
|
||
|
||
@amount Amount of deposit.
|
||
*/
|
||
_voter memory voter = getVoter(msg.sender);
|
||
uint256 funds = getFunds(msg.sender);
|
||
|
||
(bool a, bool b) = moduleHandler(moduleHandlerAddress).isICO();
|
||
require( b && b );
|
||
require( voter.status == voterStatus.base );
|
||
require( amount > 0 );
|
||
require( moduleHandler(moduleHandlerAddress).transfer(msg.sender, address(this), amount, true) );
|
||
funds += amount;
|
||
|
||
setFunds(msg.sender, funds);
|
||
}
|
||
function getFunds() isReady noContract external {
|
||
/*
|
||
Deposit withdrawn.
|
||
If the deposit isn’t lost, it can be withdrawn.
|
||
By withdrawn, the deposit will be sent from Schelling address to the users address, charged with transaction fee..
|
||
*/
|
||
_voter memory voter = getVoter(msg.sender);
|
||
uint256 funds = getFunds(msg.sender);
|
||
|
||
require( funds > 0 );
|
||
require( voter.status == voterStatus.base );
|
||
setFunds(msg.sender, 0);
|
||
|
||
require( moduleHandler(moduleHandlerAddress).transfer(address(this), msg.sender, funds, true) );
|
||
}
|
||
function getCurrentSchellingRoundID() public view returns (uint256) {
|
||
/*
|
||
Number of actual Schelling round.
|
||
|
||
@uint256 Schelling round.
|
||
*/
|
||
return getCurrentSchellingRound();
|
||
}
|
||
function getSchellingRound(uint256 id) public view returns (uint256 expansion) {
|
||
/*
|
||
Amount of token emission of the Schelling round.
|
||
|
||
@id Number of Schelling round
|
||
@expansion Amount of token emission
|
||
*/
|
||
return getSchellingExpansion(id);
|
||
}
|
||
function getRoundWeight(uint256 aboveW, uint256 belowW) internal returns (uint256) {
|
||
/*
|
||
Inside function for calculating the weights of the votes.
|
||
|
||
@aboveW Weight of votes: ABOVE
|
||
@belowW Weight of votes: BELOW
|
||
@uint256 Calculatet weight
|
||
*/
|
||
if ( aboveW == belowW ) {
|
||
return aboveW + belowW;
|
||
} else if ( aboveW > belowW ) {
|
||
return aboveW;
|
||
} else if ( aboveW < belowW) {
|
||
return belowW;
|
||
}
|
||
}
|
||
function isWinner(_rounds memory round, bool aboveVote) internal returns (bool) {
|
||
/*
|
||
Inside function for calculating the result of the game.
|
||
|
||
@round Structure of Schelling round.
|
||
@aboveVote Is the vote = ABOVE or not
|
||
@bool Result
|
||
*/
|
||
if ( round.totalAboveWeight == round.totalBelowWeight ||
|
||
( round.totalAboveWeight > round.totalBelowWeight && aboveVote ) ) {
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
function getTotalSupply() internal returns (uint256 amount) {
|
||
/*
|
||
Inside function for querying the whole amount of the tokens.
|
||
|
||
@uint256 Whole token amount
|
||
*/
|
||
(bool _success, uint256 _amount) = moduleHandler(moduleHandlerAddress).totalSupply();
|
||
require( _success );
|
||
return _amount;
|
||
}
|
||
|
||
function getTokenBalance(address addr) internal returns (uint256 balance) {
|
||
/*
|
||
Inner function in order to poll the token balance of the address.
|
||
|
||
@addr Address
|
||
|
||
@balance Balance of the address.
|
||
*/
|
||
(bool _success, uint256 _balance) = moduleHandler(moduleHandlerAddress).balanceOf(addr);
|
||
require( _success );
|
||
return _balance;
|
||
}
|
||
|
||
modifier noContract {
|
||
/*
|
||
Contract can’t call this function, only a natural address.
|
||
*/
|
||
require( msg.sender == tx.origin ); _;
|
||
}
|
||
}
|