diff --git a/script/deploy/DeployYieldDistributor.s.sol b/script/deploy/DeployYieldDistributor.s.sol index 7f85986..1e240d0 100644 --- a/script/deploy/DeployYieldDistributor.s.sol +++ b/script/deploy/DeployYieldDistributor.s.sol @@ -18,6 +18,7 @@ contract DeployYieldDistributor is Script { uint256 _maxPoints = stdJson.readUint(config_data, "._maxPoints"); uint256 _precision = stdJson.readUint(config_data, "._precision"); uint256 _lastClaimedBlockNumber = stdJson.readUint(config_data, "._lastClaimedBlockNumber"); + uint256 _yieldFixedSplitDivisor = stdJson.readUint(config_data, "._yieldFixedSplitDivisor"); address _owner = stdJson.readAddress(config_data, "._owner"); bytes projectsRaw = stdJson.parseRaw(config_data, "._projects"); address[] projects = abi.decode(projectsRaw, (address[])); @@ -28,6 +29,7 @@ contract DeployYieldDistributor is Script { _minRequiredVotingPower, _maxPoints, _cycleLength, + _yieldFixedSplitDivisor, _lastClaimedBlockNumber, projects ); diff --git a/script/deploy/config/deploy.json b/script/deploy/config/deploy.json index d298aa4..7ed5332 100644 --- a/script/deploy/config/deploy.json +++ b/script/deploy/config/deploy.json @@ -18,7 +18,8 @@ "_blocktime": 5, "_maxPoints": 10000, "_cycleLength": 518400, - "_minRequiredVotingPower":1728000000000000000000000, + "_minRequiredVotingPower": 1728000000000000000000000, "_lastClaimedBlockNumber": 0, - "_precision": 1000000000000000000 + "_precision": 1000000000000000000, + "_yieldFixedSplitDivisor": 2 } \ No newline at end of file diff --git a/src/YieldDistributor.sol b/src/YieldDistributor.sol index da03c22..9dda133 100644 --- a/src/YieldDistributor.sol +++ b/src/YieldDistributor.sol @@ -75,6 +75,8 @@ contract YieldDistributor is OwnableUpgradeable { mapping(address => uint256) public accountLastVoted; // @notice The voting power allocated to projects by voters in the current cycle mapping(address => uint256[]) voterDistributions; + // @notice How much of the yield is divided equally among projects + uint256 public yieldFixedSplitDivisor; /// @custom:oz-upgrades-unsafe-allow constructor constructor() { @@ -87,6 +89,7 @@ contract YieldDistributor is OwnableUpgradeable { uint256 _minRequiredVotingPower, uint256 _maxPoints, uint256 _cycleLength, + uint256 _yieldFixedSplitDivisor, uint256 _lastClaimedBlockNumber, address[] memory _projects ) public initializer { @@ -97,6 +100,7 @@ contract YieldDistributor is OwnableUpgradeable { minRequiredVotingPower = _minRequiredVotingPower; maxPoints = _maxPoints; cycleLength = _cycleLength; + yieldFixedSplitDivisor = _yieldFixedSplitDivisor; lastClaimedBlockNumber = _lastClaimedBlockNumber; projectDistributions = new uint256[](_projects.length); @@ -182,10 +186,11 @@ contract YieldDistributor is OwnableUpgradeable { * @return bytes Calldata used by the resolver to distribute the yield */ function resolveYieldDistribution() public view returns (bool, bytes memory) { + uint256 _available_yield = BREAD.balanceOf(address(this)) + BREAD.yieldAccrued(); if ( currentVotes == 0 // No votes were cast || block.number < lastClaimedBlockNumber + cycleLength // Already claimed this cycle - || BREAD.balanceOf(address(this)) + BREAD.yieldAccrued() < projects.length // Yield is insufficient + || _available_yield / yieldFixedSplitDivisor < projects.length // Yield is insufficient ) { return (false, new bytes(0)); } else { @@ -202,18 +207,19 @@ contract YieldDistributor is OwnableUpgradeable { BREAD.claimYield(BREAD.yieldAccrued(), address(this)); lastClaimedBlockNumber = block.number; - - uint256 _halfYield = BREAD.balanceOf(address(this)) / 2; - uint256 _baseSplit = _halfYield / projects.length; + uint256 balance = BREAD.balanceOf(address(this)); + uint256 _fixedYield = balance / yieldFixedSplitDivisor; + uint256 _baseSplit = _fixedYield / projects.length; + uint256 _votedYield = balance - _fixedYield; for (uint256 i; i < projects.length; ++i) { - uint256 _votedSplit = ((projectDistributions[i] * _halfYield * PRECISION) / currentVotes) / PRECISION; + uint256 _votedSplit = ((projectDistributions[i] * _votedYield * PRECISION) / currentVotes) / PRECISION; BREAD.transfer(projects[i], _votedSplit + _baseSplit); } _updateBreadchainProjects(); - emit YieldDistributed(_halfYield * 2, currentVotes, projectDistributions); + emit YieldDistributed(balance, currentVotes, projectDistributions); delete currentVotes; projectDistributions = new uint256[](projects.length); @@ -376,4 +382,14 @@ contract YieldDistributor is OwnableUpgradeable { cycleLength = _cycleLength; } + + /** + * @notice Set a new fixed split for the yield distribution + * @param _yieldFixedSplitDivisor New fixed split for the yield distribution + */ + function setyieldFixedSplitDivisor(uint256 _yieldFixedSplitDivisor) public onlyOwner { + if (_yieldFixedSplitDivisor == 0) revert MustBeGreaterThanZero(); + + yieldFixedSplitDivisor = _yieldFixedSplitDivisor; + } } diff --git a/test/YieldDistributor.t.sol b/test/YieldDistributor.t.sol index 47b9b99..e07f1a7 100644 --- a/test/YieldDistributor.t.sol +++ b/test/YieldDistributor.t.sol @@ -41,6 +41,7 @@ contract YieldDistributorTest is Test { uint256 _cycleLength = stdJson.readUint(config_data, "._cycleLength"); uint256 _minHoldingDuration = stdJson.readUint(config_data, "._minHoldingDuration"); uint256 _lastClaimedBlockNumber = stdJson.readUint(config_data, "._lastClaimedBlockNumber"); + uint256 _yieldFixedSplitDivisor = stdJson.readUint(config_data, "._yieldFixedSplitDivisor"); Bread public bread = Bread(address(_bread)); uint256 minHoldingDurationInBlocks = _minHoldingDuration / _blocktime; @@ -62,6 +63,7 @@ contract YieldDistributorTest is Test { _minRequiredVotingPower, _maxPoints, _cycleLength, + _yieldFixedSplitDivisor, _lastClaimedBlockNumber, projects1 ); @@ -79,6 +81,7 @@ contract YieldDistributorTest is Test { _minRequiredVotingPower, _maxPoints, _cycleLength, + _yieldFixedSplitDivisor, _lastClaimedBlockNumber, projects2 ); @@ -136,6 +139,41 @@ contract YieldDistributorTest is Test { assertGt(bread_bal_after, yieldAccrued - marginOfError); } + function test_fixed_yield_split() public { + // Getting the balance of the project before the distribution + uint256 bread_bal_before = bread.balanceOf(address(this)); + assertEq(bread_bal_before, 0); + // Getting the amount of yield to be distributed + uint256 yieldAccrued = bread.yieldAccrued(); + + // Setting up a voter + address account = address(0x1234567890123456789012345678901234567890); + address[] memory accounts = new address[](1); + accounts[0] = account; + setUpAccountsForVoting(accounts); + + // Setting up for a cycle + setUpForCycle(yieldDistributor2); + address owner = yieldDistributor2.owner(); + vm.prank(owner); + yieldDistributor2.setyieldFixedSplitDivisor(3); + + // Casting vote and distributing yield + uint256 vote = 50; + uint256 vote2 = 50; + percentages.push(vote); + percentages.push(vote2); + vm.prank(account); + yieldDistributor2.castVote(percentages); + yieldDistributor2.distributeYield(); + uint256 fixedSplit = yieldAccrued / _yieldFixedSplitDivisor; + uint256 votedSplit = yieldAccrued - fixedSplit; + uint256 projectsLength = yieldDistributor2.getProjectsLength(); + // Getting the balance of the project after the distribution and checking if it similiar to the yield accrued (there may be rounding issues) + uint256 bread_bal_after = bread.balanceOf(address(secondProject)); + assertGt(bread_bal_after, ((fixedSplit + votedSplit) / projectsLength) - marginOfError); + } + function test_simple_recast_vote() public { // Getting the balance of the project before the distribution uint256 bread_bal_before = bread.balanceOf(address(this)); diff --git a/test/test_deploy.json b/test/test_deploy.json index cd76b0c..9a3bf22 100644 --- a/test/test_deploy.json +++ b/test/test_deploy.json @@ -21,5 +21,6 @@ "_minRequiredVotingPower": 10000000000000000000, "_minVotingAmount": 10000000000000000000, "_minHoldingDuration": 864000, - "_precision": 1000000000000000000 + "_precision": 1000000000000000000, + "_yieldFixedSplitDivisor": 2 } \ No newline at end of file