diff --git a/ethereum/contracts/core/ValidationPools.sol b/ethereum/contracts/core/ValidationPools.sol index 6c51038..c947627 100644 --- a/ethereum/contracts/core/ValidationPools.sol +++ b/ethereum/contracts/core/ValidationPools.sol @@ -9,9 +9,8 @@ import "hardhat/console.sol"; struct ValidationPoolStake { uint id; bool inFavor; - uint256 amount; + uint amount; address sender; - bool fromMint; } struct ValidationPoolParams { @@ -30,7 +29,7 @@ struct ValidationPool { mapping(uint => ValidationPoolStake) stakes; uint stakeCount; ValidationPoolParams params; - uint256 fee; + uint fee; uint endTime; bool resolved; bool outcome; @@ -64,18 +63,16 @@ contract ValidationPools is Reputation, Forum { ValidationPool storage pool, address sender, uint256 amount, - bool inFavor, - bool fromMint + bool inFavor ) internal { require(block.timestamp <= pool.endTime, "Pool end time has passed"); - //_update(sender, address(this), amount); + // We don't call _update here; We defer that until evaluateOutcome. uint stakeIndex = pool.stakeCount++; ValidationPoolStake storage s = pool.stakes[stakeIndex]; s.sender = sender; s.inFavor = inFavor; s.amount = amount; s.id = stakeIndex; - s.fromMint = fromMint; } /// Accept reputation stakes toward a validation pool @@ -85,7 +82,7 @@ contract ValidationPools is Reputation, Forum { bool inFavor ) public { ValidationPool storage pool = validationPools[poolIndex]; - _stakeOnValidationPool(pool, msg.sender, amount, inFavor, false); + _stakeOnValidationPool(pool, msg.sender, amount, inFavor); } /// Accept reputation stakes toward a validation pool @@ -97,7 +94,7 @@ contract ValidationPools is Reputation, Forum { ) public { ValidationPool storage pool = validationPools[poolIndex]; _spendAllowance(owner, msg.sender, amount); - _stakeOnValidationPool(pool, owner, amount, inFavor, false); + _stakeOnValidationPool(pool, owner, amount, inFavor); } /// Accept fee to initiate a validation pool @@ -145,8 +142,8 @@ contract ValidationPools is Reputation, Forum { pool.minted = msg.value; // Here we assume a stakeForAuthor ratio of 0.5 // TODO: Make stakeForAuthor an adjustable parameter - _stakeOnValidationPool(pool, post.author, msg.value / 2, true, true); - _stakeOnValidationPool(pool, post.author, msg.value / 2, false, true); + // _stakeOnValidationPool(pool, post.author, msg.value / 2, true, true); + // _stakeOnValidationPool(pool, post.author, msg.value / 2, false, true); emit ValidationPoolInitiated(poolIndex); } @@ -155,8 +152,8 @@ contract ValidationPools is Reputation, Forum { ValidationPool storage pool = validationPools[poolIndex]; Post storage post = posts[pool.postIndex]; require(pool.resolved == false, "Pool is already resolved"); - uint256 stakedFor; - uint256 stakedAgainst; + uint stakedFor; + uint stakedAgainst; ValidationPoolStake storage s; for (uint i = 0; i < pool.stakeCount; i++) { s = pool.stakes[i]; @@ -166,6 +163,8 @@ contract ValidationPools is Reputation, Forum { stakedAgainst += s.amount; } } + stakedFor += pool.minted / 2; + stakedAgainst += pool.minted / 2; // Special case for early evaluation if dao.totalSupply has been staked require( block.timestamp > pool.endTime || @@ -195,13 +194,6 @@ contract ValidationPools is Reputation, Forum { return false; } - - // We are holding the REP minted on behalf of the author. - // We already registered their stakes. Now transfer the REP to the author, - // so that the stakes can be fulfilled. - _update(address(this), post.author, pool.minted); - // TODO: Transfer REP to the forum instead of to the author directly - // A tie is resolved in favor of the validation pool. // This is especially important so that the DAO's first pool can pass, // when no reputation has yet been minted. @@ -216,24 +208,20 @@ contract ValidationPools is Reputation, Forum { pool.resolved = true; pool.outcome = votePasses; emit ValidationPoolResolved(poolIndex, votePasses, true); + // Value of losing stakes should be distributed among winners, in proportion to their stakes - uint256 amountFromWinners = votePasses ? stakedFor : stakedAgainst; - uint256 amountFromLosers = votePasses ? stakedAgainst : stakedFor; // Only bindingPercent % should be redistributed // Stake senders should get (100-bindingPercent) % back - // We have allowances for each stake. Time to collect from the losing stakes. + uint amountFromWinners = votePasses ? stakedFor : stakedAgainst; uint totalRewards; uint totalAllocated; for (uint i = 0; i < pool.stakeCount; i++) { s = pool.stakes[i]; - uint bindingPercent = s.fromMint ? 100 : pool.params.bindingPercent; - bool redistributeLosingStakes = s.fromMint || - pool.params.redistributeLosingStakes; if (votePasses != s.inFavor) { // Losing stake // If this stake is from the minted fee, don't burn it - uint amount = (s.amount * bindingPercent) / 100; - if (redistributeLosingStakes) { + uint amount = (s.amount * pool.params.bindingPercent) / 100; + if (pool.params.redistributeLosingStakes) { _update(s.sender, address(this), amount); totalRewards += amount; } else { @@ -241,21 +229,35 @@ contract ValidationPools is Reputation, Forum { } } } + + if (votePasses) { + // If vote passes, reward the author as though they had staked the winnin portion of the VP initial stake + totalRewards += pool.minted / 2; + uint reward = ((((totalRewards * pool.minted) / 2) / + amountFromWinners) * pool.params.bindingPercent) / 100; + totalAllocated += reward; + _update(address(this), post.author, pool.minted / 2 + reward); + // TODO: Transfer REP to the forum instead of to the author directly + } else { + // If vote does not pass, divide the losing stake among the winners + totalRewards += pool.minted; + } + // Include the losign portion of the VP initial stake // Issue rewards to the winners for (uint i = 0; i < pool.stakeCount; i++) { s = pool.stakes[i]; - uint bindingPercent = s.fromMint ? 100 : pool.params.bindingPercent; - bool redistributeLosingStakes = s.fromMint || - pool.params.redistributeLosingStakes; - if (redistributeLosingStakes && votePasses == s.inFavor) { + if ( + pool.params.redistributeLosingStakes && votePasses == s.inFavor + ) { // Winning stake // If this stake is from the minted fee, always redistribute it to the winners - uint reward = (((amountFromLosers * s.amount) / - amountFromWinners) * bindingPercent) / 100; + uint reward = (((totalRewards * s.amount) / amountFromWinners) * + pool.params.bindingPercent) / 100; totalAllocated += reward; _update(address(this), s.sender, reward); } } + // Due to rounding, some reward may be left over. Let's give it to the author. uint remainder = totalRewards - totalAllocated; if (remainder > 0) { @@ -265,7 +267,7 @@ contract ValidationPools is Reputation, Forum { // Distribute fee proportionately among all reputation holders for (uint i = 0; i < memberCount; i++) { address member = members[i]; - uint256 share = (pool.fee * balanceOf(member)) / totalSupply(); + uint share = (pool.fee * balanceOf(member)) / totalSupply(); // TODO: For efficiency this could be modified to hold the funds for recipients to withdraw payable(member).transfer(share); } diff --git a/ethereum/test/Work1.js b/ethereum/test/Work1.js index 76c6de9..1ab09c5 100644 --- a/ethereum/test/Work1.js +++ b/ethereum/test/Work1.js @@ -219,7 +219,7 @@ describe('Work1', () => { expect(pool.fee).to.equal(WORK1_PRICE); expect(pool.sender).to.equal(work1.target); expect(pool.postIndex).to.equal(1); - expect(pool.stakeCount).to.equal(3); + expect(pool.stakeCount).to.equal(1); await time.increase(86401); await expect(dao.evaluateOutcome(1)).to.emit(dao, 'ValidationPoolResolved').withArgs(1, true, true); expect(await dao.balanceOf(account1)).to.equal(200);