diff --git a/packages/nouns-contracts/contracts/NounsAuctionHouseV2.sol b/packages/nouns-contracts/contracts/NounsAuctionHouseV2.sol index f7b08596c..3297ee361 100644 --- a/packages/nouns-contracts/contracts/NounsAuctionHouseV2.sol +++ b/packages/nouns-contracts/contracts/NounsAuctionHouseV2.sol @@ -441,6 +441,40 @@ contract NounsAuctionHouseV2 is require(auctionCount == actualCount, 'Not enough history'); } + function getSettlementsFromIdtoTimestamp( + uint256 startId, + uint256 endTimestamp, + bool skipEmptyValues + ) public view returns (Settlement[] memory settlements) { + uint256 maxId = auctionStorage.nounId; + settlements = new Settlement[](maxId - startId + 1); + uint256 actualCount = 0; + SettlementState memory settlementState; + for (uint256 id = startId; id <= maxId; ++id) { + settlementState = settlementHistory[id]; + + if (skipEmptyValues && settlementState.blockTimestamp <= 1) continue; + + if (settlementState.blockTimestamp > endTimestamp) break; + + settlements[actualCount] = Settlement({ + blockTimestamp: settlementState.blockTimestamp, + amount: uint64PriceToUint256(settlementState.amount), + winner: settlementState.winner, + nounId: id, + clientId: settlementState.clientId + }); + ++actualCount; + } + + if (settlements.length > actualCount) { + // this assembly trims the settlements array, getting rid of unused cells + assembly { + mstore(settlements, actualCount) + } + } + } + /** * @notice Get a range of past auction settlements. * @dev Returns prices in chronological order, as opposed to `getSettlements(count)` which returns prices in reverse order. @@ -476,7 +510,7 @@ contract NounsAuctionHouseV2 is } if (settlements.length > actualCount) { - // this assembly trims the observations array, getting rid of unused cells + // this assembly trims the settlements array, getting rid of unused cells assembly { mstore(settlements, actualCount) } diff --git a/packages/nouns-contracts/contracts/Rewards.sol b/packages/nouns-contracts/contracts/Rewards.sol index e00daaeff..15dbe4d59 100644 --- a/packages/nouns-contracts/contracts/Rewards.sol +++ b/packages/nouns-contracts/contracts/Rewards.sol @@ -67,12 +67,6 @@ contract Rewards is ERC721('NounsClientIncentives', 'NounsClientIncentives'), Ow params = rewardParams; } - struct Temp { - uint256 numEligibleVotes; - uint256 rewardPerProposal; - uint256 rewardPerVote; - } - function setParams(RewardParams calldata newParams) public onlyOwner { params = newParams; } @@ -99,19 +93,20 @@ contract Rewards is ERC721('NounsClientIncentives', 'NounsClientIncentives'), Ow } } + struct Temp { + uint256 numEligibleVotes; + uint256 rewardPerProposal; + uint256 rewardPerVote; + uint256 proposalRewardForPeriod; + } + /** * @param lastProposalId id of the last proposal to include in the rewards distribution. all proposals up to and * including this id must have ended voting. - * @param lastAuctionedNounId the noun id that was auctioned when proposal with id `lastProposalId` was created. - * The auction revenue up to this noun id are included in the bounty. * @param votingClientIds array of client ids that were used to vote on of all eligible the eligible proposals in * this rewards distribution */ - function updateRewardsForProposalWritingAndVoting( - uint32 lastProposalId, - uint256 lastAuctionedNounId, - uint32[] calldata votingClientIds - ) public { + function updateRewardsForProposalWritingAndVoting(uint32 lastProposalId, uint32[] calldata votingClientIds) public { require(lastProposalId <= nounsDAO.proposalCount(), 'bad lastProposalId'); require(lastProposalId >= nextProposalIdToReward, 'bad lastProposalId'); @@ -124,20 +119,19 @@ contract Rewards is ERC721('NounsClientIncentives', 'NounsClientIncentives'), Ow NounsDAOV3Types.ProposalForRewards memory lastProposal = proposals[proposals.length - 1]; - uint256 auctionRevenue = getAuctionRevenueBetweenNouns({ + (uint256 auctionRevenue, uint256 lastAuctionId) = getAuctionRevenue({ firstNounId: nextProposalRewardFirstAuctionId, - oneAfterLastNounId: lastAuctionedNounId, endTimestamp: lastProposal.creationTimestamp }); - nextProposalRewardFirstAuctionId = lastAuctionedNounId; + nextProposalRewardFirstAuctionId = lastAuctionId + 1; require(auctionRevenue > 0, 'auctionRevenue must be > 0'); - uint256 proposalRewardForPeriod = (auctionRevenue * params.proposalRewardBps) / 10_000; - uint256 votingRewardForPeriod = (auctionRevenue * params.votingRewardBps) / 10_000; - Temp memory t; + t.proposalRewardForPeriod = (auctionRevenue * params.proposalRewardBps) / 10_000; + uint256 votingRewardForPeriod = (auctionRevenue * params.votingRewardBps) / 10_000; + uint16 proposalEligibilityQuorumBps_ = params.proposalEligibilityQuorumBps; //// First loop over the proposals: @@ -181,7 +175,7 @@ contract Rewards is ERC721('NounsClientIncentives', 'NounsClientIncentives'), Ow lastProposalRewardsUpdate = lastProposal.creationTimestamp; // Calculate the reward per proposal and per vote - t.rewardPerProposal = proposalRewardForPeriod / numEligibleProposals; + t.rewardPerProposal = t.proposalRewardForPeriod / numEligibleProposals; t.rewardPerVote = votingRewardForPeriod / t.numEligibleVotes; //// Second loop over the proposals: @@ -234,24 +228,21 @@ contract Rewards is ERC721('NounsClientIncentives', 'NounsClientIncentives'), Ow uint32[] votingClientIds; } - function getAuctionRevenueBetweenNouns( + function getAuctionRevenue( uint256 firstNounId, - uint256 oneAfterLastNounId, uint256 endTimestamp - ) internal view returns (uint256) { - INounsAuctionHouseV2.Settlement[] memory s = auctionHouse.getSettlements( + ) internal view returns (uint256 sumRevenue, uint256 lastAuctionId) { + INounsAuctionHouseV2.Settlement[] memory s = auctionHouse.getSettlementsFromIdtoTimestamp( firstNounId, - oneAfterLastNounId + 1, + endTimestamp, true ); - require(s[s.length - 2].blockTimestamp <= endTimestamp, 'second to last auction must be before end ts'); - require(s[s.length - 1].blockTimestamp > endTimestamp, 'last auction must be after end ts'); - - return sumAuctionsExcludingLast(s); + sumRevenue = sumAuctions(s); + lastAuctionId = s[s.length - 1].nounId; } - function sumAuctionsExcludingLast(INounsAuctionHouseV2.Settlement[] memory s) internal pure returns (uint256 sum) { - for (uint256 i = 0; i < s.length - 1; ++i) { + function sumAuctions(INounsAuctionHouseV2.Settlement[] memory s) internal pure returns (uint256 sum) { + for (uint256 i = 0; i < s.length; ++i) { sum += s[i].amount; } } diff --git a/packages/nouns-contracts/contracts/interfaces/INounsAuctionHouseV2.sol b/packages/nouns-contracts/contracts/interfaces/INounsAuctionHouseV2.sol index 43e70272a..e5e426c6f 100644 --- a/packages/nouns-contracts/contracts/interfaces/INounsAuctionHouseV2.sol +++ b/packages/nouns-contracts/contracts/interfaces/INounsAuctionHouseV2.sol @@ -124,6 +124,12 @@ interface INounsAuctionHouseV2 { bool skipEmptyValues ) external view returns (Settlement[] memory settlements); + function getSettlementsFromIdtoTimestamp( + uint256 startId, + uint256 endTimestamp, + bool skipEmptyValues + ) external view returns (Settlement[] memory settlements); + function warmUpSettlementState(uint256[] calldata nounIds) external; function duration() external view returns (uint256); diff --git a/packages/nouns-contracts/test/foundry/rewards/ProposalRewards.t.sol b/packages/nouns-contracts/test/foundry/rewards/ProposalRewards.t.sol index d813d40ae..51f511f79 100644 --- a/packages/nouns-contracts/test/foundry/rewards/ProposalRewards.t.sol +++ b/packages/nouns-contracts/test/foundry/rewards/ProposalRewards.t.sol @@ -149,12 +149,11 @@ contract ProposalRewardsTest is BaseProposalRewardsTest { vm.warp(block.timestamp + 2 weeks + 1); uint32 proposalId = proposeVoteAndEndVotingPeriod(clientId1); - uint256 lastNounId = settleAuction(); + settleAuction(); votingClientIds = [0]; vm.expectRevert('auctionRevenue must be > 0'); rewards.updateRewardsForProposalWritingAndVoting({ lastProposalId: proposalId, - lastAuctionedNounId: lastNounId, votingClientIds: votingClientIds }); } @@ -165,12 +164,11 @@ contract ProposalRewardsTest is BaseProposalRewardsTest { vm.warp(block.timestamp + 2 weeks + 1); uint32 proposalId = proposeAndVote(clientId1); - uint256 lastNounId = settleAuction(); + settleAuction(); votingClientIds = [0]; vm.expectRevert('all proposals must be done with voting'); rewards.updateRewardsForProposalWritingAndVoting({ lastProposalId: proposalId, - lastAuctionedNounId: lastNounId, votingClientIds: votingClientIds }); } @@ -184,11 +182,10 @@ contract ProposalRewardsTest is BaseProposalRewardsTest { vm.warp(startTimestamp + 2 weeks + 1); uint32 proposalId = proposeVoteAndEndVotingPeriod(clientId1); - uint256 lastNounId = settleAuction(); + settleAuction(); votingClientIds = [0]; rewards.updateRewardsForProposalWritingAndVoting({ lastProposalId: proposalId, - lastAuctionedNounId: lastNounId, votingClientIds: votingClientIds }); @@ -205,11 +202,10 @@ contract ProposalRewardsTest is BaseProposalRewardsTest { propose(bidder2, address(1), 1 ether, '', '', 'my proposal', clientId1); uint32 proposalId = proposeVoteAndEndVotingPeriod(clientId2); - uint256 lastNounId = settleAuction(); + settleAuction(); votingClientIds = [0]; rewards.updateRewardsForProposalWritingAndVoting({ lastProposalId: proposalId, - lastAuctionedNounId: lastNounId, votingClientIds: votingClientIds }); @@ -227,11 +223,10 @@ contract ProposalRewardsTest is BaseProposalRewardsTest { proposeVoteAndEndVotingPeriod(clientId1); uint32 proposalId = proposeVoteAndEndVotingPeriod(clientId2); - uint256 lastNounId = settleAuction(); + settleAuction(); votingClientIds = [0]; rewards.updateRewardsForProposalWritingAndVoting({ lastProposalId: proposalId, - lastAuctionedNounId: lastNounId, votingClientIds: votingClientIds }); @@ -249,13 +244,12 @@ contract ProposalRewardsTest is BaseProposalRewardsTest { uint32 proposalId = proposeVoteAndEndVotingPeriod(clientId1); - uint256 lastNounId = settleAuction(); + settleAuction(); votingClientIds = [0]; vm.expectRevert('not enough time passed'); rewards.updateRewardsForProposalWritingAndVoting({ lastProposalId: proposalId, - lastAuctionedNounId: lastNounId, votingClientIds: votingClientIds }); } @@ -283,12 +277,11 @@ contract ProposalRewardsTest is BaseProposalRewardsTest { uint32 proposalId = proposeVoteAndEndVotingPeriod(clientId1); - uint256 lastNounId = settleAuction(); + settleAuction(); votingClientIds = [0]; rewards.updateRewardsForProposalWritingAndVoting({ lastProposalId: proposalId, - lastAuctionedNounId: lastNounId, votingClientIds: votingClientIds }); assertEq(rewards.clientBalances(clientId1), 0.15 ether); // 15 eth * 1% @@ -325,7 +318,6 @@ contract ProposalRewardsEligibilityTest is BaseProposalRewardsTest { vm.expectRevert('at least one eligible proposal'); rewards.updateRewardsForProposalWritingAndVoting({ lastProposalId: proposalId, - lastAuctionedNounId: lastNounId, votingClientIds: votingClientIds }); } @@ -337,7 +329,6 @@ contract ProposalRewardsEligibilityTest is BaseProposalRewardsTest { rewards.updateRewardsForProposalWritingAndVoting({ lastProposalId: proposalId, - lastAuctionedNounId: lastNounId, votingClientIds: votingClientIds }); } @@ -357,11 +348,10 @@ contract AfterOneSuccessfulRewardsDistributionTest is BaseProposalRewardsTest { lastProposalCreationTimestamp = block.timestamp; uint32 proposalId = proposeVoteAndEndVotingPeriod(clientId1); - uint256 lastNounId = settleAuction(); + settleAuction(); votingClientIds = [0]; rewards.updateRewardsForProposalWritingAndVoting({ lastProposalId: proposalId, - lastAuctionedNounId: lastNounId, votingClientIds: votingClientIds }); @@ -374,11 +364,10 @@ contract AfterOneSuccessfulRewardsDistributionTest is BaseProposalRewardsTest { vm.warp(lastProposalCreationTimestamp + 2 weeks - 10); uint32 proposalId = proposeVoteAndEndVotingPeriod(clientId1); - uint256 lastNounId = settleAuction(); + settleAuction(); vm.expectRevert('not enough time passed'); rewards.updateRewardsForProposalWritingAndVoting({ lastProposalId: proposalId, - lastAuctionedNounId: lastNounId, votingClientIds: votingClientIds }); } @@ -389,10 +378,9 @@ contract AfterOneSuccessfulRewardsDistributionTest is BaseProposalRewardsTest { vm.warp(lastProposalCreationTimestamp + 2 weeks + 10); uint32 proposalId = proposeVoteAndEndVotingPeriod(clientId1); - uint256 lastNounId = settleAuction(); + settleAuction(); rewards.updateRewardsForProposalWritingAndVoting({ lastProposalId: proposalId, - lastAuctionedNounId: lastNounId, votingClientIds: votingClientIds }); @@ -418,11 +406,10 @@ contract VotesRewardsTest is BaseProposalRewardsTest { vote(bidder1, proposalId, 1, 'i support', clientId1); mineBlocks(VOTING_PERIOD); - uint256 lastNounId = settleAuction(); + settleAuction(); votingClientIds = [clientId1]; rewards.updateRewardsForProposalWritingAndVoting({ lastProposalId: proposalId, - lastAuctionedNounId: lastNounId, votingClientIds: votingClientIds }); @@ -440,11 +427,10 @@ contract VotesRewardsTest is BaseProposalRewardsTest { mineBlocks(VOTING_PERIOD); - uint256 lastNounId = settleAuction(); + settleAuction(); votingClientIds = [clientId1, clientId2]; rewards.updateRewardsForProposalWritingAndVoting({ lastProposalId: proposalId, - lastAuctionedNounId: lastNounId, votingClientIds: votingClientIds }); diff --git a/packages/nouns-contracts/test/foundry/rewards/Rewards.t.sol b/packages/nouns-contracts/test/foundry/rewards/Rewards.t.sol index e68e9d544..58f61c31e 100644 --- a/packages/nouns-contracts/test/foundry/rewards/Rewards.t.sol +++ b/packages/nouns-contracts/test/foundry/rewards/Rewards.t.sol @@ -203,7 +203,6 @@ contract ProposalRewardsHappyFlow is RewardsBaseTest { rewards.updateRewardsForProposalWritingAndVoting({ lastProposalId: uint32(proposalId), - lastAuctionedNounId: nounOnAuctionWhenLastProposalWasCreated, votingClientIds: clientIds }); }