Skip to content

Instantly share code, notes, and snippets.

@sriharish
Last active September 5, 2020 04:33
Show Gist options
  • Save sriharish/541bc1713d006bab064fe824dc0dc6c6 to your computer and use it in GitHub Desktop.
Save sriharish/541bc1713d006bab064fe824dc0dc6c6 to your computer and use it in GitHub Desktop.
Sample treasury system in solidity
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.4.22 <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);
_;
}
modifier verifySender(address sender) {
require(msg.sender == sender);
_;
}
/*=====================================
= 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() public
{
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) verifySender(_claimer)
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