Stakers unfairly loose out on bonus in cases where registry state jumps directly from NEW_DEPLOYMENT to PRODUCTION.
contract BonusLossTest is BaseConfidencePoolTest {
function getBonusSharePercentages(uint256 bonus) internal view returns (uint256) {
uint256 bonusTotal = pool.snapshotTotalBonus();
return Math.mulDiv(bonus, 100, bonusTotal);
}
function _passThroughAttackRequested() internal {
attackRegistry.setAgreementState(IAttackRegistry.ContractState.ATTACK_REQUESTED);
pool.pokeRiskWindow();
}
function _passThroughDirectPromotion() internal {
attackRegistry.setAgreementState(IAttackRegistry.ContractState.PRODUCTION);
pool.pokeRiskWindow();
}
function testUnfairSweepWhenDirectRegistryPromotion() external {
_stake(alice, 1000 * ONE);
uint256 aliceStake = pool.eligibleStake(alice);
_stake(bob, 330 * ONE);
uint256 bobStake = pool.eligibleStake(bob);
_stake(dave, 520 * ONE);
uint256 daveStake = pool.eligibleStake(dave);
_contributeBonus(alice, 1000 * ONE);
_contributeBonus(bob, 350 * ONE);
_contributeBonus(dave, 20 * ONE);
* GOES INTO PRODUCTION DIRECTLY FROM NEW_DEPLOYMENT
* IAttackRegistry.registerDeployment(agreement, deployer);
* IAttackRegistry.goToProduction(agreement);
*
*/
_passThroughDirectPromotion();
vm.prank(moderator);
pool.flagOutcome(PoolStates.Outcome.SURVIVED, false, address(0));
console.log("TOTAL BONUS: ", pool.snapshotTotalBonus());
vm.prank(alice);
pool.claimSurvived();
uint256 aliceBonus = token.balanceOf(alice) - aliceStake;
console.log("alice bonus & balance: ", aliceBonus, token.balanceOf(alice));
vm.prank(bob);
pool.claimSurvived();
uint256 bobBonus = token.balanceOf(bob) - bobStake;
console.log("bob bonus & balance: ", bobBonus, token.balanceOf(bob));
vm.prank(dave);
pool.claimSurvived();
uint256 daveBonus = token.balanceOf(dave) - daveStake;
console.log("dave bonus & balance: ", daveBonus, token.balanceOf(dave));
console.log("CLAIMED BONUS", pool.claimedBonus());
console.log("recoveryBalance", token.balanceOf(pool.recoveryAddress()));
assertEq(0, pool.claimedBonus());
assertEq(token.balanceOf(address(pool)), pool.snapshotTotalBonus());
console.log("bonusSweepable", token.balanceOf(address(pool)));
pool.sweepUnclaimedBonus();
console.log("SweptBonusToRecovery", token.balanceOf(pool.recoveryAddress()));
assertEq(token.balanceOf(pool.recoveryAddress()), pool.snapshotTotalBonus());
}
}
+uint256 public stateCache = uint256(_observePoolState);
+function _observeStatedifference() internal returns (uint256) {
+ uint256 state = uint256(_observePoolState());
+ return stateCache -= state;
+}
+function _bonusShare(address u, uint256 userEligible) internal view returns (uint256) {
+ if (snapshotTotalBonus == 0) return 0;
+ // No observable risk → no bonus (see contract natspec).
- if (riskWindowStart == 0) return 0;
+ if (_observeStatedifference() > 3) return 0;
+ uint256 T = outcomeFlaggedAt;