Last active
October 21, 2021 04:42
-
-
Save alexvandesande/0ede2c8f1ae98158236c413b5fa0ada1 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
/* | |
# Time Locked Multisig | |
This is a different twist on the Congress DAO / Multisig. Instead of every action requiring the approval of an X number of members, instead any transactions can be initiated by a single member, but they all will require a minimum amount of delay before they can be executed, which varies according to the support that transaction has. The more approvals a proposal has, the sooner it can be executed. A member can vote against a transaction, which will mean that it will cancel one of the other approved signatures. In a 5 members DAO, each vote means the time to wait dimishes by 10x. | |
This means that if you don't have urgency, one or two signatures might be all you need to execute any transaction. But if a single key is compromised, other keys can delay that transaction for years, making sure that the main account is emptied out way before that. | |
Transaction delays: | |
(Support - oppositon)/ total members => Time delay (approximate) | |
100% approval: 30 min | |
90%: 1h30 | |
80%: 5 hours | |
50%: about a week | |
25%: 4 months | |
10%: 2 years | |
support=oppos: 5 years or more | |
-10% 18 years | |
*/ | |
pragma solidity ^0.4.2; | |
contract owned { | |
address public owner; | |
function owned() { | |
owner = msg.sender; | |
} | |
modifier onlyOwner { | |
if (msg.sender != owner) throw; | |
_; | |
} | |
function transferOwnership(address newOwner) onlyOwner { | |
owner = newOwner; | |
} | |
} | |
contract TimeLockMultisig is owned { | |
/* Contract Variables and events */ | |
Proposal[] public proposals; | |
uint public numProposals; | |
mapping (address => uint) public memberId; | |
Member[] public members; | |
int minimumTime = 30; | |
event ProposalAdded(uint proposalID, address recipient, uint amount, string description); | |
event Voted(uint proposalID, bool position, address voter, string justification); | |
event ProposalExecuted(uint proposalID, int result, uint deadline); | |
event MembershipChanged(address member, bool isMember); | |
struct Proposal { | |
address recipient; | |
uint amount; | |
string description; | |
bool executed; | |
int currentResult; | |
bytes32 proposalHash; | |
uint creationDate; | |
Vote[] votes; | |
mapping (address => bool) voted; | |
} | |
struct Member { | |
address member; | |
bool canVote; | |
string name; | |
uint memberSince; | |
} | |
struct Vote { | |
bool inSupport; | |
address voter; | |
string justification; | |
} | |
/* modifier that allows only shareholders to vote and create new proposals */ | |
modifier onlyMembers { | |
if (memberId[msg.sender] == 0 | |
|| !members[memberId[msg.sender]].canVote) | |
throw; | |
_; | |
} | |
/* First time setup */ | |
function TimeLockMultisig(address founder, address[] initialMembers, uint minimumAmountOfMinutes) payable { | |
if (founder != 0) owner = founder; | |
if (minimumAmountOfMinutes !=0) minimumTime = int(minimumAmountOfMinutes); | |
// It’s necessary to add an empty first member | |
changeMembership(0, false, ''); | |
// and let's add the founder, to save a step later | |
changeMembership(owner, true, 'founder'); | |
changeMembers(initialMembers, true); | |
} | |
/*make member*/ | |
function changeMembership(address targetMember, bool canVote, string memberName) onlyOwner { | |
uint id; | |
if (memberId[targetMember] == 0) { | |
memberId[targetMember] = members.length; | |
id = members.length++; | |
members[id] = Member({member: targetMember, canVote: canVote, memberSince: now, name: memberName}); | |
} else { | |
id = memberId[targetMember]; | |
Member m = members[id]; | |
m.canVote = canVote; | |
} | |
MembershipChanged(targetMember, canVote); | |
} | |
function changeMembers(address[] newMembers, bool canVote) { | |
for (uint i = 0; i < newMembers.length; i++) { | |
changeMembership(newMembers[i], canVote, ''); | |
} | |
} | |
/* Function to create a new proposal */ | |
function newProposal( | |
address beneficiary, | |
uint weiAmount, | |
string jobDescription, | |
bytes transactionBytecode | |
) | |
onlyMembers | |
returns (uint proposalID) | |
{ | |
proposalID = proposals.length++; | |
Proposal p = proposals[proposalID]; | |
p.recipient = beneficiary; | |
p.amount = weiAmount; | |
p.description = jobDescription; | |
p.proposalHash = sha3(beneficiary, weiAmount, transactionBytecode); | |
p.executed = false; | |
p.creationDate = now; | |
ProposalAdded(proposalID, beneficiary, weiAmount, jobDescription); | |
numProposals = proposalID+1; | |
vote(proposalID, true, ''); | |
return proposalID; | |
} | |
/* Function to create a new proposal */ | |
function newProposalInEther( | |
address beneficiary, | |
uint etherAmount, | |
string jobDescription, | |
bytes transactionBytecode | |
) | |
onlyMembers | |
returns (uint proposalID) | |
{ | |
return newProposal(beneficiary, etherAmount * 1 ether, jobDescription, transactionBytecode); | |
} | |
/* function to check if a proposal code matches */ | |
function checkProposalCode( | |
uint proposalNumber, | |
address beneficiary, | |
uint etherAmount, | |
bytes transactionBytecode | |
) | |
constant | |
returns (bool codeChecksOut) | |
{ | |
Proposal p = proposals[proposalNumber]; | |
return p.proposalHash == sha3(beneficiary, etherAmount, transactionBytecode); | |
} | |
function vote( | |
uint proposalNumber, | |
bool supportsProposal, | |
string justificationText | |
) | |
onlyMembers | |
returns (uint voteID) | |
{ | |
Proposal p = proposals[proposalNumber]; // Get the proposal | |
if (p.voted[msg.sender] == true) throw; // If has already voted, cancel | |
p.voted[msg.sender] = true; // Set this voter as having voted | |
if (supportsProposal) { // If they support the proposal | |
p.currentResult++; // Increase score | |
} else { // If they don't | |
p.currentResult--; // Decrease the score | |
} | |
// Create a log of this event | |
Voted(proposalNumber, supportsProposal, msg.sender, justificationText); | |
} | |
function proposalDeadline(uint proposalNumber) constant returns(uint deadline) { | |
Proposal p = proposals[proposalNumber]; | |
int factor = minimumTime*10**(6 - (5 * p.currentResult / int(members.length - 1)))/10; | |
return p.creationDate + uint(factor * 1 minutes); | |
} | |
function executeProposal(uint proposalNumber, bytes transactionBytecode) returns (int result) { | |
Proposal p = proposals[proposalNumber]; | |
/* Check if the proposal can be executed: | |
- Has the voting deadline arrived? | |
- Has it been already executed or is it being executed? | |
- Does the transaction code match the proposal? | |
- Has a minimum quorum? | |
*/ | |
if (now < proposalDeadline(proposalNumber) | |
|| p.currentResult <= 0 | |
|| p.executed | |
|| !checkProposalCode(proposalNumber, p.recipient, p.amount, transactionBytecode)) | |
throw; | |
p.executed = true; | |
if (!p.recipient.call.value(p.amount)(transactionBytecode)) { | |
throw; | |
} | |
// Fire Events | |
ProposalExecuted(proposalNumber, p.currentResult, proposalDeadline(proposalNumber)); | |
} | |
function () payable {} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment