Skip to content

Fix reward distribution of split DAOs #69

Merged
merged 9 commits into from Apr 8, 2016
View
77 DAO.sol
@@ -33,9 +33,7 @@ contract DAOInterface {
uint minQuorumDivisor;
// The unix time of the last time quorum was reached on a proposal
uint lastTimeMinQuorumMet;
- // The total amount of wei received as reward that has not been sent to
- // the rewardAccount
- uint public rewards;
+
// Address of the service provider
address public serviceProvider;
// The whitelist: List of addresses the DAO is allowed to send money to
@@ -50,8 +48,16 @@ contract DAOInterface {
uint public totalRewardToken;
// The account used to manage the rewards which are to be distributed to the
- // DAO Token Holders of any DAO that holds Reward Tokens
+ // DAO Token Holders of this DAO
ManagedAccount public rewardAccount;
+
+ // The account used to manage the rewards which are to be distributed to
+ // any DAO that holds Reward Tokens
+ ManagedAccount public DAOrewardAccount;
+
+ // Amount of rewards (in wei) already paid out to a certain DAO
+ mapping (address => uint) public DAOpaidOut;
+
// Amount of rewards (in wei) already paid out to a certain address
mapping (address => uint) public paidOut;
// Map of addresses blocked during a vote (not allowed to transfer DAO
@@ -143,10 +149,6 @@ contract DAOInterface {
/// @return Whether the purchase was successful
function () returns (bool success);
- /// @dev Function used by the products of the DAO (e.g. Slocks) to send
- /// rewards to the DAO
- /// @return Whether the call to this function was successful or not
- function payDAO() returns(bool);
/// @dev This function is used by the service provider to send money back
/// to the DAO, it can also be used to receive payments that should not be
@@ -246,6 +248,12 @@ contract DAOInterface {
/// recipient being this DAO itself)
function changeProposalDeposit(uint _proposalDeposit) external;
+ /// @notice Move rewards from the DAORewards managed account
+ /// @param _toMembers If true rewards are move to the actual reward account
+ /// for the DAO. If not then it's moved to the DAO itself
+ /// @return Whether the call was successful
+ function retrieveDAOReward(bool _toMembers) external returns (bool _success);
+
/// @notice Get my portion of the reward that was sent to `rewardAccount`
/// @return Whether the call was successful
function getMyReward() returns(bool _success);
@@ -321,30 +329,27 @@ contract DAO is DAOInterface, Token, TokenSale {
daoCreator = _daoCreator;
proposalDeposit = 20 ether;
rewardAccount = new ManagedAccount(address(this));
+ DAOrewardAccount = new ManagedAccount(address(this));
if (address(rewardAccount) == 0)
throw;
+ if (address(DAOrewardAccount) == 0)
+ throw;
lastTimeMinQuorumMet = now;
minQuorumDivisor = 5; // sets the minimal quorum to 20%
proposals.length++; // avoids a proposal with ID 0 because it is used
allowedRecipients[address(this)] = true;
allowedRecipients[serviceProvider] = true;
- allowedRecipients[address(rewardAccount)] = true;
}
function () returns (bool success) {
if (now < closingTime + 40 days)
return buyTokenProxy(msg.sender);
else
- return payDAO();
+ return receiveEther();
}
- function payDAO() returns (bool) {
- rewards += msg.value;
- return true;
- }
-
function receiveEther() returns (bool) {
return true;
}
@@ -381,9 +386,6 @@ contract DAO is DAOInterface, Token, TokenSale {
throw;
}
- if (_recipient == address(rewardAccount) && _amount > rewards)
- throw;
-
if (now + _debatingPeriod < now) // prevents overflow
throw;
@@ -489,16 +491,8 @@ contract DAO is DAOInterface, Token, TokenSale {
p.proposalPassed = true;
_success = true;
lastTimeMinQuorumMet = now;
- if (p.recipient == address(rewardAccount)) {
- // This happens when multiple similar proposals are created and
- // both are passed at the same time.
- if (rewards < p.amount)
- throw;
- rewards -= p.amount;
- } else {
- rewardToken[address(this)] += p.amount;
- totalRewardToken += p.amount;
- }
+ rewardToken[address(this)] += p.amount;
+ totalRewardToken += p.amount;
} else if (quorum >= minQuorum(p.amount) && p.nay >= p.yea) {
if (!p.creator.send(p.proposalDeposit))
throw;
@@ -572,6 +566,7 @@ contract DAO is DAOInterface, Token, TokenSale {
// 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[address(p.splitData[0].newDAO)] += paidOut[msg.sender];
@@ -588,22 +583,36 @@ contract DAO is DAOInterface, Token, TokenSale {
//move all reward tokens
rewardToken[_newContract] += rewardToken[address(this)];
rewardToken[address(this)] = 0;
+ DAOpaidOut[_newContract] += DAOpaidOut[address(this)];
+ DAOpaidOut[address(this)] = 0;
}
+ function retrieveDAOReward(bool _toMembers) external noEther returns (bool _success) {
+ DAO dao = DAO(msg.sender);
+ uint reward =
+ (rewardToken[msg.sender] * DAOrewardAccount.accumulatedInput()) /
+ totalRewardToken - DAOpaidOut[msg.sender];
+ if(_toMembers) {
+ if (!DAOrewardAccount.payOut(dao.rewardAccount(), reward))
+ throw;
+ }
+ else {
+ if (!DAOrewardAccount.payOut(dao, reward))
+ throw;
+ }
+ DAOpaidOut[msg.sender] += reward;
+ return true;
+ }
+
function getMyReward() noEther returns (bool _success) {
return withdrawRewardFor(msg.sender);
}
function withdrawRewardFor(address _account) noEther internal returns (bool _success) {
- // The account's portion of Reward Tokens of this DAO
- uint portionOfTheReward =
- (balanceOf(_account) * rewardToken[address(this)]) /
- totalSupply + rewardToken[_account];
uint reward =
- (portionOfTheReward * rewardAccount.accumulatedInput()) /
- totalRewardToken - paidOut[_account];
+ (balanceOf(_account) * rewardAccount.accumulatedInput()) / totalSupply - paidOut[_account];
if (!rewardAccount.payOut(_account, reward))
throw;
paidOut[_account] += reward;
View
4 SampleOffer.sol
@@ -119,7 +119,7 @@ contract SampleOffer {
if (msg.value < deploymentReward)
throw;
if (promiseValid) {
- if (client.payDAO.value(msg.value)()) {
+ if (client.DAOrewardAccount().call.value(msg.value)()) {
return true;
} else {
throw;
@@ -136,7 +136,7 @@ contract SampleOffer {
// pay reward
function payReward() returns(bool) {
if (promiseValid) {
- if (client.payDAO.value(msg.value)()) {
+ if (client.DAOrewardAccount().call.value(msg.value)()) {
return true;
} else {
throw;
View
2 tests/scenarios/deploy/template.js
@@ -6,7 +6,7 @@ var _daoCreatorContract = creatorContract.new(
{
from: web3.eth.accounts[0],
data: '$creator_bin',
- gas: 3000000
+ gas: 4000000
}, function (e, contract){
if (e) {
console.log(e+" at DAOCreator creation!");
View
13 tests/scenarios/deposit/run.py
@@ -1,22 +1,15 @@
+from utils import calculate_bytecode
+
scenario_description = (
"Make a proposal to change the default proposal deposit, vote for it and "
"then assure that the DAO's proposal deposit did indeed change"
)
-def calculate_bytecode(new_deposit):
- """
- Create the bytecode for calling dao.changeProposalDeposit() as defined
- here:
- https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#examples
- """
- return "{0}{1:0{2}x}".format('0xe33734fd', new_deposit, 64)
-
-
def run(ctx):
ctx.assert_scenario_ran('fund')
- bytecode = calculate_bytecode(ctx.args.deposit_new_value)
+ bytecode = calculate_bytecode('0xe33734fd', ctx.args.deposit_new_value)
ctx.create_js_file(substitutions={
"dao_abi": ctx.dao_abi,
"dao_address": ctx.dao_addr,
View
5 tests/scenarios/rewards/run.py
@@ -1,3 +1,5 @@
+from utils import calculate_bytecode
+
scenario_description = (
" A kind soul donates to the DAO so the DAO has rewards for distribution. "
"Create a proposal to send the rewards to the RewardsAccount, vote and "
@@ -14,12 +16,13 @@ def calculate_reward(tokens, total_tokens, total_rewards):
def run(ctx):
ctx.assert_scenario_ran('proposal')
+ bytecode = calculate_bytecode('0xa1da2fb9', True)
ctx.create_js_file(substitutions={
"dao_abi": ctx.dao_abi,
"dao_address": ctx.dao_addr,
"total_rewards": ctx.args.rewards_total_amount,
"proposal_deposit": ctx.args.proposal_deposit,
- "transaction_bytecode": '0x0', # fallback function
+ "transaction_bytecode": bytecode,
"debating_period": ctx.args.proposal_debate_seconds,
"prop_id": ctx.next_proposal_id()
}
View
19 tests/scenarios/rewards/template.js
@@ -2,26 +2,27 @@ var dao = web3.eth.contract($dao_abi).at('$dao_address');
// some kind soul makes a donation to the DAO, so rewards get populated
console.log("Donating to DAO...");
-dao.payDAO.sendTransaction({
- from: eth.accounts[1],
- value: web3.toWei($total_rewards, "ether"),
- gas: 100000
+eth.sendTransaction({
+ from:eth.accounts[1],
+ to: dao.DAOrewardAccount(),
+ gas: 210000,
+ value: web3.toWei($total_rewards, "ether")
});
checkWork();
// create a new proposal for sending this whole donation to the rewardAccount
console.log("Creating proposal to send to rewardAccount...");
var tx_hash = null;
dao.newProposal.sendTransaction(
- dao.rewardAccount(),
- web3.toWei($total_rewards, "ether"),
- 'Send money to the reward account',
- '$transaction_bytecode', // bytecode, not needed here, calling the fallback function
+ '$dao_address',
+ 0,
+ 'Ask the DAO to retrieveDAOReward()',
+ '$transaction_bytecode',
$debating_period,
false,
{
from: proposalCreator,
- value: web3.toWei($proposal_deposit, "ether"),
+ value: web3.toWei($proposal_deposit + 1, "ether"),
gas: 1000000
}
, function (e, res) {
View
31 tests/utils.py
@@ -291,6 +291,37 @@ def edit_dao_source(contracts_dir, keep_limits, halve_minquorum):
return new_path
+def calculate_bytecode(function_hash, value):
+ """
+ Create the bytecode for calling function with `function_hash` and the
+ given argument value as defined here:
+ https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#examples
+
+ Parameters
+ ----------
+ function_hash : string
+ The first 4 bytes of the hash of the function signature.
+
+ value : anything
+ The value to encode. Encoding depends on the value's type
+
+ Returns
+ ----------
+ results : string
+ The encoded ABI for the function call with the given argument
+ """
+ value_type = type(value)
+ if value_type is bool:
+ value_type = int
+ value = 1 if value is True else 0
+
+ if value_type is int:
+ return "{0}{1:0{2}x}".format(function_hash, value, 64)
+ else:
+ print("Error: Invalid value type at 'calculate_bytecode()`")
+ sys.exit(1)
+
+
def available_scenarios():
dir = "scenarios"
return [name for name in os.listdir(dir)
Something went wrong with that request. Please try again.