From 3ff4a9a7b833950b178fd9179df531eff01d3003 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 2 Jul 2026 11:14:18 +0000 Subject: [PATCH] Fix RBTS timeout stake returns Co-authored-by: David Hawig --- .../RoundVotingEngineRbtsSettlementModule.sol | 26 ++++++++++- .../test/RoundVotingEngineBranches.t.sol | 45 +++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/packages/foundry/contracts/RoundVotingEngineRbtsSettlementModule.sol b/packages/foundry/contracts/RoundVotingEngineRbtsSettlementModule.sol index cec2aeaf..08bf6c4d 100644 --- a/packages/foundry/contracts/RoundVotingEngineRbtsSettlementModule.sol +++ b/packages/foundry/contracts/RoundVotingEngineRbtsSettlementModule.sol @@ -41,7 +41,7 @@ contract RoundVotingEngineRbtsSettlementModule is RoundVotingEngineStorage { if (readyAt == 0 || block.timestamp < uint256(readyAt) + RBTS_SETTLEMENT_SNAPSHOT_TIMEOUT) { revert RoundNotExpired(); } - _returnRbtsStakes(contentId, roundId); + _returnRevealedRbtsStakes(contentId, roundId); _completeRbtsSettlement(contentId, roundId, round, 0, 0, bytes32(0)); emit RbtsSettlementSnapshotTimedOut(contentId, roundId); return; @@ -143,6 +143,30 @@ contract RoundVotingEngineRbtsSettlementModule is RoundVotingEngineStorage { roundRbtsScoreSeed[contentId][roundId] = bytes32(0); } + function _returnRevealedRbtsStakes(uint256 contentId, uint256 roundId) internal { + bytes32[] storage commitKeys = roundCommitHashes[contentId][roundId]; + uint256 committedCount = commitKeys.length; + for (uint256 i = 0; i < committedCount;) { + bytes32 commitKey = commitKeys[i]; + RoundLib.Commit storage revealedCommit = commits[contentId][roundId][commitKey]; + if (revealedCommit.revealed) { + commitRbtsStakeReturned[contentId][roundId][commitKey] = revealedCommit.stakeAmount; + } + unchecked { + ++i; + } + } + + roundRbtsRewardWeight[contentId][roundId] = 0; + roundRbtsRewardClaimants[contentId][roundId] = 0; + roundRbtsParticipationWeight[contentId][roundId] = 0; + roundRbtsParticipationClaimants[contentId][roundId] = 0; + roundRbtsForfeitedPool[contentId][roundId] = 0; + roundRbtsForfeitClaimants[contentId][roundId] = 0; + roundRbtsMeanScoreBps[contentId][roundId] = 0; + roundRbtsScoreSeed[contentId][roundId] = bytes32(0); + } + function _scoreRbtsRewards(uint256 contentId, uint256 roundId, uint256 revealedCount, uint48 thresholdReachedAt) internal returns (uint256 rewardWeight, uint256 forfeitedPool, bytes32 scoreSeed, bool seedReady) diff --git a/packages/foundry/test/RoundVotingEngineBranches.t.sol b/packages/foundry/test/RoundVotingEngineBranches.t.sol index 8e06b410..b2dc2b1d 100644 --- a/packages/foundry/test/RoundVotingEngineBranches.t.sol +++ b/packages/foundry/test/RoundVotingEngineBranches.t.sol @@ -1516,6 +1516,51 @@ contract RoundVotingEngineBranchesTest is VotingTestBase { assertGt(round.settledAt, 0); } + function test_RbtsSettlementTimeoutReturnsRevealedStake() public { + (uint256 contentId, uint256 roundId) = _setupThreeVoterRound(true, true, false); + + vm.roll(block.number + 1); + engine.settleRound(contentId, roundId); + + RoundLib.Round memory pendingRound = RoundEngineReadHelpers.round(engine, contentId, roundId); + assertEq(uint256(pendingRound.state), uint256(RoundLib.RoundState.SettlementPending)); + uint48 readyAt = _roundClusterPayoutReadyAt(engine, contentId, roundId); + assertGt(readyAt, 0); + + IClusterPayoutOracle.PayoutWeight[] memory emptyWeights = new IClusterPayoutOracle.PayoutWeight[](0); + bytes32[][] memory emptyProofs = new bytes32[][](0); + vm.expectRevert(RoundVotingEngineRbtsSettlementModule.RoundNotExpired.selector); + engine.applyRbtsSettlementSnapshot(contentId, roundId, emptyWeights, emptyProofs); + + vm.warp(uint256(readyAt) + 14 days); + engine.applyRbtsSettlementSnapshot(contentId, roundId, emptyWeights, emptyProofs); + + RoundLib.Round memory settledRound = RoundEngineReadHelpers.round(engine, contentId, roundId); + assertEq(uint256(settledRound.state), uint256(RoundLib.RoundState.Settled)); + assertTrue(_roundRbtsScored(engine, contentId, roundId)); + assertEq(_roundRbtsRewardWeight(engine, contentId, roundId), 0); + + bytes32[] memory commitKeys = RoundEngineReadHelpers.commitKeys(engine, contentId, roundId); + for (uint256 i = 0; i < commitKeys.length; i++) { + assertEq(_commitRbtsStakeReturned(engine, contentId, roundId, commitKeys[i]), STAKE); + } + + uint256 voter1Before = lrepToken.balanceOf(voter1); + vm.prank(voter1); + rewardDistributor.claimReward(contentId, roundId); + assertEq(lrepToken.balanceOf(voter1) - voter1Before, STAKE); + + uint256 voter2Before = lrepToken.balanceOf(voter2); + vm.prank(voter2); + rewardDistributor.claimReward(contentId, roundId); + assertEq(lrepToken.balanceOf(voter2) - voter2Before, STAKE); + + uint256 voter3Before = lrepToken.balanceOf(voter3); + vm.prank(voter3); + rewardDistributor.claimReward(contentId, roundId); + assertEq(lrepToken.balanceOf(voter3) - voter3Before, STAKE); + } + function test_RbtsSettlementRejectsZeroEffectiveWeightLeaf() public { (uint256 contentId, uint256 roundId) = _setupThreeVoterRound(true, true, false);