Skip to content

Dao11 #228

Closed
wants to merge 22 commits into from
Select commit
+182 −82
View
214 DAO.sol
@@ -43,6 +43,10 @@ contract DAOInterface {
// Denotes the maximum proposal deposit that can be given. It is given as
// a fraction of total Ether spent plus balance of the DAO
uint constant maxDepositDivisor = 100;
+ // Grace period for splitting after voting did end
+ uint constant splitGracePeriod = 3 days;
+ // Time for vote freeze. A proposal needs to have majority support before votingDeadline - preSupportTime
+ uint constant preSupportTime = 2 days;
// Proposals to spend the DAO's ether or to choose a new Curator
Proposal[] public proposals;
@@ -82,6 +86,9 @@ contract DAOInterface {
// tokens). The address points to the proposal ID.
mapping (address => uint) public blocked;
+ // Map of addresses and proposal voted on by this address
+ mapping (address => uint[]) public votingRegister;
+
// The minimum deposit (in wei) required to submit any proposal that is not
// requesting a new Curator (no deposit is required for splits)
uint public proposalDeposit;
@@ -121,6 +128,13 @@ contract DAOInterface {
bool newCurator;
// Data needed for splitting the DAO
SplitData[] splitData;
+ // Dependencies in other proposals. List of proposal IDs and list defining
+ // whether the other proposal needs to have passed or failed
+ uint[] dependentProposalIDs;
+ bool[] dependencyShouldPass;
+ // true if more tokens are in favour of the proposal than opposed to it at
+ // least `preSupportTime` before the voting deadline
+ bool preSupport;
// Number of Tokens in favor of the proposal
uint yea;
// Number of Tokens opposed to the proposal
@@ -206,7 +220,9 @@ contract DAOInterface {
string _description,
bytes _transactionData,
uint _debatingPeriod,
- bool _newCurator
+ bool _newCurator,
+ uint[] dependentProposalIDs,
+ bool[] dependencyShouldPass
) onlyTokenholders returns (uint _proposalID);
/// @notice Check that the proposal with the ID `_proposalID` matches the
@@ -337,7 +353,8 @@ contract DAOInterface {
address recipient,
uint amount,
bool newCurator,
- string description
+ string description,
+ bytes transactionData
);
event Voted(uint indexed proposalID, bool position, address indexed voter);
event ProposalTallied(uint indexed proposalID, bool result, uint quorum);
@@ -382,7 +399,7 @@ contract DAO is DAOInterface, Token, TokenCreation {
if (address(DAOrewardAccount) == 0)
throw;
lastTimeMinQuorumMet = now;
- minQuorumDivisor = 5; // sets the minimal quorum to 20%
+ minQuorumDivisor = 7; // sets the minimal quorum to 14.2%
proposals.length = 1; // avoids a proposal with ID 0 because it is used
allowedRecipients[address(this)] = true;
@@ -408,10 +425,16 @@ contract DAO is DAOInterface, Token, TokenCreation {
string _description,
bytes _transactionData,
uint _debatingPeriod,
- bool _newCurator
+ bool _newCurator,
+ uint[] _dependentProposalIDs,
+ bool[] _dependencyShouldPass
) onlyTokenholders returns (uint _proposalID) {
// Sanity check
+
+ if (_dependentProposalIDs.length != _dependencyShouldPass.length)
+ throw;
+
if (_newCurator && (
_amount != 0
|| _transactionData.length != 0
@@ -421,16 +444,15 @@ contract DAO is DAOInterface, Token, TokenCreation {
throw;
} else if (
!_newCurator
- && (!isRecipientAllowed(_recipient) || (_debatingPeriod < minProposalDebatePeriod))
+ && (!allowedRecipients[_recipient] || (_debatingPeriod < minProposalDebatePeriod))
) {
throw;
}
if (_debatingPeriod > 8 weeks)
throw;
- if (!isFueled
- || now < closingTime
+ if (now < closingTime
|| (msg.value < proposalDeposit && !_newCurator)) {
throw;
@@ -461,6 +483,8 @@ contract DAO is DAOInterface, Token, TokenCreation {
p.splitData.length++;
p.creator = msg.sender;
p.proposalDeposit = msg.value;
+ p.dependentProposalIDs = _dependentProposalIDs;
+ p.dependencyShouldPass = _dependencyShouldPass;
sumOfProposalDeposits += msg.value;
@@ -469,7 +493,8 @@ contract DAO is DAOInterface, Token, TokenCreation {
_recipient,
_amount,
_newCurator,
- _description
+ _description,
+ _transactionData
);
}
@@ -487,13 +512,12 @@ contract DAO is DAOInterface, Token, TokenCreation {
function vote(uint _proposalID, bool _supportsProposal) onlyTokenholders noEther {
+ if (DAO(parentDAO).balanceOf(msg.sender) != 0)
+ swapTokens();
+
Proposal p = proposals[_proposalID];
- if (p.votedYes[msg.sender]
- || p.votedNo[msg.sender]
- || now >= p.votingDeadline) {
- throw;
- }
+ unVote(_proposalID);
if (_supportsProposal) {
p.yea += balances[msg.sender];
@@ -511,9 +535,56 @@ contract DAO is DAOInterface, Token, TokenCreation {
blocked[msg.sender] = _proposalID;
}
+ votingRegister[msg.sender].push(_proposalID);
Voted(_proposalID, _supportsProposal, msg.sender);
}
+ function unVote(uint _proposalID){
@colm
colm added a note Jun 10, 2016

can we make unVote internal? I think its better to minimize the attack surface in this way. Unless you intend for it to be called by DTH.

@LefterisJP
LefterisJP added a note Jun 10, 2016

I think it's intended to be used by the DTH too.

@colm
colm added a note Jun 10, 2016

So then I think unVote should remove the proposal ID from votingRegister[msg.sender] Otherwise if someone votes on too many proposals it will result in them being unable to call unVoteAll due to block gas limit. Then they cannot call unVote to remove once blockage at a time since votingRegister[msg.sender] remains the same.

@CJentzsch
CJentzsch added a note Jun 13, 2016

this is done in unvoteAll . This can be called at any time to clear things up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ Proposal p = proposals[_proposalID];
+
+ if (now >= p.votingDeadline) {
+ throw;
+ }
+
+ if ((p.votedYes[msg.sender]
+ || p.votedNo[msg.sender])
+ && now > p.votingDeadline - preSupportTime) {
+ throw;
+ }
+
+ if (p.votedYes[msg.sender]) {
+ p.yea -= balances[msg.sender];
+ p.votedYes[msg.sender] = false;
+ }
+
+ if (p.votedNo[msg.sender]) {
+ p.nay -= balances[msg.sender];
+ p.votedNo[msg.sender] = false;
+ }
+ }
+
+ function unVoteAll() {
+ for (uint i = 0; i < votingRegister[msg.sender].length; i++) {
+ Proposal p = proposals[votingRegister[msg.sender][i]];
+ if (now < p.votingDeadline)
+ unVote(i);
+ }
+
+ votingRegister[msg.sender].length = 0;
+ blocked[msg.sender] = 0;
+ }
+
+ function verifyPreSupport(uint _proposalID) {
+ Proposal p = proposals[_proposalID];
+ if (now < p.votingDeadline - preSupportTime) {
+ if (p.yea > p.nay) {
@LefterisJP
LefterisJP added a note Jun 8, 2016

Maybe add a yea quorum requirement at the presupport? Just like we have done with PFOffer.sol?

@CJentzsch
CJentzsch added a note Jun 8, 2016

I think it is enough that we don't count the nay votes in the real quorum.
Are there any good arguments for a yea quorum here?

@LefterisJP
LefterisJP added a note Jun 8, 2016

Same one as I used for PFOffer.sol. I can call verifyPreSupport() in the beginning when the vote is not really set and set the flag to true if yea > nay.

At a later state someone else may call it to change it back to false if yea and nay are in a very close fight. But if they don't, then the preSupport flag will be set while it should not have been.

@CJentzsch
CJentzsch added a note Jun 8, 2016

As you said, the function can be called again and again to update the result, until preSupportTime before the end of the voting deadline. I think this is enough, another quorum is not needed in my view.

@cougarously
cougarously added a note Jun 9, 2016 edited

I think there's an issue with unvote() if you don't keep state of their original balance[voter] used to increment yea or nay votes.

Here's the scenario:

1) balances[voter] is currently 100
2) the msg.sender votes yes and yea is incremented by 100
3) someone calls Token.transfer(voter, 1000) or the spender.transferFrom(someone, voter, 1000)
4) we unvote() based on a balance[vote] of 1000 and yea is decremented too much.

Perhaps we can use Proposal.yesVote[voter] and Proposal.votedNo[voter] can be used to keep state of their original voting balance[voter] instead of as a bool.

Thoughts?

@LefterisJP
LefterisJP added a note Jun 9, 2016

@cougarously: No we don't need this because we are now also stopping people from transferring tokens to blocked addresses.

so in your scenario step (3) would fail.

@cougarously
cougarously added a note Jun 9, 2016

@LefterisJP - Ah okay! I see that there is DAO.transfer and DAO.transferFrom() that overrides the ones in Token.

@cougarously
cougarously added a note Jun 9, 2016 edited

How about this scenario.

I have a voted on many proposals that are currently still active. One of those proposals is a split proposal and I split and I'm not part of the DAO anymore but I have influence on the proposals that are still active. Should this be allowed? If not (which I believe is the case), we need to unvoteAll() when burning their tokens in splitDAO() and withdraw().

So here's what may need to be done:

1) add the modifier onlyTokenholders to unvote() and unvoteAll()
2) In the splitDAO() and withdraw(), call unvoteAll() first before setting balance[msg.sender] = 0. However, you want a way to not unvote the split proposal itself so that you can keep a record.

@CJentzsch
CJentzsch added a note Jun 10, 2016

Thats already the case. In splitDAO and withdraw unvoteAll is called.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ p.preSupport = true;
+ }
+ else
+ p.preSupport = false;
+ }
+ }
+
function executeProposal(
uint _proposalID,
@@ -526,13 +597,14 @@ contract DAO is DAOInterface, Token, TokenCreation {
? splitExecutionPeriod
: executeProposalPeriod;
// If we are over deadline and waiting period, assert proposal is closed
- if (p.open && now > p.votingDeadline + waitPeriod) {
+ if (p.open && now > p.votingDeadline + splitGracePeriod + waitPeriod) {
closeProposal(_proposalID);
return;
}
// Check if the proposal can be executed
- if (now < p.votingDeadline // has the voting deadline arrived?
+ if (!isFueled
+ || now < p.votingDeadline + splitGracePeriod // has the voting deadline arrived?
// Have the votes been counted?
|| !p.open
|| p.proposalPassed // anyone trying to call us recursively?
@@ -542,22 +614,29 @@ contract DAO is DAOInterface, Token, TokenCreation {
throw;
}
+ // check dependencies
+ for (uint _p = 0; _p < p.dependentProposalIDs.length; _p++) {
+ Proposal requiredProposal = proposals[p.dependentProposalIDs[_p]];
+ if (requiredProposal.proposalPassed != p.dependencyShouldPass[_p])
+ throw;
+ }
+
// If the curator removed the recipient from the whitelist, close the proposal
// in order to free the deposit and allow unblocking of voters
- if (!isRecipientAllowed(p.recipient)) {
+ if (!allowedRecipients[p.recipient]) {
closeProposal(_proposalID);
p.creator.send(p.proposalDeposit);
return;
}
bool proposalCheck = true;
- if (p.amount > actualBalance())
+ if (p.amount > actualBalance() || p.preSupport == false)
proposalCheck = false;
- uint quorum = p.yea + p.nay;
+ uint quorum = p.yea;
- // require 53% for calling newContract()
+ // require max quorum for calling newContract()
if (_transactionData.length >= 4 && _transactionData[0] == 0x68
&& _transactionData[1] == 0x37 && _transactionData[2] == 0xff
&& _transactionData[3] == 0x1e
@@ -566,18 +645,20 @@ contract DAO is DAOInterface, Token, TokenCreation {
proposalCheck = false;
}
- if (quorum >= minQuorum(p.amount)) {
+ uint minimumQuorum = minQuorum(p.amount);
+
+ if (quorum >= minimumQuorum) {
if (!p.creator.send(p.proposalDeposit))
throw;
lastTimeMinQuorumMet = now;
- // set the minQuorum to 20% again, in the case it has been reached
- if (quorum > totalSupply / 5)
- minQuorumDivisor = 5;
+ // set the minQuorum to 14.3% again, in the case it has been reached
+ if (quorum > totalSupply / 7)
+ minQuorumDivisor = 7;
}
// Execute result
- if (quorum >= minQuorum(p.amount) && p.yea > p.nay && proposalCheck) {
+ if (quorum >= minimumQuorum && p.yea > p.nay && proposalCheck) {
// we are setting this here before the CALL() value transfer to
// assure that in the case of a malicious recipient contract trying
// to call executeProposal() recursively money can't be transferred
@@ -593,7 +674,6 @@ contract DAO is DAOInterface, Token, TokenCreation {
// related addresses. Proxy addresses should be forbidden by the curator.
if (p.recipient != address(this) && p.recipient != address(rewardAccount)
&& p.recipient != address(DAOrewardAccount)
- && p.recipient != address(extraBalance)
&& p.recipient != address(curator)) {
@cougarously
cougarously added a note Jun 9, 2016 edited

Do you think this should be removed: && p.recipient != address(extraBalance)? If we remove it I think we will be double counting reward tokens.

I think what we want to do is move the funds from the extraBalance account to the DAO without incrementing the reward tokens initially. Then when there is a real proposal that requires funding from the DAO, then the reward tokens will be incremented appropriately.

@cougarously
cougarously added a note Jun 9, 2016

In addition to my comment above. How do we actually move funds from the extraBalance to the DAO?

If a Proposal's recipient is the DAO itself, then the code in executeProposal() will send funds to itself when executing:

   if (!p.recipient.call.value(p.amount)(_transactionData))
      throw;

So it seems like we need some special code where the DAO can send a message to the extraBalance account by calling extraBalance.payout(address(this), extraBalance.balance)

@CJentzsch
CJentzsch added a note Jun 10, 2016

In the new DAO there is no extraBalance anymore.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
rewardToken[address(this)] += p.amount;
@@ -615,6 +695,46 @@ contract DAO is DAOInterface, Token, TokenCreation {
p.open = false;
}
+ function withdraw(
+ address _newCurator
+ ) noEther onlyTokenholders returns (bool _success) {
+
+ unVoteAll();
+
+ // Move ether
+ uint fundsToBeMoved =
+ (balances[msg.sender] * actualBalance()) /
+ totalSupply;
+
+ msg.sender.call.value(fundsToBeMoved);
+
+ // Assign reward rights
+ uint rewardTokenToBeMoved =
+ (balances[msg.sender] * rewardToken[address(this)]) /
+ totalSupply;
+
+ uint paidOutToBeMoved = DAOpaidOut[address(this)] * rewardTokenToBeMoved /
+ rewardToken[address(this)];
+
+ rewardToken[msg.sender] += rewardTokenToBeMoved;
+ if (rewardToken[address(this)] < rewardTokenToBeMoved)
+ throw;
+ rewardToken[address(this)] -= rewardTokenToBeMoved;
+
+ DAOpaidOut[msg.sender] += paidOutToBeMoved;
+ if (DAOpaidOut[address(this)] < paidOutToBeMoved)
+ throw;
+ DAOpaidOut[address(this)] -= paidOutToBeMoved;
+
+ // Burn DAO Tokens
+ Transfer(msg.sender, 0, balances[msg.sender]);
+ withdrawRewardFor(msg.sender); // be nice, and get his rewards
+ totalSupply -= balances[msg.sender];
+ balances[msg.sender] = 0;
+ paidOut[msg.sender] = 0;
+ return true;
+ }
+
function splitDAO(
uint _proposalID,
address _newCurator
@@ -632,13 +752,13 @@ contract DAO is DAOInterface, Token, TokenCreation {
// Is it a new curator proposal?
|| !p.newCurator
// Have you voted for this split?
- || !p.votedYes[msg.sender]
- // Did you already vote on another proposal?
- || (blocked[msg.sender] != _proposalID && blocked[msg.sender] != 0) ) {
+ || !p.votedYes[msg.sender]) {
throw;
}
+ unVoteAll();
+
// If the new DAO doesn't exist yet, create the new DAO and store the
// current split data
if (address(p.splitData[0].newDAO) == 0) {
@@ -662,7 +782,6 @@ contract DAO is DAOInterface, Token, TokenCreation {
if (p.splitData[0].newDAO.createTokenProxy.value(fundsToBeMoved)(msg.sender) == false)
throw;
-
// Assign reward rights to new DAO
uint rewardTokenToBeMoved =
(balances[msg.sender] * p.splitData[0].rewardToken) /
@@ -755,6 +874,7 @@ contract DAO is DAOInterface, Token, TokenCreation {
if (isFueled
&& now > closingTime
&& !isBlocked(msg.sender)
+ && !isBlocked(_to)
&& _to != address(this)
&& transferPaidOut(msg.sender, _to, _value)
&& super.transfer(_to, _value)) {
@@ -777,6 +897,7 @@ contract DAO is DAOInterface, Token, TokenCreation {
if (isFueled
&& now > closingTime
&& !isBlocked(_from)
+ && !isBlocked(_to)
&& _to != address(this)
&& transferPaidOut(_from, _to, _value)
&& super.transferFrom(_from, _to, _value)) {
@@ -826,32 +947,24 @@ contract DAO is DAOInterface, Token, TokenCreation {
function changeAllowedRecipients(address _recipient, bool _allowed) noEther external returns (bool _success) {
- if (msg.sender != curator)
+ // only allow the curator and the DAO itself (only removing addresses) to make changes to the whitelist
+ if (msg.sender != curator && (msg.sender != address(this) || _allowed))
@colm
colm added a note Jun 10, 2016

msg.sender != address(this) is a pointless check. If msg.sender == curator we know msg.seder != address(this) maybe it should be _recipient to disallow removal of the dao from whitelist.

@LefterisJP
LefterisJP added a note Jun 10, 2016

The second leg of the boolean condition after the && is essentially saying:

And if the DAO makes a proposal to change the allowed recipient and that change is removal from the whitelist then let it go through.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ throw;
+ if (_recipient == address(this))
throw;
allowedRecipients[_recipient] = _allowed;
AllowedRecipientChanged(_recipient, _allowed);
return true;
}
- function isRecipientAllowed(address _recipient) internal returns (bool _isAllowed) {
- if (allowedRecipients[_recipient]
- || (_recipient == address(extraBalance)
- // only allowed when at least the amount held in the
- // extraBalance account has been spent from the DAO
- && totalRewardToken > extraBalance.accumulatedInput()))
- return true;
- else
- return false;
- }
-
function actualBalance() constant returns (uint _actualBalance) {
return this.balance - sumOfProposalDeposits;
}
function minQuorum(uint _value) internal constant returns (uint _minQuorum) {
- // minimum of 20% and maximum of 53.33%
+ // minimum of 14.3% and maximum of 47.6%
return totalSupply / minQuorumDivisor +
(_value * totalSupply) / (3 * (actualBalance() + rewardToken[address(this)]));
}
@@ -910,6 +1023,23 @@ contract DAO is DAOInterface, Token, TokenCreation {
function unblockMe() returns (bool) {
return isBlocked(msg.sender);
}
+
+ function combinedBalanceOf(address _address){
+ DAO(parentDAO).balanceOf(_address) + balanceOf(_address);
+ }
+
+ // approve DAO to transfer your tokens prior to that
+ function swapTokens() {
+ uint balance = DAO(parentDAO).balanceOf(msg.sender);
+ if (DAO(parentDAO).transferFrom(msg.sender, this, balance)) {
+ balances[msg.sender] += balance;
+ totalSupply += balance;
+ if (totalSupply >= minTokensToCreate && !isFueled) {
+ isFueled = true;
+ FuelingToDate(totalSupply);
+ }
+ }
+ }
}
contract DAO_Creator {
View
50 TokenCreation.sol
@@ -34,15 +34,13 @@ contract TokenCreationInterface {
uint public minTokensToCreate;
// True if the DAO reached its minimum fueling goal, false otherwise
bool public isFueled;
- // For DAO splits - if privateCreation is 0, then it is a public token
- // creation, otherwise only the address stored in privateCreation is
+ // For DAO splits - if parentDAO is 0, then it is a public token
+ // creation, otherwise only the address stored in parentDAO is
// allowed to create tokens
- address public privateCreation;
+ address public parentDAO;
// hold extra ether which has been sent after the DAO token
// creation rate has increased
ManagedAccount public extraBalance;
- // tracks the amount of wei given from each contributor (used for refund)
- mapping (address => uint256) weiGiven;
/// @dev Constructor setting the minimum fueling goal and the
/// end of the Token Creation
@@ -71,10 +69,6 @@ contract TokenCreationInterface {
/// not reach its minimum fueling goal
function refund();
- /// @return The divisor used to calculate the token creation rate during
- /// the creation phase
- function divisor() constant returns (uint divisor);
-
event FuelingToDate(uint value);
event CreatedToken(address indexed to, uint amount);
event Refund(address indexed to, uint value);
@@ -85,15 +79,14 @@ contract TokenCreation is TokenCreationInterface, Token {
function TokenCreation(
uint _minTokensToCreate,
uint _closingTime,
- address _privateCreation,
+ address _parentDAO,
string _tokenName,
string _tokenSymbol,
uint8 _decimalPlaces) {
closingTime = _closingTime;
minTokensToCreate = _minTokensToCreate;
- privateCreation = _privateCreation;
- extraBalance = new ManagedAccount(address(this), true);
+ parentDAO = _parentDAO;
name = _tokenName;
symbol = _tokenSymbol;
decimals = _decimalPlaces;
@@ -102,13 +95,11 @@ contract TokenCreation is TokenCreationInterface, Token {
function createTokenProxy(address _tokenHolder) returns (bool success) {
if (now < closingTime && msg.value > 0
- && (privateCreation == 0 || privateCreation == msg.sender)) {
+ && (parentDAO == 0 || parentDAO == msg.sender)) {
- uint token = (msg.value * 20) / divisor();
- extraBalance.call.value(msg.value - token)();
+ uint token = msg.value;
balances[_tokenHolder] += token;
totalSupply += token;
- weiGiven[_tokenHolder] += msg.value;
@pirapira
pirapira added a note Jun 9, 2016

weiGiven should be removed from TokenCreationInterface too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
CreatedToken(_tokenHolder, token);
if (totalSupply >= minTokensToCreate && !isFueled) {
isFueled = true;
@@ -120,33 +111,12 @@ contract TokenCreation is TokenCreationInterface, Token {
}
function refund() noEther {
- if (now > closingTime && !isFueled) {
- // Get extraBalance - will only succeed when called for the first time
- if (extraBalance.balance >= extraBalance.accumulatedInput())
- extraBalance.payOut(address(this), extraBalance.accumulatedInput());
-
- // Execute refund
- if (msg.sender.call.value(weiGiven[msg.sender])()) {
- Refund(msg.sender, weiGiven[msg.sender]);
+ if (now > closingTime && !isFueled && parentDAO == 0) {
+ if (msg.sender.call.value(balances[msg.sender])()) {
+ Refund(msg.sender, balances[msg.sender]);
totalSupply -= balances[msg.sender];
balances[msg.sender] = 0;
- weiGiven[msg.sender] = 0;
}
}
}
-
- function divisor() constant returns (uint divisor) {
- // The number of (base unit) tokens per wei is calculated
- // as `msg.value` * 20 / `divisor`
- // The fueling period starts with a 1:1 ratio
- if (closingTime - 2 weeks > now) {
- return 20;
- // Followed by 10 days with a daily creation rate increase of 5%
- } else if (closingTime - 4 days > now) {
- return (20 + (now - (closingTime - 2 weeks)) / (1 days));
- // The last 4 days there is a constant creation rate ratio of 1:1.5
- } else {
- return 30;
- }
- }
}
Something went wrong with that request. Please try again.