Skip to content

Instantly share code, notes, and snippets.

@alexvandesande
Last active October 21, 2021 04:42
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alexvandesande/0ede2c8f1ae98158236c413b5fa0ada1 to your computer and use it in GitHub Desktop.
Save alexvandesande/0ede2c8f1ae98158236c413b5fa0ada1 to your computer and use it in GitHub Desktop.
/*
# 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