Last active
September 5, 2020 05:08
-
-
Save antsankov/f6c614bb1df63e10bb885bbb8b022255 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
// SPDX-License-Identifier: Apache-2.0 | |
pragma solidity ^0.7.0; | |
/*** | |
* | |
* Structue of Treasury | |
* - Every block 20% of all block reward goes to this contract with deposit(false), if miner has no faith in the system they can call deposit(true) and funds are sent to burn address. | |
* - There are 3 triads addresses (ETCLabs, ETCCoop, IOHK) - any policy requires consensus of 2 of 3. | |
* - A proposal is made with a claiment address, amount of ether to be claimed, blockNumber that activates the claim. | |
* - If a poroposal gets support of 2 of 3 triads, proposal funds can be collected after the blockNumber, with redeem() which pays out to the claiment. | |
* - If 2 triads lose confidence in the third, they can vote to replace them. Requires 2/3 votes. | |
*/ | |
contract Treasury { | |
/*================================= | |
= MODIFIERS = | |
=================================*/ | |
modifier onlyTriad() { | |
require(msg.sender == ALPHA || msg.sender == BETA || msg.sender == GAMMA); | |
_; | |
} | |
/*===================================== | |
= STATIC VARS = | |
=====================================*/ | |
address payable ALPHA = 0x1111111111111111111111111111111111111111; | |
address payable BETA = 0x2222222222222222222222222222222222222222; | |
address payable GAMMA = 0x3333333333333333333333333333333333333333; | |
address payable BURN_ADDRESS = 0x0000000000000000000000000000000000000000; | |
/*================================ | |
= DATASTRUCT = | |
================================*/ | |
struct Proposal { | |
address claimer; | |
uint256 amount; | |
uint256 blockNumber; | |
} | |
// map of propsal hash to votes on that proposal | |
mapping(bytes32 => bool[3]) proposalLibrary_; | |
mapping(address => bool[3]) confidence_; | |
/*======================================= | |
= PUBLIC FUNCTIONS = | |
=======================================*/ | |
// The treasury is launched with all Triads confident in each other. | |
constructor() | |
{ | |
confidence_[ALPHA] = [true,true,true]; | |
confidence_[BETA] = [true,true,true]; | |
confidence_[GAMMA] = [true,true,true]; | |
} | |
/** | |
* Recieves Ether from Block and then gives option to burn, returns UINT amount it recieved. | |
*/ | |
function deposit(bool burn) | |
public | |
payable | |
returns(uint256) | |
{ | |
// transfer to burn address if selected, otherwise hold funds. | |
if (burn){ | |
BURN_ADDRESS.transfer(msg.value); | |
} | |
return(msg.value); | |
} | |
/** | |
* Takes in a prposal of who gets paid, the amount of ether, and the blockNumber they can do it after. Anyone can propose. | |
*/ | |
function propose(address _claimer, uint256 _amount, uint256 _blockNumber) | |
public | |
{ | |
// Proposal memory givenProposal = Proposal({ claimer: _claimer, amount: _amount, blockNumber: _blockNumber }); | |
bytes32 proposalHash = keccak256(abi.encodePacked(_claimer, _amount, _blockNumber)); | |
// Require that nothing is set at that hash. If there is any truth value there, this function will throw. | |
require ((proposalLibrary_[proposalHash][0] || proposalLibrary_[proposalHash][1] || proposalLibrary_[proposalHash][2]) == false); | |
// Set the new propposal up. | |
proposalLibrary_[proposalHash] = [false,false,false]; | |
} | |
/** | |
* Allows the Proposal to pay out, and anybody can call this function and it will payout to the desginated claimer | |
*/ | |
function ClaimerRedeem(address payable _claimer, uint256 _amount, uint256 _blockNumber) | |
public | |
{ | |
// Only can claim after the proposal block number | |
require(block.number > _blockNumber); | |
require(address(this).balance > _amount); | |
// Proposal memory claimerProposal = Proposal({ claimer: _claimer, amount: _amount, blockNumber: _blockNumber }); | |
bytes32 claimerProposalHash = keccak256(abi.encodePacked(_claimer, _amount, _blockNumber)); | |
bool[3] storage proposalSupport = proposalLibrary_[claimerProposalHash]; | |
bool alphaVote = proposalSupport[0]; | |
bool betaVote = proposalSupport[1]; | |
bool gammaVote = proposalSupport[2]; | |
if (atLeastTwoTrueVotes(alphaVote, betaVote, gammaVote)){ | |
_claimer.transfer(_amount); | |
} | |
} | |
/** | |
* Queries the Confidence in the triads, and if one is removed, replace with the caller of this function, the Hero. | |
*/ | |
function replaceTriad(address oldTriad) | |
public | |
returns(bool) | |
{ | |
require(oldTriad == ALPHA || oldTriad == BETA || oldTriad == GAMMA); | |
// The person replacing the triad, has to call this function. This forces them to control the Private key of the new triad. | |
address payable HERO = msg.sender; | |
bool[3] storage supportForNew = confidence_[HERO]; | |
bool[3] storage supportForOld = confidence_[oldTriad]; | |
// Check the quorum for supporting the oldTraid | |
bool changeQuorum = atLeastTwoTrueVotes(supportForNew[0],supportForNew[1],supportForNew[2]); | |
bool remainQuorum = atLeastTwoTrueVotes(supportForOld[0],supportForOld[1],supportForOld[2]); | |
// If we have agreed to replace- remove the oldTriad and relace with msg.sender | |
require(changeQuorum && !remainQuorum); | |
if (oldTriad == ALPHA){ | |
ALPHA = HERO; | |
} | |
else if (oldTriad == BETA){ | |
BETA = HERO; | |
} | |
else if (oldTriad == GAMMA){ | |
GAMMA = HERO; | |
} | |
// Reset the confidences, it is possible for oldTriad to come back. | |
confidence_[HERO] = [true, true, true]; | |
confidence_[oldTriad] = [false, false, false]; | |
} | |
/*======================================= | |
= TRIAD FUNCTIONS = | |
=======================================*/ | |
/** | |
* Takes in the keccak256 hash of the proposal struct, and the bool of true= yay or nay. | |
*/ | |
function voteOnProposal(bytes32 proposalHash, bool vote) | |
onlyTriad() | |
public | |
{ | |
if (msg.sender == ALPHA){ | |
proposalLibrary_[proposalHash][0] = vote; | |
} | |
else if (msg.sender == BETA){ | |
proposalLibrary_[proposalHash][1] = vote; | |
} | |
else if (msg.sender == GAMMA){ | |
proposalLibrary_[proposalHash][2] = vote; | |
} | |
} | |
/** | |
* A triad votes to remove another and replace it with a newTriad address. | |
*/ | |
function callForChange(address newTriad, address oldTriad) | |
onlyTriad() | |
public | |
{ | |
if (msg.sender == ALPHA){ | |
confidence_[oldTriad][0] = false; | |
confidence_[newTriad][0] = true; | |
} | |
else if (msg.sender == BETA){ | |
confidence_[oldTriad][1] = false; | |
confidence_[newTriad][1] = true; | |
} | |
else if (msg.sender == GAMMA){ | |
confidence_[oldTriad][2] = false; | |
confidence_[newTriad][2] = true; | |
} | |
} | |
/*========================================== | |
= INTERNAL FUNCTIONS = | |
==========================================*/ | |
// Returns true if at least two are true. | |
// See: https://stackoverflow.com/questions/3076078/check-if-at-least-two-out-of-three-booleans-are-true | |
function atLeastTwoTrueVotes(bool a, bool b, bool c) | |
internal | |
pure | |
returns(bool) | |
{ | |
return a && (b || c) || (b && c); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hey @antsankov, I've written some tests in remix (with some caveats). Here's the gist.
Actually, I change my mind on the "operational" modifier. I was only talking about applying it to stop the deposit call (send deposits back to miners), but I suppose the philosophy behind burning the reward conflicts with this.
You rock, thanks Alex!