From bdf483225f96c31c3c5ade7488452c9c16855bbc Mon Sep 17 00:00:00 2001 From: gallo Date: Mon, 7 Oct 2024 09:52:40 +0200 Subject: [PATCH 01/22] chore: notes for V4 fixes --- src/UniV4Donations.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/UniV4Donations.sol b/src/UniV4Donations.sol index 654aa01..4e223bf 100644 --- a/src/UniV4Donations.sol +++ b/src/UniV4Donations.sol @@ -85,6 +85,8 @@ contract UniV4Donations is BribeInitiative, BaseHook { } function _donateToPool() internal returns (uint256) { + /// @audit TODO: Need to use storage value here I think + /// TODO: Test and fix release speed, which looks off Vesting memory _vesting = _restartVesting(uint240(governance.claimForInitiative(address(this)))); uint256 amount = (_vesting.amount * (block.timestamp - vestingEpochStart()) / VESTING_EPOCH_DURATION) - _vesting.released; From 175d782d4b1e984c6d4b83e1b8dea8645e051910 Mon Sep 17 00:00:00 2001 From: gallo Date: Mon, 7 Oct 2024 10:09:45 +0200 Subject: [PATCH 02/22] feat: low gas protected --- src/Governance.sol | 21 ++++++++++++------ src/utils/SafeCallMinGas.sol | 42 ++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 src/utils/SafeCallMinGas.sol diff --git a/src/Governance.sol b/src/Governance.sol index 2f361b3..d60caf0 100644 --- a/src/Governance.sol +++ b/src/Governance.sol @@ -15,6 +15,7 @@ import {UserProxyFactory} from "./UserProxyFactory.sol"; import {add, max} from "./utils/Math.sol"; import {Multicall} from "./utils/Multicall.sol"; import {WAD, PermitParams} from "./utils/Types.sol"; +import {safeCallWithMinGas} from "./utils/SafeCallMinGas.sol"; /// @title Governance: Modular Initiative based Governance contract Governance is Multicall, UserProxyFactory, ReentrancyGuard, IGovernance { @@ -323,7 +324,9 @@ contract Governance is Multicall, UserProxyFactory, ReentrancyGuard, IGovernance emit RegisterInitiative(_initiative, msg.sender, currentEpoch); - try IInitiative(_initiative).onRegisterInitiative(currentEpoch) {} catch {} + // try IInitiative(_initiative).onRegisterInitiative(currentEpoch) {} catch {} + // Replaces try / catch | Enforces sufficient gas is passed + safeCallWithMinGas(_initiative, 350_000, 0, abi.encodeCall(IInitiative.onRegisterInitiative, (currentEpoch))); } /// @inheritdoc IGovernance @@ -369,7 +372,9 @@ contract Governance is Multicall, UserProxyFactory, ReentrancyGuard, IGovernance emit UnregisterInitiative(_initiative, currentEpoch); - try IInitiative(_initiative).onUnregisterInitiative(currentEpoch) {} catch {} + // try IInitiative(_initiative).onUnregisterInitiative(currentEpoch) {} catch {} + // Replaces try / catch | Enforces sufficient gas is passed + safeCallWithMinGas(_initiative, 350_000, 0, abi.encodeCall(IInitiative.onUnregisterInitiative, (currentEpoch))); } /// @inheritdoc IGovernance @@ -477,9 +482,11 @@ contract Governance is Multicall, UserProxyFactory, ReentrancyGuard, IGovernance emit AllocateLQTY(msg.sender, initiative, deltaLQTYVotes, deltaLQTYVetos, currentEpoch); - try IInitiative(initiative).onAfterAllocateLQTY( - currentEpoch, msg.sender, allocation.voteLQTY, allocation.vetoLQTY - ) {} catch {} + // try IInitiative(initiative).onAfterAllocateLQTY( + // currentEpoch, msg.sender, allocation.voteLQTY, allocation.vetoLQTY + // ) {} catch {} + // Replaces try / catch | Enforces sufficient gas is passed + safeCallWithMinGas(initiative, 350_000, 0, abi.encodeCall(IInitiative.onAfterAllocateLQTY, (currentEpoch, msg.sender, allocation.voteLQTY, allocation.vetoLQTY))); } require( @@ -509,7 +516,9 @@ contract Governance is Multicall, UserProxyFactory, ReentrancyGuard, IGovernance emit ClaimForInitiative(_initiative, claim, votesSnapshot_.forEpoch); - try IInitiative(_initiative).onClaimForInitiative(votesSnapshot_.forEpoch, claim) {} catch {} + // try IInitiative(_initiative).onClaimForInitiative(votesSnapshot_.forEpoch, claim) {} catch {} + // Replaces try / catch | Enforces sufficient gas is passed + safeCallWithMinGas(_initiative, 350_000, 0, abi.encodeCall(IInitiative.onClaimForInitiative, (votesSnapshot_.forEpoch, claim))); return claim; } diff --git a/src/utils/SafeCallMinGas.sol b/src/utils/SafeCallMinGas.sol new file mode 100644 index 0000000..186b90a --- /dev/null +++ b/src/utils/SafeCallMinGas.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +/// @notice Given the gas requirement, ensures that the current context has sufficient gas to perform a call + a fixed buffer +/// @dev Credits: https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/libraries/SafeCall.sol#L100-L107 +function hasMinGas(uint256 _minGas, uint256 _reservedGas) view returns (bool) { + bool _hasMinGas; + assembly { + // Equation: gas × 63 ≥ minGas × 64 + 63(40_000 + reservedGas) + _hasMinGas := iszero(lt(mul(gas(), 63), add(mul(_minGas, 64), mul(add(40000, _reservedGas), 63)))) + } + return _hasMinGas; +} + +/// @dev Performs a call ignoring the recipient existing or not, passing the exact gas value, ignoring any return value +function safeCallWithMinGas( + address _target, + uint256 _gas, + uint256 _value, + bytes memory _calldata +) returns (bool success) { + require(hasMinGas(_gas, 1_000), "Must have minGas"); + + // dispatch message to recipient + // by assembly calling "handle" function + // we call via assembly to avoid memcopying a very large returndata + // returned by a malicious contract + assembly { + success := call( + _gas, // gas + _target, // recipient + _value, // ether value + add(_calldata, 0x20), // inloc + mload(_calldata), // inlen + 0, // outloc + 0 // outlen + ) + + // Ignore all return values + } + return (success); +} \ No newline at end of file From fcd26fba000223333a5ebad03bc48de8209249a9 Mon Sep 17 00:00:00 2001 From: gallo Date: Mon, 7 Oct 2024 10:11:41 +0200 Subject: [PATCH 03/22] feat: docs --- src/Governance.sol | 10 ++++++---- src/utils/SafeCallMinGas.sol | 4 +++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Governance.sol b/src/Governance.sol index d60caf0..ef5f850 100644 --- a/src/Governance.sol +++ b/src/Governance.sol @@ -21,6 +21,8 @@ import {safeCallWithMinGas} from "./utils/SafeCallMinGas.sol"; contract Governance is Multicall, UserProxyFactory, ReentrancyGuard, IGovernance { using SafeERC20 for IERC20; + uint256 constant MIN_GAS_TO_HOOK = 350_000; /// Replace this to ensure hooks have sufficient gas + /// @inheritdoc IGovernance ILQTYStaking public immutable stakingV1; /// @inheritdoc IGovernance @@ -326,7 +328,7 @@ contract Governance is Multicall, UserProxyFactory, ReentrancyGuard, IGovernance // try IInitiative(_initiative).onRegisterInitiative(currentEpoch) {} catch {} // Replaces try / catch | Enforces sufficient gas is passed - safeCallWithMinGas(_initiative, 350_000, 0, abi.encodeCall(IInitiative.onRegisterInitiative, (currentEpoch))); + safeCallWithMinGas(_initiative, MIN_GAS_TO_HOOK, 0, abi.encodeCall(IInitiative.onRegisterInitiative, (currentEpoch))); } /// @inheritdoc IGovernance @@ -374,7 +376,7 @@ contract Governance is Multicall, UserProxyFactory, ReentrancyGuard, IGovernance // try IInitiative(_initiative).onUnregisterInitiative(currentEpoch) {} catch {} // Replaces try / catch | Enforces sufficient gas is passed - safeCallWithMinGas(_initiative, 350_000, 0, abi.encodeCall(IInitiative.onUnregisterInitiative, (currentEpoch))); + safeCallWithMinGas(_initiative, MIN_GAS_TO_HOOK, 0, abi.encodeCall(IInitiative.onUnregisterInitiative, (currentEpoch))); } /// @inheritdoc IGovernance @@ -486,7 +488,7 @@ contract Governance is Multicall, UserProxyFactory, ReentrancyGuard, IGovernance // currentEpoch, msg.sender, allocation.voteLQTY, allocation.vetoLQTY // ) {} catch {} // Replaces try / catch | Enforces sufficient gas is passed - safeCallWithMinGas(initiative, 350_000, 0, abi.encodeCall(IInitiative.onAfterAllocateLQTY, (currentEpoch, msg.sender, allocation.voteLQTY, allocation.vetoLQTY))); + safeCallWithMinGas(initiative, MIN_GAS_TO_HOOK, 0, abi.encodeCall(IInitiative.onAfterAllocateLQTY, (currentEpoch, msg.sender, allocation.voteLQTY, allocation.vetoLQTY))); } require( @@ -518,7 +520,7 @@ contract Governance is Multicall, UserProxyFactory, ReentrancyGuard, IGovernance // try IInitiative(_initiative).onClaimForInitiative(votesSnapshot_.forEpoch, claim) {} catch {} // Replaces try / catch | Enforces sufficient gas is passed - safeCallWithMinGas(_initiative, 350_000, 0, abi.encodeCall(IInitiative.onClaimForInitiative, (votesSnapshot_.forEpoch, claim))); + safeCallWithMinGas(_initiative, MIN_GAS_TO_HOOK, 0, abi.encodeCall(IInitiative.onClaimForInitiative, (votesSnapshot_.forEpoch, claim))); return claim; } diff --git a/src/utils/SafeCallMinGas.sol b/src/utils/SafeCallMinGas.sol index 186b90a..04025c3 100644 --- a/src/utils/SafeCallMinGas.sol +++ b/src/utils/SafeCallMinGas.sol @@ -19,7 +19,9 @@ function safeCallWithMinGas( uint256 _value, bytes memory _calldata ) returns (bool success) { - require(hasMinGas(_gas, 1_000), "Must have minGas"); + /// @audit This is not necessary + /// But this is basically a worst case estimate of mem exp cost + operations before the call + require(hasMinGas(_gas, 1_000), "Must have minGas"); // dispatch message to recipient // by assembly calling "handle" function From 328b8e593b9a66f3b6e65a3c3b66287531a321f0 Mon Sep 17 00:00:00 2001 From: gallo Date: Mon, 7 Oct 2024 15:10:41 +0200 Subject: [PATCH 04/22] fix: require eth success --- src/UserProxy.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UserProxy.sol b/src/UserProxy.sol index 08ae5fb..86f4a8c 100644 --- a/src/UserProxy.sol +++ b/src/UserProxy.sol @@ -75,7 +75,7 @@ contract UserProxy is IUserProxy { ethAmount = address(this).balance; if (ethAmount > 0) { (bool success,) = payable(_lusdEthRecipient).call{value: ethAmount}(""); - success; + require(success, "UserProxy: eth-fail"); } emit Unstake(_amount, _lqtyRecipient, _lusdEthRecipient, lusdAmount, ethAmount); From c6e9dfdc996247a6f97f2b53be813f1794e3cfa4 Mon Sep 17 00:00:00 2001 From: gallo Date: Mon, 7 Oct 2024 17:39:20 +0200 Subject: [PATCH 05/22] feat: curvev2 integration cannot be griefed --- src/CurveV2GaugeRewards.sol | 28 ++++++++++++---- test/CurveV2GaugeRewards.t.sol | 61 +++++++++++++++++++++++++++++----- 2 files changed, 75 insertions(+), 14 deletions(-) diff --git a/src/CurveV2GaugeRewards.sol b/src/CurveV2GaugeRewards.sol index 9c6ae51..95282ac 100644 --- a/src/CurveV2GaugeRewards.sol +++ b/src/CurveV2GaugeRewards.sol @@ -18,14 +18,30 @@ contract CurveV2GaugeRewards is BribeInitiative { duration = _duration; } - function depositIntoGauge() external returns (uint256) { - uint256 amount = governance.claimForInitiative(address(this)); + uint256 public remainder; - bold.approve(address(gauge), amount); - gauge.deposit_reward_token(address(bold), amount, duration); - emit DepositIntoGauge(amount); + /// @notice Governance transfers Bold, and we deposit it into the gauge + /// @dev Doing this allows anyone to trigger the claim + function onClaimForInitiative(uint16, uint256 _bold) external override onlyGovernance { + _depositIntoGauge(_bold); + } + + function _depositIntoGauge(uint256 amount) internal { + + // For small donations queue them into the contract + if(amount < duration * 1000) { + remainder += amount; + return; + } + + uint256 total = amount + remainder; + remainder = 0; - return amount; + bold.approve(address(gauge), total); + gauge.deposit_reward_token(address(bold), total, duration); + + emit DepositIntoGauge(total); } + } diff --git a/test/CurveV2GaugeRewards.t.sol b/test/CurveV2GaugeRewards.t.sol index e46a137..fd053d2 100644 --- a/test/CurveV2GaugeRewards.t.sol +++ b/test/CurveV2GaugeRewards.t.sol @@ -42,6 +42,8 @@ contract CurveV2GaugeRewardsTest is Test { ILiquidityGauge private gauge; CurveV2GaugeRewards private curveV2GaugeRewards; + address mockGovernance = address(0x123123); + function setUp() public { vm.createSelectFork(vm.rpcUrl("mainnet"), 20430000); @@ -68,7 +70,7 @@ contract CurveV2GaugeRewardsTest is Test { curveV2GaugeRewards = new CurveV2GaugeRewards( // address(vm.computeCreateAddress(address(this), vm.getNonce(address(this)) + 1)), - address(new MockGovernance()), + address(mockGovernance), address(lusd), address(lqty), address(gauge), @@ -117,14 +119,57 @@ contract CurveV2GaugeRewardsTest is Test { vm.stopPrank(); } - function test_depositIntoGauge() public { - vm.startPrank(lusdHolder); - lusd.transfer(address(curveV2GaugeRewards), 1000e18); - vm.stopPrank(); + function test_claimAndDepositIntoGaugeFuzz(uint128 amt) public { + deal(address(lusd), mockGovernance, amt); + vm.assume(amt > 604800); + + + // Pretend a Proposal has passed + vm.startPrank( + address(mockGovernance) + ); + lusd.transfer(address(curveV2GaugeRewards), amt); + + assertEq(lusd.balanceOf(address(curveV2GaugeRewards)), amt); + curveV2GaugeRewards.onClaimForInitiative(0, amt); + assertEq(lusd.balanceOf(address(curveV2GaugeRewards)), curveV2GaugeRewards.remainder()); + } + + /// @dev If the amount rounds down below 1 per second it reverts + function test_claimAndDepositIntoGaugeGrief() public { + uint256 amt = 604800 - 1; + deal(address(lusd), mockGovernance, amt); + + + // Pretend a Proposal has passed + vm.startPrank( + address(mockGovernance) + ); + lusd.transfer(address(curveV2GaugeRewards), amt); + + assertEq(lusd.balanceOf(address(curveV2GaugeRewards)), amt); + curveV2GaugeRewards.onClaimForInitiative(0, amt); + assertEq(lusd.balanceOf(address(curveV2GaugeRewards)), curveV2GaugeRewards.remainder()); + } + + + /// @dev Fuzz test that shows that given a total = amt + dust, the dust is lost permanently + function test_noDustGriefFuzz(uint128 amt, uint128 dust) public { + uint256 total = uint256(amt) + uint256(dust); + deal(address(lusd), mockGovernance, total); + - vm.mockCall( - address(governance), abi.encode(IGovernance.claimForInitiative.selector), abi.encode(uint256(1000e18)) + // Pretend a Proposal has passed + vm.startPrank( + address(mockGovernance) ); - curveV2GaugeRewards.depositIntoGauge(); + // Dust amount + lusd.transfer(address(curveV2GaugeRewards), amt); + // Rest + lusd.transfer(address(curveV2GaugeRewards), dust); + + assertEq(lusd.balanceOf(address(curveV2GaugeRewards)), total); + curveV2GaugeRewards.onClaimForInitiative(0, amt); + assertEq(lusd.balanceOf(address(curveV2GaugeRewards)), curveV2GaugeRewards.remainder() + dust); } } From d4b4c6c9080e886459bceff354747c1d5d1fc999 Mon Sep 17 00:00:00 2001 From: jlqty <172397380+jltqy@users.noreply.github.com> Date: Tue, 8 Oct 2024 15:55:44 +0100 Subject: [PATCH 06/22] Fix bribing accounting by converting lqty to votes --- src/BribeInitiative.sol | 130 ++++---- src/Governance.sol | 2 +- src/interfaces/IBribeInitiative.sol | 14 +- src/interfaces/IInitiative.sol | 15 +- src/utils/DoubleLinkedList.sol | 6 +- test/BribeInitiative.t.sol | 16 +- test/BribeInitiativeAllocate.t.sol | 468 ++++++++++++++++------------ test/mocks/MockGovernance.sol | 13 + test/mocks/MockInitiative.sol | 8 +- 9 files changed, 385 insertions(+), 287 deletions(-) diff --git a/src/BribeInitiative.sol b/src/BribeInitiative.sol index dd313ae..d855a19 100644 --- a/src/BribeInitiative.sol +++ b/src/BribeInitiative.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; +import {console} from "forge-std/console.sol"; + import {IERC20} from "openzeppelin-contracts/contracts/interfaces/IERC20.sol"; import {SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; @@ -43,13 +45,13 @@ contract BribeInitiative is IInitiative, IBribeInitiative { } /// @inheritdoc IBribeInitiative - function totalLQTYAllocatedByEpoch(uint16 _epoch) external view returns (uint88) { - return totalLQTYAllocationByEpoch.getValue(_epoch); + function totalLQTYAllocatedByEpoch(uint16 _epoch) external view returns (uint88, uint32) { + return _loadTotalLQTYAllocation(_epoch); } /// @inheritdoc IBribeInitiative - function lqtyAllocatedByUserAtEpoch(address _user, uint16 _epoch) external view returns (uint88) { - return lqtyAllocationByUserAtEpoch[_user].getValue(_epoch); + function lqtyAllocatedByUserAtEpoch(address _user, uint16 _epoch) external view returns (uint88, uint32) { + return _loadLQTYAllocation(_user, _epoch); } /// @inheritdoc IBribeInitiative @@ -96,9 +98,14 @@ contract BribeInitiative is IInitiative, IBribeInitiative { "BribeInitiative: invalid-prev-total-lqty-allocation-epoch" ); - boldAmount = uint256(bribe.boldAmount) * uint256(lqtyAllocation.value) / uint256(totalLQTYAllocation.value); - bribeTokenAmount = - uint256(bribe.bribeTokenAmount) * uint256(lqtyAllocation.value) / uint256(totalLQTYAllocation.value); + (uint88 totalLQTY, uint32 totalAverageTimestamp) = _decodeLQTYAllocation(totalLQTYAllocation.value); + uint240 totalVotes = governance.lqtyToVotes(totalLQTY, block.timestamp, totalAverageTimestamp); + if (totalVotes != 0) { + (uint88 lqty, uint32 averageTimestamp) = _decodeLQTYAllocation(lqtyAllocation.value); + uint240 votes = governance.lqtyToVotes(lqty, block.timestamp, averageTimestamp); + boldAmount = uint256(bribe.boldAmount) * uint256(votes) / uint256(totalVotes); + bribeTokenAmount = uint256(bribe.bribeTokenAmount) * uint256(votes) / uint256(totalVotes); + } claimedBribeAtEpoch[_user][_epoch] = true; @@ -129,92 +136,81 @@ contract BribeInitiative is IInitiative, IBribeInitiative { /// @inheritdoc IInitiative function onUnregisterInitiative(uint16) external virtual override onlyGovernance {} - function _setTotalLQTYAllocationByEpoch(uint16 _epoch, uint88 _value, bool _insert) private { + function _setTotalLQTYAllocationByEpoch(uint16 _epoch, uint88 _lqty, uint32 _averageTimestamp, bool _insert) + private + { + uint224 value = (uint224(_lqty) << 32) | _averageTimestamp; if (_insert) { - totalLQTYAllocationByEpoch.insert(_epoch, _value, 0); + totalLQTYAllocationByEpoch.insert(_epoch, value, 0); } else { - totalLQTYAllocationByEpoch.items[_epoch].value = _value; + totalLQTYAllocationByEpoch.items[_epoch].value = value; } - emit ModifyTotalLQTYAllocation(_epoch, _value); + emit ModifyTotalLQTYAllocation(_epoch, _lqty, _averageTimestamp); } - function _setLQTYAllocationByUserAtEpoch(address _user, uint16 _epoch, uint88 _value, bool _insert) private { + function _setLQTYAllocationByUserAtEpoch( + address _user, + uint16 _epoch, + uint88 _lqty, + uint32 _averageTimestamp, + bool _insert + ) private { + uint224 value = (uint224(_lqty) << 32) | _averageTimestamp; if (_insert) { - lqtyAllocationByUserAtEpoch[_user].insert(_epoch, _value, 0); + lqtyAllocationByUserAtEpoch[_user].insert(_epoch, value, 0); } else { - lqtyAllocationByUserAtEpoch[_user].items[_epoch].value = _value; + lqtyAllocationByUserAtEpoch[_user].items[_epoch].value = value; } - emit ModifyLQTYAllocation(_user, _epoch, _value); + emit ModifyLQTYAllocation(_user, _epoch, _lqty, _averageTimestamp); + } + + function _decodeLQTYAllocation(uint224 _value) private pure returns (uint88, uint32) { + return (uint88(_value >> 32), uint32(_value)); + } + + function _loadTotalLQTYAllocation(uint16 _epoch) private view returns (uint88, uint32) { + return _decodeLQTYAllocation(totalLQTYAllocationByEpoch.items[_epoch].value); + } + + function _loadLQTYAllocation(address _user, uint16 _epoch) private view returns (uint88, uint32) { + return _decodeLQTYAllocation(lqtyAllocationByUserAtEpoch[_user].items[_epoch].value); } /// @inheritdoc IInitiative - function onAfterAllocateLQTY(uint16 _currentEpoch, address _user, uint88 _voteLQTY, uint88 _vetoLQTY) - external - virtual - onlyGovernance - { + function onAfterAllocateLQTY( + uint16 _currentEpoch, + address _user, + IGovernance.UserState calldata _userState, + IGovernance.Allocation calldata _allocation, + IGovernance.InitiativeState calldata _initiativeState + ) external virtual onlyGovernance { uint16 mostRecentUserEpoch = lqtyAllocationByUserAtEpoch[_user].getHead(); if (_currentEpoch == 0) return; // if this is the first user allocation in the epoch, then insert a new item into the user allocation DLL if (mostRecentUserEpoch != _currentEpoch) { - uint88 prevVoteLQTY = lqtyAllocationByUserAtEpoch[_user].items[mostRecentUserEpoch].value; - uint88 newVoteLQTY = (_vetoLQTY == 0) ? _voteLQTY : 0; + uint88 newVoteLQTY = (_allocation.vetoLQTY == 0) ? _allocation.voteLQTY : 0; uint16 mostRecentTotalEpoch = totalLQTYAllocationByEpoch.getHead(); // if this is the first allocation in the epoch, then insert a new item into the total allocation DLL if (mostRecentTotalEpoch != _currentEpoch) { - uint88 prevTotalLQTYAllocation = totalLQTYAllocationByEpoch.items[mostRecentTotalEpoch].value; - if (_vetoLQTY == 0) { - // no veto to no veto - _setTotalLQTYAllocationByEpoch( - _currentEpoch, prevTotalLQTYAllocation + newVoteLQTY - prevVoteLQTY, true - ); - } else { - if (prevVoteLQTY != 0) { - // if the prev user allocation was counted in, then remove the prev user allocation from the - // total allocation (no veto to veto) - _setTotalLQTYAllocationByEpoch(_currentEpoch, prevTotalLQTYAllocation - prevVoteLQTY, true); - } else { - // veto to veto - _setTotalLQTYAllocationByEpoch(_currentEpoch, prevTotalLQTYAllocation, true); - } - } - } else { - if (_vetoLQTY == 0) { - // no veto to no veto - _setTotalLQTYAllocationByEpoch( - _currentEpoch, - totalLQTYAllocationByEpoch.items[_currentEpoch].value + newVoteLQTY - prevVoteLQTY, - false - ); - } else if (prevVoteLQTY != 0) { - // no veto to veto - _setTotalLQTYAllocationByEpoch( - _currentEpoch, totalLQTYAllocationByEpoch.items[_currentEpoch].value - prevVoteLQTY, false - ); - } - } - // insert a new item into the user allocation DLL - _setLQTYAllocationByUserAtEpoch(_user, _currentEpoch, newVoteLQTY, true); - } else { - uint88 prevVoteLQTY = lqtyAllocationByUserAtEpoch[_user].getItem(_currentEpoch).value; - if (_vetoLQTY == 0) { - // update the allocation for the current epoch by adding the new allocation and subtracting - // the previous one (no veto to no veto) _setTotalLQTYAllocationByEpoch( - _currentEpoch, - totalLQTYAllocationByEpoch.items[_currentEpoch].value + _voteLQTY - prevVoteLQTY, - false + _currentEpoch, _initiativeState.voteLQTY, _initiativeState.averageStakingTimestampVoteLQTY, true ); - _setLQTYAllocationByUserAtEpoch(_user, _currentEpoch, _voteLQTY, false); } else { - // if the user vetoed the initiative, subtract the allocation from the DLLs (no veto to veto) _setTotalLQTYAllocationByEpoch( - _currentEpoch, totalLQTYAllocationByEpoch.items[_currentEpoch].value - prevVoteLQTY, false + _currentEpoch, _initiativeState.voteLQTY, _initiativeState.averageStakingTimestampVoteLQTY, false ); - _setLQTYAllocationByUserAtEpoch(_user, _currentEpoch, 0, false); } + _setLQTYAllocationByUserAtEpoch(_user, _currentEpoch, newVoteLQTY, _userState.averageStakingTimestamp, true); + } else { + _setTotalLQTYAllocationByEpoch( + _currentEpoch, _initiativeState.voteLQTY, _initiativeState.averageStakingTimestampVoteLQTY, false + ); + uint88 newVoteLQTY = (_allocation.vetoLQTY == 0) ? _allocation.voteLQTY : 0; + _setLQTYAllocationByUserAtEpoch( + _user, _currentEpoch, newVoteLQTY, _userState.averageStakingTimestamp, false + ); } } diff --git a/src/Governance.sol b/src/Governance.sol index 2f361b3..a51b443 100644 --- a/src/Governance.sol +++ b/src/Governance.sol @@ -478,7 +478,7 @@ contract Governance is Multicall, UserProxyFactory, ReentrancyGuard, IGovernance emit AllocateLQTY(msg.sender, initiative, deltaLQTYVotes, deltaLQTYVetos, currentEpoch); try IInitiative(initiative).onAfterAllocateLQTY( - currentEpoch, msg.sender, allocation.voteLQTY, allocation.vetoLQTY + currentEpoch, msg.sender, userState, allocation, initiativeState ) {} catch {} } diff --git a/src/interfaces/IBribeInitiative.sol b/src/interfaces/IBribeInitiative.sol index 7f8838e..9d28d82 100644 --- a/src/interfaces/IBribeInitiative.sol +++ b/src/interfaces/IBribeInitiative.sol @@ -7,8 +7,8 @@ import {IGovernance} from "./IGovernance.sol"; interface IBribeInitiative { event DepositBribe(address depositor, uint128 boldAmount, uint128 bribeTokenAmount, uint16 epoch); - event ModifyLQTYAllocation(address user, uint16 epoch, uint88 lqtyAllocated); - event ModifyTotalLQTYAllocation(uint16 epoch, uint88 totalLQTYAllocated); + event ModifyLQTYAllocation(address user, uint16 epoch, uint88 lqtyAllocated, uint32 averageTimestamp); + event ModifyTotalLQTYAllocation(uint16 epoch, uint88 totalLQTYAllocated, uint32 averageTimestamp); event ClaimBribe(address user, uint16 epoch, uint256 boldAmount, uint256 bribeTokenAmount); /// @notice Address of the governance contract @@ -40,12 +40,18 @@ interface IBribeInitiative { /// @notice Total LQTY allocated to the initiative at a given epoch /// @param _epoch Epoch at which the LQTY was allocated /// @return totalLQTYAllocated Total LQTY allocated - function totalLQTYAllocatedByEpoch(uint16 _epoch) external view returns (uint88 totalLQTYAllocated); + function totalLQTYAllocatedByEpoch(uint16 _epoch) + external + view + returns (uint88 totalLQTYAllocated, uint32 averageTimestamp); /// @notice LQTY allocated by a user to the initiative at a given epoch /// @param _user Address of the user /// @param _epoch Epoch at which the LQTY was allocated by the user /// @return lqtyAllocated LQTY allocated by the user - function lqtyAllocatedByUserAtEpoch(address _user, uint16 _epoch) external view returns (uint88 lqtyAllocated); + function lqtyAllocatedByUserAtEpoch(address _user, uint16 _epoch) + external + view + returns (uint88 lqtyAllocated, uint32 averageTimestamp); /// @notice Deposit bribe tokens for a given epoch /// @dev The caller has to approve this contract to spend the BOLD and bribe tokens. diff --git a/src/interfaces/IInitiative.sol b/src/interfaces/IInitiative.sol index b530291..ddb3179 100644 --- a/src/interfaces/IInitiative.sol +++ b/src/interfaces/IInitiative.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; +import {IGovernance} from "./IGovernance.sol"; + interface IInitiative { /// @notice Callback hook that is called by Governance after the initiative was successfully registered /// @param _atEpoch Epoch at which the initiative is registered @@ -13,9 +15,16 @@ interface IInitiative { /// @notice Callback hook that is called by Governance after the LQTY allocation is updated by a user /// @param _currentEpoch Epoch at which the LQTY allocation is updated /// @param _user Address of the user that updated their LQTY allocation - /// @param _voteLQTY Allocated voting LQTY - /// @param _vetoLQTY Allocated vetoing LQTY - function onAfterAllocateLQTY(uint16 _currentEpoch, address _user, uint88 _voteLQTY, uint88 _vetoLQTY) external; + /// @param _userState User state + /// @param _allocation Allocation state from user to initiative + /// @param _initiativeState Initiative state + function onAfterAllocateLQTY( + uint16 _currentEpoch, + address _user, + IGovernance.UserState calldata _userState, + IGovernance.Allocation calldata _allocation, + IGovernance.InitiativeState calldata _initiativeState + ) external; /// @notice Callback hook that is called by Governance after the claim for the last epoch was distributed /// to the initiative diff --git a/src/utils/DoubleLinkedList.sol b/src/utils/DoubleLinkedList.sol index 7624d59..e91cb14 100644 --- a/src/utils/DoubleLinkedList.sol +++ b/src/utils/DoubleLinkedList.sol @@ -6,7 +6,7 @@ pragma solidity ^0.8.24; /// and the tail is defined as the null item's next pointer ([tail][prev][item][next][head]) library DoubleLinkedList { struct Item { - uint88 value; + uint224 value; uint16 prev; uint16 next; } @@ -53,7 +53,7 @@ library DoubleLinkedList { /// @param list Linked list which contains the item /// @param id Id of the item /// @return _ Value of the item - function getValue(List storage list, uint16 id) internal view returns (uint88) { + function getValue(List storage list, uint16 id) internal view returns (uint224) { return list.items[id].value; } @@ -81,7 +81,7 @@ library DoubleLinkedList { /// @param id Id of the item to insert /// @param value Value of the item to insert /// @param next Id of the item which should follow item `id` - function insert(List storage list, uint16 id, uint88 value, uint16 next) internal { + function insert(List storage list, uint16 id, uint224 value, uint16 next) internal { if (contains(list, id)) revert ItemInList(); if (next != 0 && !contains(list, next)) revert ItemNotInList(); uint16 prev = list.items[next].prev; diff --git a/test/BribeInitiative.t.sol b/test/BribeInitiative.t.sol index 6f8df63..35ad992 100644 --- a/test/BribeInitiative.t.sol +++ b/test/BribeInitiative.t.sol @@ -106,8 +106,14 @@ contract BribeInitiativeTest is Test { deltaVoteLQTY[0] = 1e18; int176[] memory deltaVetoLQTY = new int176[](1); governance.allocateLQTY(initiatives, deltaVoteLQTY, deltaVetoLQTY); - assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); - assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 1e18); + (uint88 totalLQTYAllocated, uint32 averageTimestamp) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + assertEq(totalLQTYAllocated, 1e18); + assertEq(averageTimestamp, block.timestamp - 365 days - 365 days); + (uint88 userLQTYAllocated, uint32 userAverageTimestamp) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()); + assertEq(userLQTYAllocated, 1e18); + assertEq(userAverageTimestamp, block.timestamp - 365 days - 365 days); // should be zero since user was not deposited at that time BribeInitiative.ClaimData[] memory epochs = new BribeInitiative.ClaimData[](1); @@ -144,8 +150,10 @@ contract BribeInitiativeTest is Test { deltaVoteLQTY[0] = -0.5e18; governance.allocateLQTY(initiatives, deltaVoteLQTY, deltaVetoLQTY); governance.allocateLQTY(initiatives, deltaVoteLQTY, deltaVetoLQTY); - assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 0); - assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 0); + (userLQTYAllocated, userAverageTimestamp) = bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()); + assertEq(userLQTYAllocated, 0); + (totalLQTYAllocated, averageTimestamp) = bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + assertEq(totalLQTYAllocated, 0); vm.stopPrank(); } } diff --git a/test/BribeInitiativeAllocate.t.sol b/test/BribeInitiativeAllocate.t.sol index 8fa21b1..9a57e5c 100644 --- a/test/BribeInitiativeAllocate.t.sol +++ b/test/BribeInitiativeAllocate.t.sol @@ -2,11 +2,14 @@ pragma solidity ^0.8.24; import {Test} from "forge-std/Test.sol"; +import {console} from "forge-std/console.sol"; import {MockERC20} from "forge-std/mocks/MockERC20.sol"; import {Governance} from "../src/Governance.sol"; import {BribeInitiative} from "../src/BribeInitiative.sol"; +import {IGovernance} from "../src/interfaces/IGovernance.sol"; + import {MockStakingV1} from "./mocks/MockStakingV1.sol"; import {MockGovernance} from "./mocks/MockGovernance.sol"; @@ -64,13 +67,51 @@ contract BribeInitiativeAllocateTest is Test { vm.startPrank(address(governance)); - bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user2, 1e18, 0); - assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); - assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()), 1e18); - - bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 1000e18, 0); - assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1001e18); - assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 1000e18); + { + IGovernance.UserState memory userState = + IGovernance.UserState({allocatedLQTY: 1e18, averageStakingTimestamp: uint32(block.timestamp)}); + IGovernance.Allocation memory allocation = IGovernance.Allocation({voteLQTY: 1e18, vetoLQTY: 0, atEpoch: 1}); + IGovernance.InitiativeState memory initiativeState = IGovernance.InitiativeState({ + voteLQTY: 1e18, + vetoLQTY: 0, + averageStakingTimestampVoteLQTY: uint32(block.timestamp), + averageStakingTimestampVetoLQTY: 0, + counted: 0 + }); + bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user2, userState, allocation, initiativeState); + } + (uint88 totalLQTYAllocated, uint32 totalAverageTimestamp) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + assertEq(totalLQTYAllocated, 1e18); + assertEq(totalAverageTimestamp, uint32(block.timestamp)); + (uint88 userLQTYAllocated, uint32 userAverageTimestamp) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()); + assertEq(userLQTYAllocated, 1e18); + assertEq(userAverageTimestamp, uint32(block.timestamp)); + + { + IGovernance.UserState memory userState2 = + IGovernance.UserState({allocatedLQTY: 1000e18, averageStakingTimestamp: uint32(block.timestamp)}); + IGovernance.Allocation memory allocation2 = + IGovernance.Allocation({voteLQTY: 1000e18, vetoLQTY: 0, atEpoch: 1}); + IGovernance.InitiativeState memory initiativeState2 = IGovernance.InitiativeState({ + voteLQTY: 1001e18, + vetoLQTY: 0, + averageStakingTimestampVoteLQTY: uint32(block.timestamp), + averageStakingTimestampVetoLQTY: 0, + counted: 0 + }); + bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, userState2, allocation2, initiativeState2); + } + + (uint88 totalLQTYAllocated2, uint32 totalAverageTimestamp2) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + assertEq(totalLQTYAllocated2, 1001e18); + assertEq(totalAverageTimestamp2, block.timestamp); + (uint88 userLQTYAllocated2, uint32 userAverageTimestamp2) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()); + assertEq(userLQTYAllocated2, 1000e18); + assertEq(userAverageTimestamp2, block.timestamp); vm.startPrank(lusdHolder); lqty.approve(address(bribeInitiative), 1000e18); @@ -81,10 +122,27 @@ contract BribeInitiativeAllocateTest is Test { vm.startPrank(address(governance)); - bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 2000e18, 0); - - assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 2001e18); - assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 2000e18); + { + IGovernance.UserState memory userState = + IGovernance.UserState({allocatedLQTY: 2000e18, averageStakingTimestamp: uint32(1)}); + IGovernance.Allocation memory allocation = + IGovernance.Allocation({voteLQTY: 2000e18, vetoLQTY: 0, atEpoch: 2}); + IGovernance.InitiativeState memory initiativeState = IGovernance.InitiativeState({ + voteLQTY: 2001e18, + vetoLQTY: 0, + averageStakingTimestampVoteLQTY: uint32(1), + averageStakingTimestampVetoLQTY: 0, + counted: 0 + }); + bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, userState, allocation, initiativeState); + } + + (totalLQTYAllocated, totalAverageTimestamp) = bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + assertEq(totalLQTYAllocated, 2001e18); + assertEq(totalAverageTimestamp, 1); + (userLQTYAllocated, userAverageTimestamp) = bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()); + assertEq(userLQTYAllocated, 2000e18); + assertEq(userAverageTimestamp, 1); governance.setEpoch(3); @@ -94,267 +152,269 @@ contract BribeInitiativeAllocateTest is Test { claimData[0].epoch = 2; claimData[0].prevLQTYAllocationEpoch = 2; claimData[0].prevTotalLQTYAllocationEpoch = 2; - bribeInitiative.claimBribes(claimData); + (uint256 boldAmount, uint256 bribeTokenAmount) = bribeInitiative.claimBribes(claimData); + assertGt(boldAmount, 999e18); + assertGt(bribeTokenAmount, 999e18); } - function test_onAfterAllocateLQTY_newEpoch_NoVetoToVeto() public { - governance.setEpoch(1); + // function test_onAfterAllocateLQTY_newEpoch_NoVetoToVeto() public { + // governance.setEpoch(1); - vm.startPrank(address(governance)); + // vm.startPrank(address(governance)); - bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user2, 1e18, 0); - assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); - assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()), 1e18); + // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user2, 1e18, 0); + // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); + // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()), 1e18); - bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 1000e18, 0); - assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1001e18); - assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 1000e18); + // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 1000e18, 0); + // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1001e18); + // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 1000e18); - vm.startPrank(lusdHolder); - lqty.approve(address(bribeInitiative), 1000e18); - lusd.approve(address(bribeInitiative), 1000e18); - bribeInitiative.depositBribe(1000e18, 1000e18, governance.epoch() + 1); - vm.stopPrank(); + // vm.startPrank(lusdHolder); + // lqty.approve(address(bribeInitiative), 1000e18); + // lusd.approve(address(bribeInitiative), 1000e18); + // bribeInitiative.depositBribe(1000e18, 1000e18, governance.epoch() + 1); + // vm.stopPrank(); - governance.setEpoch(2); + // governance.setEpoch(2); - vm.startPrank(address(governance)); + // vm.startPrank(address(governance)); - bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 2000e18, 1); - assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); - assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 0); + // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 2000e18, 1); + // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); + // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 0); - bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user2, 1e18, 1); - assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 0); - assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()), 0); + // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user2, 1e18, 1); + // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 0); + // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()), 0); - governance.setEpoch(3); + // governance.setEpoch(3); - vm.startPrank(address(user)); + // vm.startPrank(address(user)); - BribeInitiative.ClaimData[] memory claimData = new BribeInitiative.ClaimData[](1); - claimData[0].epoch = 2; - claimData[0].prevLQTYAllocationEpoch = 2; - claimData[0].prevTotalLQTYAllocationEpoch = 2; - vm.expectRevert("BribeInitiative: invalid-prev-lqty-allocation-epoch"); // nothing to claim - bribeInitiative.claimBribes(claimData); - } + // BribeInitiative.ClaimData[] memory claimData = new BribeInitiative.ClaimData[](1); + // claimData[0].epoch = 2; + // claimData[0].prevLQTYAllocationEpoch = 2; + // claimData[0].prevTotalLQTYAllocationEpoch = 2; + // vm.expectRevert("BribeInitiative: invalid-prev-lqty-allocation-epoch"); // nothing to claim + // bribeInitiative.claimBribes(claimData); + // } - function test_onAfterAllocateLQTY_newEpoch_VetoToNoVeto() public { - governance.setEpoch(1); + // function test_onAfterAllocateLQTY_newEpoch_VetoToNoVeto() public { + // governance.setEpoch(1); - vm.startPrank(address(governance)); + // vm.startPrank(address(governance)); - bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user2, 1e18, 0); - assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); - assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()), 1e18); + // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user2, 1e18, 0); + // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); + // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()), 1e18); - bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 1000e18, 0); - assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1001e18); - assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 1000e18); + // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 1000e18, 0); + // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1001e18); + // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 1000e18); - governance.setEpoch(2); - bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 2000e18, 1); - assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); - assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 0); + // governance.setEpoch(2); + // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 2000e18, 1); + // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); + // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 0); - vm.startPrank(lusdHolder); - lqty.approve(address(bribeInitiative), 1000e18); - lusd.approve(address(bribeInitiative), 1000e18); - bribeInitiative.depositBribe(1000e18, 1000e18, governance.epoch() + 1); - vm.stopPrank(); + // vm.startPrank(lusdHolder); + // lqty.approve(address(bribeInitiative), 1000e18); + // lusd.approve(address(bribeInitiative), 1000e18); + // bribeInitiative.depositBribe(1000e18, 1000e18, governance.epoch() + 1); + // vm.stopPrank(); - vm.startPrank(address(governance)); + // vm.startPrank(address(governance)); - governance.setEpoch(3); - bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 2000e18, 0); - assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 2001e18); - assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 2000e18); + // governance.setEpoch(3); + // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 2000e18, 0); + // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 2001e18); + // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 2000e18); - governance.setEpoch(4); + // governance.setEpoch(4); - vm.startPrank(address(user)); + // vm.startPrank(address(user)); - BribeInitiative.ClaimData[] memory claimData = new BribeInitiative.ClaimData[](1); - claimData[0].epoch = 3; - claimData[0].prevLQTYAllocationEpoch = 3; - claimData[0].prevTotalLQTYAllocationEpoch = 3; - bribeInitiative.claimBribes(claimData); - } + // BribeInitiative.ClaimData[] memory claimData = new BribeInitiative.ClaimData[](1); + // claimData[0].epoch = 3; + // claimData[0].prevLQTYAllocationEpoch = 3; + // claimData[0].prevTotalLQTYAllocationEpoch = 3; + // bribeInitiative.claimBribes(claimData); + // } - function test_onAfterAllocateLQTY_newEpoch_VetoToVeto() public { - governance.setEpoch(1); + // function test_onAfterAllocateLQTY_newEpoch_VetoToVeto() public { + // governance.setEpoch(1); - vm.startPrank(address(governance)); + // vm.startPrank(address(governance)); - bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user2, 1e18, 0); - assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); - assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()), 1e18); + // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user2, 1e18, 0); + // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); + // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()), 1e18); - bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 1000e18, 0); - assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1001e18); - assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 1000e18); + // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 1000e18, 0); + // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1001e18); + // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 1000e18); - governance.setEpoch(2); - bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 2000e18, 1); - assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); - assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 0); + // governance.setEpoch(2); + // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 2000e18, 1); + // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); + // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 0); - governance.setEpoch(3); - bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 2000e18, 1); - assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); - assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 0); - } + // governance.setEpoch(3); + // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 2000e18, 1); + // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); + // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 0); + // } - function test_onAfterAllocateLQTY_sameEpoch_NoVetoToNoVeto() public { - vm.startPrank(lusdHolder); - lqty.approve(address(bribeInitiative), 1000e18); - lusd.approve(address(bribeInitiative), 1000e18); - bribeInitiative.depositBribe(1000e18, 1000e18, governance.epoch() + 1); - vm.stopPrank(); + // function test_onAfterAllocateLQTY_sameEpoch_NoVetoToNoVeto() public { + // vm.startPrank(lusdHolder); + // lqty.approve(address(bribeInitiative), 1000e18); + // lusd.approve(address(bribeInitiative), 1000e18); + // bribeInitiative.depositBribe(1000e18, 1000e18, governance.epoch() + 1); + // vm.stopPrank(); - governance.setEpoch(1); + // governance.setEpoch(1); - vm.startPrank(address(governance)); + // vm.startPrank(address(governance)); - bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user2, 1e18, 0); - assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); - assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()), 1e18); + // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user2, 1e18, 0); + // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); + // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()), 1e18); - bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 1000e18, 0); - assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1001e18); - assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 1000e18); + // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 1000e18, 0); + // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1001e18); + // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 1000e18); - bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 2000e18, 0); - assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 2001e18); - assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 2000e18); + // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 2000e18, 0); + // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 2001e18); + // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 2000e18); - governance.setEpoch(2); + // governance.setEpoch(2); - vm.startPrank(address(user)); + // vm.startPrank(address(user)); - BribeInitiative.ClaimData[] memory claimData = new BribeInitiative.ClaimData[](1); - claimData[0].epoch = 1; - claimData[0].prevLQTYAllocationEpoch = 1; - claimData[0].prevTotalLQTYAllocationEpoch = 1; - bribeInitiative.claimBribes(claimData); - } + // BribeInitiative.ClaimData[] memory claimData = new BribeInitiative.ClaimData[](1); + // claimData[0].epoch = 1; + // claimData[0].prevLQTYAllocationEpoch = 1; + // claimData[0].prevTotalLQTYAllocationEpoch = 1; + // bribeInitiative.claimBribes(claimData); + // } - function test_onAfterAllocateLQTY_sameEpoch_NoVetoToVeto() public { - vm.startPrank(lusdHolder); - lqty.approve(address(bribeInitiative), 1000e18); - lusd.approve(address(bribeInitiative), 1000e18); - bribeInitiative.depositBribe(1000e18, 1000e18, governance.epoch() + 1); - vm.stopPrank(); + // function test_onAfterAllocateLQTY_sameEpoch_NoVetoToVeto() public { + // vm.startPrank(lusdHolder); + // lqty.approve(address(bribeInitiative), 1000e18); + // lusd.approve(address(bribeInitiative), 1000e18); + // bribeInitiative.depositBribe(1000e18, 1000e18, governance.epoch() + 1); + // vm.stopPrank(); - governance.setEpoch(1); + // governance.setEpoch(1); - vm.startPrank(address(governance)); + // vm.startPrank(address(governance)); - bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user2, 1e18, 0); - assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); - assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()), 1e18); + // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user2, 1e18, 0); + // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); + // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()), 1e18); - bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 1000e18, 0); - assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1001e18); - assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 1000e18); + // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 1000e18, 0); + // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1001e18); + // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 1000e18); - bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 2000e18, 1); - assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); - assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 0); + // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 2000e18, 1); + // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); + // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 0); - governance.setEpoch(2); + // governance.setEpoch(2); - vm.startPrank(address(user)); + // vm.startPrank(address(user)); - BribeInitiative.ClaimData[] memory claimData = new BribeInitiative.ClaimData[](1); - claimData[0].epoch = 1; - claimData[0].prevLQTYAllocationEpoch = 1; - claimData[0].prevTotalLQTYAllocationEpoch = 1; - vm.expectRevert("BribeInitiative: invalid-prev-lqty-allocation-epoch"); // nothing to claim - bribeInitiative.claimBribes(claimData); - } + // BribeInitiative.ClaimData[] memory claimData = new BribeInitiative.ClaimData[](1); + // claimData[0].epoch = 1; + // claimData[0].prevLQTYAllocationEpoch = 1; + // claimData[0].prevTotalLQTYAllocationEpoch = 1; + // vm.expectRevert("BribeInitiative: invalid-prev-lqty-allocation-epoch"); // nothing to claim + // bribeInitiative.claimBribes(claimData); + // } - function test_onAfterAllocateLQTY_sameEpoch_VetoToNoVeto() public { - vm.startPrank(lusdHolder); - lqty.approve(address(bribeInitiative), 1000e18); - lusd.approve(address(bribeInitiative), 1000e18); - bribeInitiative.depositBribe(1000e18, 1000e18, governance.epoch() + 1); - vm.stopPrank(); + // function test_onAfterAllocateLQTY_sameEpoch_VetoToNoVeto() public { + // vm.startPrank(lusdHolder); + // lqty.approve(address(bribeInitiative), 1000e18); + // lusd.approve(address(bribeInitiative), 1000e18); + // bribeInitiative.depositBribe(1000e18, 1000e18, governance.epoch() + 1); + // vm.stopPrank(); - governance.setEpoch(1); + // governance.setEpoch(1); - vm.startPrank(address(governance)); + // vm.startPrank(address(governance)); - bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user2, 1e18, 0); - assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); - assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()), 1e18); + // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user2, 1e18, 0); + // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); + // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()), 1e18); - bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 1000e18, 0); - assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1001e18); - assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 1000e18); + // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 1000e18, 0); + // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1001e18); + // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 1000e18); - bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 2000e18, 1); - assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); - assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 0); + // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 2000e18, 1); + // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); + // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 0); - bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 2000e18, 0); - assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 2001e18); - assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 2000e18); + // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 2000e18, 0); + // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 2001e18); + // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 2000e18); - governance.setEpoch(2); + // governance.setEpoch(2); - vm.startPrank(address(user)); + // vm.startPrank(address(user)); - BribeInitiative.ClaimData[] memory claimData = new BribeInitiative.ClaimData[](1); - claimData[0].epoch = 1; - claimData[0].prevLQTYAllocationEpoch = 1; - claimData[0].prevTotalLQTYAllocationEpoch = 1; - bribeInitiative.claimBribes(claimData); - } + // BribeInitiative.ClaimData[] memory claimData = new BribeInitiative.ClaimData[](1); + // claimData[0].epoch = 1; + // claimData[0].prevLQTYAllocationEpoch = 1; + // claimData[0].prevTotalLQTYAllocationEpoch = 1; + // bribeInitiative.claimBribes(claimData); + // } - function test_onAfterAllocateLQTY_sameEpoch_VetoToVeto() public { - governance.setEpoch(1); + // function test_onAfterAllocateLQTY_sameEpoch_VetoToVeto() public { + // governance.setEpoch(1); - vm.startPrank(address(governance)); + // vm.startPrank(address(governance)); - bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user2, 1e18, 0); - assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); - assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()), 1e18); + // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user2, 1e18, 0); + // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); + // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()), 1e18); - bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 1000e18, 0); - assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1001e18); - assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 1000e18); + // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 1000e18, 0); + // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1001e18); + // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 1000e18); - bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 2000e18, 1); - assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); - assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 0); + // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 2000e18, 1); + // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); + // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 0); - bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 1000e18, 1); - assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); - assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 0); - } + // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 1000e18, 1); + // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); + // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 0); + // } - function test_onAfterAllocateLQTY() public { - governance.setEpoch(1); + // function test_onAfterAllocateLQTY() public { + // governance.setEpoch(1); - vm.startPrank(address(governance)); + // vm.startPrank(address(governance)); - // first total deposit, first user deposit - bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 1000e18, 0); - assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1000e18); - assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 1000e18); + // // first total deposit, first user deposit + // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 1000e18, 0); + // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1000e18); + // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 1000e18); - // second total deposit, second user deposit - bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 1000e18, 0); - assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1000e18); // should stay the same - assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 1000e18); // should stay the same + // // second total deposit, second user deposit + // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 1000e18, 0); + // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1000e18); // should stay the same + // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 1000e18); // should stay the same - // third total deposit, first user deposit - bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user2, 1000e18, 0); - assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 2000e18); - assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()), 1000e18); + // // third total deposit, first user deposit + // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user2, 1000e18, 0); + // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 2000e18); + // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()), 1000e18); - vm.stopPrank(); - } + // vm.stopPrank(); + // } } diff --git a/test/mocks/MockGovernance.sol b/test/mocks/MockGovernance.sol index 3d34b14..3ed3e75 100644 --- a/test/mocks/MockGovernance.sol +++ b/test/mocks/MockGovernance.sol @@ -15,4 +15,17 @@ contract MockGovernance { function epoch() external view returns (uint16) { return __epoch; } + + function _averageAge(uint32 _currentTimestamp, uint32 _averageTimestamp) internal pure returns (uint32) { + if (_averageTimestamp == 0 || _currentTimestamp < _averageTimestamp) return 0; + return _currentTimestamp - _averageTimestamp; + } + + function lqtyToVotes(uint88 _lqtyAmount, uint256 _currentTimestamp, uint32 _averageTimestamp) + public + pure + returns (uint240) + { + return uint240(_lqtyAmount) * _averageAge(uint32(_currentTimestamp), _averageTimestamp); + } } diff --git a/test/mocks/MockInitiative.sol b/test/mocks/MockInitiative.sol index 8861f42..b5402cc 100644 --- a/test/mocks/MockInitiative.sol +++ b/test/mocks/MockInitiative.sol @@ -22,7 +22,13 @@ contract MockInitiative is IInitiative { } /// @inheritdoc IInitiative - function onAfterAllocateLQTY(uint16, address, uint88, uint88) external virtual { + function onAfterAllocateLQTY( + uint16, + address, + IGovernance.UserState calldata, + IGovernance.Allocation calldata, + IGovernance.InitiativeState calldata + ) external virtual { address[] memory initiatives = new address[](0); int176[] memory deltaLQTYVotes = new int176[](0); int176[] memory deltaLQTYVetos = new int176[](0); From df8cabf02b23bd6f196742d33f8f04e5dc1d07e8 Mon Sep 17 00:00:00 2001 From: jlqty <172397380+jltqy@users.noreply.github.com> Date: Tue, 8 Oct 2024 16:19:01 +0100 Subject: [PATCH 07/22] Simplify bribing accounting --- src/BribeInitiative.sol | 8 +- test/BribeInitiativeAllocate.t.sol | 135 ++++++++++++++++++++++------- 2 files changed, 108 insertions(+), 35 deletions(-) diff --git a/src/BribeInitiative.sol b/src/BribeInitiative.sol index d855a19..1d261a3 100644 --- a/src/BribeInitiative.sol +++ b/src/BribeInitiative.sol @@ -190,7 +190,6 @@ contract BribeInitiative is IInitiative, IBribeInitiative { // if this is the first user allocation in the epoch, then insert a new item into the user allocation DLL if (mostRecentUserEpoch != _currentEpoch) { - uint88 newVoteLQTY = (_allocation.vetoLQTY == 0) ? _allocation.voteLQTY : 0; uint16 mostRecentTotalEpoch = totalLQTYAllocationByEpoch.getHead(); // if this is the first allocation in the epoch, then insert a new item into the total allocation DLL if (mostRecentTotalEpoch != _currentEpoch) { @@ -202,14 +201,15 @@ contract BribeInitiative is IInitiative, IBribeInitiative { _currentEpoch, _initiativeState.voteLQTY, _initiativeState.averageStakingTimestampVoteLQTY, false ); } - _setLQTYAllocationByUserAtEpoch(_user, _currentEpoch, newVoteLQTY, _userState.averageStakingTimestamp, true); + _setLQTYAllocationByUserAtEpoch( + _user, _currentEpoch, _allocation.voteLQTY, _userState.averageStakingTimestamp, true + ); } else { _setTotalLQTYAllocationByEpoch( _currentEpoch, _initiativeState.voteLQTY, _initiativeState.averageStakingTimestampVoteLQTY, false ); - uint88 newVoteLQTY = (_allocation.vetoLQTY == 0) ? _allocation.voteLQTY : 0; _setLQTYAllocationByUserAtEpoch( - _user, _currentEpoch, newVoteLQTY, _userState.averageStakingTimestamp, false + _user, _currentEpoch, _allocation.voteLQTY, _userState.averageStakingTimestamp, false ); } } diff --git a/test/BribeInitiativeAllocate.t.sol b/test/BribeInitiativeAllocate.t.sol index 9a57e5c..165d531 100644 --- a/test/BribeInitiativeAllocate.t.sol +++ b/test/BribeInitiativeAllocate.t.sol @@ -157,48 +157,121 @@ contract BribeInitiativeAllocateTest is Test { assertGt(bribeTokenAmount, 999e18); } - // function test_onAfterAllocateLQTY_newEpoch_NoVetoToVeto() public { - // governance.setEpoch(1); + function test_onAfterAllocateLQTY_newEpoch_NoVetoToVeto() public { + governance.setEpoch(1); - // vm.startPrank(address(governance)); + vm.startPrank(address(governance)); - // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user2, 1e18, 0); - // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); - // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()), 1e18); + { + IGovernance.UserState memory userState = + IGovernance.UserState({allocatedLQTY: 1e18, averageStakingTimestamp: uint32(block.timestamp)}); + IGovernance.Allocation memory allocation = IGovernance.Allocation({voteLQTY: 1e18, vetoLQTY: 0, atEpoch: 1}); + IGovernance.InitiativeState memory initiativeState = IGovernance.InitiativeState({ + voteLQTY: 1e18, + vetoLQTY: 0, + averageStakingTimestampVoteLQTY: uint32(block.timestamp), + averageStakingTimestampVetoLQTY: 0, + counted: 0 + }); + bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user2, userState, allocation, initiativeState); + (uint88 totalLQTYAllocated, uint32 totalAverageTimestamp) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + assertEq(totalLQTYAllocated, 1e18); + assertEq(totalAverageTimestamp, uint32(block.timestamp)); + (uint88 userLQTYAllocated, uint32 userAverageTimestamp) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()); + assertEq(userLQTYAllocated, 1e18); + assertEq(userAverageTimestamp, uint32(block.timestamp)); + } - // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 1000e18, 0); - // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1001e18); - // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 1000e18); + { + IGovernance.UserState memory userState = + IGovernance.UserState({allocatedLQTY: 1e18, averageStakingTimestamp: uint32(block.timestamp)}); + IGovernance.Allocation memory allocation = IGovernance.Allocation({voteLQTY: 1e18, vetoLQTY: 0, atEpoch: 1}); + IGovernance.InitiativeState memory initiativeState = IGovernance.InitiativeState({ + voteLQTY: 1001e18, + vetoLQTY: 0, + averageStakingTimestampVoteLQTY: uint32(block.timestamp), + averageStakingTimestampVetoLQTY: 0, + counted: 0 + }); + bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user2, userState, allocation, initiativeState); + (uint88 totalLQTYAllocated, uint32 totalAverageTimestamp) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + assertEq(totalLQTYAllocated, 1001e18); + assertEq(totalAverageTimestamp, uint32(block.timestamp)); + (uint88 userLQTYAllocated, uint32 userAverageTimestamp) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()); + assertEq(userLQTYAllocated, 1e18); + assertEq(userAverageTimestamp, uint32(block.timestamp)); + } - // vm.startPrank(lusdHolder); - // lqty.approve(address(bribeInitiative), 1000e18); - // lusd.approve(address(bribeInitiative), 1000e18); - // bribeInitiative.depositBribe(1000e18, 1000e18, governance.epoch() + 1); - // vm.stopPrank(); + vm.startPrank(lusdHolder); + lqty.approve(address(bribeInitiative), 1000e18); + lusd.approve(address(bribeInitiative), 1000e18); + bribeInitiative.depositBribe(1000e18, 1000e18, governance.epoch() + 1); + vm.stopPrank(); - // governance.setEpoch(2); + governance.setEpoch(2); - // vm.startPrank(address(governance)); + vm.startPrank(address(governance)); - // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 2000e18, 1); - // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); - // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 0); + { + IGovernance.UserState memory userState = + IGovernance.UserState({allocatedLQTY: 1e18, averageStakingTimestamp: uint32(block.timestamp)}); + IGovernance.Allocation memory allocation = IGovernance.Allocation({voteLQTY: 0, vetoLQTY: 1, atEpoch: 1}); + IGovernance.InitiativeState memory initiativeState = IGovernance.InitiativeState({ + voteLQTY: 0, + vetoLQTY: 1, + averageStakingTimestampVoteLQTY: uint32(block.timestamp), + averageStakingTimestampVetoLQTY: 0, + counted: 0 + }); + bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, userState, allocation, initiativeState); + (uint88 totalLQTYAllocated, uint32 totalAverageTimestamp) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + assertEq(totalLQTYAllocated, 0); + assertEq(totalAverageTimestamp, uint32(block.timestamp)); + (uint88 userLQTYAllocated, uint32 userAverageTimestamp) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()); + assertEq(userLQTYAllocated, 0); + assertEq(userAverageTimestamp, uint32(block.timestamp)); + } - // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user2, 1e18, 1); - // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 0); - // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()), 0); + { + IGovernance.UserState memory userState = + IGovernance.UserState({allocatedLQTY: 1e18, averageStakingTimestamp: uint32(block.timestamp)}); + IGovernance.Allocation memory allocation = IGovernance.Allocation({voteLQTY: 0, vetoLQTY: 1, atEpoch: 1}); + IGovernance.InitiativeState memory initiativeState = IGovernance.InitiativeState({ + voteLQTY: 0, + vetoLQTY: 1, + averageStakingTimestampVoteLQTY: uint32(block.timestamp), + averageStakingTimestampVetoLQTY: 0, + counted: 0 + }); + bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user2, userState, allocation, initiativeState); + (uint88 totalLQTYAllocated, uint32 totalAverageTimestamp) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + assertEq(totalLQTYAllocated, 0); + assertEq(totalAverageTimestamp, uint32(block.timestamp)); + (uint88 userLQTYAllocated, uint32 userAverageTimestamp) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()); + assertEq(userLQTYAllocated, 0); + assertEq(userAverageTimestamp, uint32(block.timestamp)); + } - // governance.setEpoch(3); + governance.setEpoch(3); - // vm.startPrank(address(user)); + vm.startPrank(address(user)); - // BribeInitiative.ClaimData[] memory claimData = new BribeInitiative.ClaimData[](1); - // claimData[0].epoch = 2; - // claimData[0].prevLQTYAllocationEpoch = 2; - // claimData[0].prevTotalLQTYAllocationEpoch = 2; - // vm.expectRevert("BribeInitiative: invalid-prev-lqty-allocation-epoch"); // nothing to claim - // bribeInitiative.claimBribes(claimData); - // } + BribeInitiative.ClaimData[] memory claimData = new BribeInitiative.ClaimData[](1); + claimData[0].epoch = 2; + claimData[0].prevLQTYAllocationEpoch = 2; + claimData[0].prevTotalLQTYAllocationEpoch = 2; + (uint256 boldAmount, uint256 bribeTokenAmount) = bribeInitiative.claimBribes(claimData); + assertEq(boldAmount, 0); + assertEq(bribeTokenAmount, 0); + } // function test_onAfterAllocateLQTY_newEpoch_VetoToNoVeto() public { // governance.setEpoch(1); From 91ccc634cfc03250346cd3c85e535c3cdf22c44f Mon Sep 17 00:00:00 2001 From: jlqty <172397380+jltqy@users.noreply.github.com> Date: Tue, 8 Oct 2024 17:02:29 +0100 Subject: [PATCH 08/22] Update tests --- test/BribeInitiativeAllocate.t.sol | 370 +++++++++++++++++++++++------ 1 file changed, 296 insertions(+), 74 deletions(-) diff --git a/test/BribeInitiativeAllocate.t.sol b/test/BribeInitiativeAllocate.t.sol index 165d531..bb62e5d 100644 --- a/test/BribeInitiativeAllocate.t.sol +++ b/test/BribeInitiativeAllocate.t.sol @@ -273,105 +273,327 @@ contract BribeInitiativeAllocateTest is Test { assertEq(bribeTokenAmount, 0); } - // function test_onAfterAllocateLQTY_newEpoch_VetoToNoVeto() public { - // governance.setEpoch(1); + function test_onAfterAllocateLQTY_newEpoch_VetoToNoVeto() public { + governance.setEpoch(1); - // vm.startPrank(address(governance)); + vm.startPrank(address(governance)); - // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user2, 1e18, 0); - // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); - // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()), 1e18); + IGovernance.UserState memory userState = + IGovernance.UserState({allocatedLQTY: 1e18, averageStakingTimestamp: uint32(block.timestamp)}); + IGovernance.Allocation memory allocation = + IGovernance.Allocation({voteLQTY: 1e18, vetoLQTY: 0, atEpoch: uint16(governance.epoch())}); + IGovernance.InitiativeState memory initiativeState = IGovernance.InitiativeState({ + voteLQTY: 1e18, + vetoLQTY: 0, + averageStakingTimestampVoteLQTY: uint32(block.timestamp), + averageStakingTimestampVetoLQTY: 0, + counted: 1 + }); + bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user2, userState, allocation, initiativeState); - // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 1000e18, 0); - // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1001e18); - // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 1000e18); + (uint88 totalLQTYAllocated, uint32 totalAverageTimestamp) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + assertEq(totalLQTYAllocated, 1e18); + assertEq(totalAverageTimestamp, uint32(block.timestamp)); + (uint88 userLQTYAllocated, uint32 userAverageTimestamp) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()); + assertEq(userLQTYAllocated, 1e18); + assertEq(userAverageTimestamp, uint32(block.timestamp)); - // governance.setEpoch(2); - // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 2000e18, 1); - // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); - // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 0); + IGovernance.UserState memory userStateVeto = + IGovernance.UserState({allocatedLQTY: 1000e18, averageStakingTimestamp: uint32(block.timestamp)}); + IGovernance.Allocation memory allocationVeto = + IGovernance.Allocation({voteLQTY: 0, vetoLQTY: 1000e18, atEpoch: uint16(governance.epoch())}); + IGovernance.InitiativeState memory initiativeStateVeto = IGovernance.InitiativeState({ + voteLQTY: 1e18, + vetoLQTY: 1000e18, + averageStakingTimestampVoteLQTY: uint32(block.timestamp), + averageStakingTimestampVetoLQTY: uint32(block.timestamp), + counted: 1 + }); + bribeInitiative.onAfterAllocateLQTY( + governance.epoch(), user, userStateVeto, allocationVeto, initiativeStateVeto + ); + + (uint88 totalLQTYAllocatedAfterVeto, uint32 totalAverageTimestampAfterVeto) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + assertEq(totalLQTYAllocatedAfterVeto, 1e18); + assertEq(totalAverageTimestampAfterVeto, uint32(block.timestamp)); + (uint88 userLQTYAllocatedAfterVeto, uint32 userAverageTimestampAfterVeto) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()); + assertEq(userLQTYAllocatedAfterVeto, 0); + assertEq(userAverageTimestampAfterVeto, uint32(block.timestamp)); - // vm.startPrank(lusdHolder); - // lqty.approve(address(bribeInitiative), 1000e18); - // lusd.approve(address(bribeInitiative), 1000e18); - // bribeInitiative.depositBribe(1000e18, 1000e18, governance.epoch() + 1); - // vm.stopPrank(); + governance.setEpoch(2); - // vm.startPrank(address(governance)); + IGovernance.UserState memory userStateNewEpoch = + IGovernance.UserState({allocatedLQTY: 1, averageStakingTimestamp: uint32(block.timestamp)}); + IGovernance.Allocation memory allocationNewEpoch = + IGovernance.Allocation({voteLQTY: 0, vetoLQTY: 1, atEpoch: uint16(governance.epoch())}); + IGovernance.InitiativeState memory initiativeStateNewEpoch = IGovernance.InitiativeState({ + voteLQTY: 1e18, + vetoLQTY: 1, + averageStakingTimestampVoteLQTY: uint32(block.timestamp), + averageStakingTimestampVetoLQTY: uint32(block.timestamp), + counted: 1 + }); + bribeInitiative.onAfterAllocateLQTY( + governance.epoch(), user, userStateNewEpoch, allocationNewEpoch, initiativeStateNewEpoch + ); + + (uint88 totalLQTYAllocatedNewEpoch, uint32 totalAverageTimestampNewEpoch) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + assertEq(totalLQTYAllocatedNewEpoch, 1e18); + assertEq(totalAverageTimestampNewEpoch, uint32(block.timestamp)); + (uint88 userLQTYAllocatedNewEpoch, uint32 userAverageTimestampNewEpoch) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()); + assertEq(userLQTYAllocatedNewEpoch, 0); + assertEq(userAverageTimestampNewEpoch, uint32(block.timestamp)); - // governance.setEpoch(3); - // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 2000e18, 0); - // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 2001e18); - // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 2000e18); + vm.startPrank(lusdHolder); + lqty.approve(address(bribeInitiative), 1000e18); + lusd.approve(address(bribeInitiative), 1000e18); + bribeInitiative.depositBribe(1000e18, 1000e18, governance.epoch() + 1); + vm.stopPrank(); - // governance.setEpoch(4); + vm.startPrank(address(governance)); - // vm.startPrank(address(user)); + governance.setEpoch(3); - // BribeInitiative.ClaimData[] memory claimData = new BribeInitiative.ClaimData[](1); - // claimData[0].epoch = 3; - // claimData[0].prevLQTYAllocationEpoch = 3; - // claimData[0].prevTotalLQTYAllocationEpoch = 3; - // bribeInitiative.claimBribes(claimData); - // } + IGovernance.UserState memory userStateNewEpoch3 = + IGovernance.UserState({allocatedLQTY: 2000e18, averageStakingTimestamp: uint32(block.timestamp)}); + IGovernance.Allocation memory allocationNewEpoch3 = + IGovernance.Allocation({voteLQTY: 2000e18, vetoLQTY: 0, atEpoch: uint16(governance.epoch())}); + IGovernance.InitiativeState memory initiativeStateNewEpoch3 = IGovernance.InitiativeState({ + voteLQTY: 2001e18, + vetoLQTY: 0, + averageStakingTimestampVoteLQTY: uint32(block.timestamp), + averageStakingTimestampVetoLQTY: 0, + counted: 1 + }); + bribeInitiative.onAfterAllocateLQTY( + governance.epoch(), user, userStateNewEpoch3, allocationNewEpoch3, initiativeStateNewEpoch3 + ); + + (uint88 totalLQTYAllocatedNewEpoch3, uint32 totalAverageTimestampNewEpoch3) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + assertEq(totalLQTYAllocatedNewEpoch3, 2001e18); + assertEq(totalAverageTimestampNewEpoch3, uint32(block.timestamp)); + (uint88 userLQTYAllocatedNewEpoch3, uint32 userAverageTimestampNewEpoch3) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()); + assertEq(userLQTYAllocatedNewEpoch3, 2000e18); + assertEq(userAverageTimestampNewEpoch3, uint32(block.timestamp)); - // function test_onAfterAllocateLQTY_newEpoch_VetoToVeto() public { - // governance.setEpoch(1); + governance.setEpoch(4); - // vm.startPrank(address(governance)); + vm.startPrank(address(user)); - // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user2, 1e18, 0); - // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); - // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()), 1e18); + BribeInitiative.ClaimData[] memory claimData = new BribeInitiative.ClaimData[](1); + claimData[0].epoch = 3; + claimData[0].prevLQTYAllocationEpoch = 3; + claimData[0].prevTotalLQTYAllocationEpoch = 3; + bribeInitiative.claimBribes(claimData); + } - // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 1000e18, 0); - // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1001e18); - // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 1000e18); + function test_onAfterAllocateLQTY_newEpoch_VetoToVeto() public { + governance.setEpoch(1); - // governance.setEpoch(2); - // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 2000e18, 1); - // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); - // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 0); + vm.startPrank(address(governance)); - // governance.setEpoch(3); - // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 2000e18, 1); - // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); - // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 0); - // } + { + IGovernance.UserState memory userState = + IGovernance.UserState({allocatedLQTY: 1e18, averageStakingTimestamp: uint32(block.timestamp)}); + IGovernance.Allocation memory allocation = + IGovernance.Allocation({voteLQTY: 1e18, vetoLQTY: 0, atEpoch: uint16(governance.epoch())}); + IGovernance.InitiativeState memory initiativeState = IGovernance.InitiativeState({ + voteLQTY: 1e18, + vetoLQTY: 0, + averageStakingTimestampVoteLQTY: uint32(block.timestamp), + averageStakingTimestampVetoLQTY: 0, + counted: 1 + }); + bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user2, userState, allocation, initiativeState); - // function test_onAfterAllocateLQTY_sameEpoch_NoVetoToNoVeto() public { - // vm.startPrank(lusdHolder); - // lqty.approve(address(bribeInitiative), 1000e18); - // lusd.approve(address(bribeInitiative), 1000e18); - // bribeInitiative.depositBribe(1000e18, 1000e18, governance.epoch() + 1); - // vm.stopPrank(); + (uint88 totalLQTYAllocated, uint32 totalAverageTimestamp) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + assertEq(totalLQTYAllocated, 1e18); + assertEq(totalAverageTimestamp, uint32(block.timestamp)); + (uint88 userLQTYAllocated, uint32 userAverageTimestamp) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()); + assertEq(userLQTYAllocated, 1e18); + assertEq(userAverageTimestamp, uint32(block.timestamp)); + } - // governance.setEpoch(1); + { + IGovernance.UserState memory userState = + IGovernance.UserState({allocatedLQTY: 1000e18, averageStakingTimestamp: uint32(block.timestamp)}); + IGovernance.Allocation memory allocation = + IGovernance.Allocation({voteLQTY: 1000e18, vetoLQTY: 0, atEpoch: uint16(governance.epoch())}); + IGovernance.InitiativeState memory initiativeState = IGovernance.InitiativeState({ + voteLQTY: 1001e18, + vetoLQTY: 0, + averageStakingTimestampVoteLQTY: uint32(block.timestamp), + averageStakingTimestampVetoLQTY: 0, + counted: 1 + }); + bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, userState, allocation, initiativeState); - // vm.startPrank(address(governance)); + (uint88 totalLQTYAllocated, uint32 totalAverageTimestamp) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + assertEq(totalLQTYAllocated, 1001e18); + assertEq(totalAverageTimestamp, uint32(block.timestamp)); + (uint88 userLQTYAllocated, uint32 userAverageTimestamp) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()); + assertEq(userLQTYAllocated, 1000e18); + assertEq(userAverageTimestamp, uint32(block.timestamp)); + } - // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user2, 1e18, 0); - // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); - // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()), 1e18); + governance.setEpoch(2); - // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 1000e18, 0); - // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1001e18); - // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 1000e18); + { + IGovernance.UserState memory userState = + IGovernance.UserState({allocatedLQTY: 1, averageStakingTimestamp: uint32(block.timestamp)}); + IGovernance.Allocation memory allocation = + IGovernance.Allocation({voteLQTY: 0, vetoLQTY: 1, atEpoch: uint16(governance.epoch())}); + IGovernance.InitiativeState memory initiativeState = IGovernance.InitiativeState({ + voteLQTY: 1e18, + vetoLQTY: 0, + averageStakingTimestampVoteLQTY: uint32(block.timestamp), + averageStakingTimestampVetoLQTY: 0, + counted: 1 + }); + bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, userState, allocation, initiativeState); - // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 2000e18, 0); - // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 2001e18); - // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 2000e18); + (uint88 totalLQTYAllocated, uint32 totalAverageTimestamp) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + assertEq(totalLQTYAllocated, 1e18); + assertEq(totalAverageTimestamp, uint32(block.timestamp)); + (uint88 userLQTYAllocated, uint32 userAverageTimestamp) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()); + assertEq(userLQTYAllocated, 0); + assertEq(userAverageTimestamp, uint32(block.timestamp)); + } - // governance.setEpoch(2); + governance.setEpoch(3); - // vm.startPrank(address(user)); + { + IGovernance.UserState memory userState = + IGovernance.UserState({allocatedLQTY: 1, averageStakingTimestamp: uint32(block.timestamp)}); + IGovernance.Allocation memory allocation = + IGovernance.Allocation({voteLQTY: 0, vetoLQTY: 1, atEpoch: uint16(governance.epoch())}); + IGovernance.InitiativeState memory initiativeState = IGovernance.InitiativeState({ + voteLQTY: 1e18, + vetoLQTY: 0, + averageStakingTimestampVoteLQTY: uint32(block.timestamp), + averageStakingTimestampVetoLQTY: 0, + counted: 1 + }); + bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, userState, allocation, initiativeState); - // BribeInitiative.ClaimData[] memory claimData = new BribeInitiative.ClaimData[](1); - // claimData[0].epoch = 1; - // claimData[0].prevLQTYAllocationEpoch = 1; - // claimData[0].prevTotalLQTYAllocationEpoch = 1; - // bribeInitiative.claimBribes(claimData); - // } + (uint88 totalLQTYAllocated, uint32 totalAverageTimestamp) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + assertEq(totalLQTYAllocated, 1e18); + assertEq(totalAverageTimestamp, uint32(block.timestamp)); + (uint88 userLQTYAllocated, uint32 userAverageTimestamp) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()); + assertEq(userLQTYAllocated, 0); + assertEq(userAverageTimestamp, uint32(block.timestamp)); + } + } + + function test_onAfterAllocateLQTY_sameEpoch_NoVetoToNoVeto() public { + vm.startPrank(lusdHolder); + lqty.approve(address(bribeInitiative), 1000e18); + lusd.approve(address(bribeInitiative), 1000e18); + bribeInitiative.depositBribe(1000e18, 1000e18, governance.epoch() + 1); + vm.stopPrank(); + + governance.setEpoch(1); + + vm.startPrank(address(governance)); + + { + IGovernance.UserState memory userState = + IGovernance.UserState({allocatedLQTY: 1e18, averageStakingTimestamp: uint32(block.timestamp)}); + IGovernance.Allocation memory allocation = + IGovernance.Allocation({voteLQTY: 1e18, vetoLQTY: 0, atEpoch: uint16(governance.epoch())}); + IGovernance.InitiativeState memory initiativeState = IGovernance.InitiativeState({ + voteLQTY: 1e18, + vetoLQTY: 0, + averageStakingTimestampVoteLQTY: uint32(block.timestamp), + averageStakingTimestampVetoLQTY: 0, + counted: 1 + }); + bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user2, userState, allocation, initiativeState); + + (uint88 totalLQTYAllocated, uint32 totalAverageTimestamp) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + assertEq(totalLQTYAllocated, 1e18); + assertEq(totalAverageTimestamp, uint32(block.timestamp)); + (uint88 userLQTYAllocated, uint32 userAverageTimestamp) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()); + assertEq(userLQTYAllocated, 1e18); + assertEq(userAverageTimestamp, uint32(block.timestamp)); + } + + { + IGovernance.UserState memory userState = + IGovernance.UserState({allocatedLQTY: 1000e18, averageStakingTimestamp: uint32(block.timestamp)}); + IGovernance.Allocation memory allocation = + IGovernance.Allocation({voteLQTY: 1000e18, vetoLQTY: 0, atEpoch: uint16(governance.epoch())}); + IGovernance.InitiativeState memory initiativeState = IGovernance.InitiativeState({ + voteLQTY: 1001e18, + vetoLQTY: 0, + averageStakingTimestampVoteLQTY: uint32(block.timestamp), + averageStakingTimestampVetoLQTY: 0, + counted: 1 + }); + bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, userState, allocation, initiativeState); + + (uint88 totalLQTYAllocated, uint32 totalAverageTimestamp) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + assertEq(totalLQTYAllocated, 1001e18); + assertEq(totalAverageTimestamp, uint32(block.timestamp)); + (uint88 userLQTYAllocated, uint32 userAverageTimestamp) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()); + assertEq(userLQTYAllocated, 1000e18); + assertEq(userAverageTimestamp, uint32(block.timestamp)); + } + + { + IGovernance.UserState memory userState = + IGovernance.UserState({allocatedLQTY: 2000e18, averageStakingTimestamp: uint32(block.timestamp)}); + IGovernance.Allocation memory allocation = + IGovernance.Allocation({voteLQTY: 2000e18, vetoLQTY: 0, atEpoch: uint16(governance.epoch())}); + IGovernance.InitiativeState memory initiativeState = IGovernance.InitiativeState({ + voteLQTY: 2001e18, + vetoLQTY: 0, + averageStakingTimestampVoteLQTY: uint32(block.timestamp), + averageStakingTimestampVetoLQTY: 0, + counted: 1 + }); + bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, userState, allocation, initiativeState); + + (uint88 totalLQTYAllocated, uint32 totalAverageTimestamp) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + assertEq(totalLQTYAllocated, 2001e18); + assertEq(totalAverageTimestamp, uint32(block.timestamp)); + (uint88 userLQTYAllocated, uint32 userAverageTimestamp) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()); + assertEq(userLQTYAllocated, 2000e18); + assertEq(userAverageTimestamp, uint32(block.timestamp)); + } + + governance.setEpoch(2); + + vm.startPrank(address(user)); + + BribeInitiative.ClaimData[] memory claimData = new BribeInitiative.ClaimData[](1); + claimData[0].epoch = 1; + claimData[0].prevLQTYAllocationEpoch = 1; + claimData[0].prevTotalLQTYAllocationEpoch = 1; + bribeInitiative.claimBribes(claimData); + } // function test_onAfterAllocateLQTY_sameEpoch_NoVetoToVeto() public { // vm.startPrank(lusdHolder); From 07fdd43e9c0e9d2442910756fae5f39c42adbabd Mon Sep 17 00:00:00 2001 From: gallo Date: Tue, 8 Oct 2024 18:27:31 +0200 Subject: [PATCH 09/22] feat: malicious governance attack tester --- test/GovernanceAttacks.t.sol | 243 +++++++++++++++++++++++++++++ test/mocks/MaliciousInitiative.sol | 77 +++++++++ 2 files changed, 320 insertions(+) create mode 100644 test/GovernanceAttacks.t.sol create mode 100644 test/mocks/MaliciousInitiative.sol diff --git a/test/GovernanceAttacks.t.sol b/test/GovernanceAttacks.t.sol new file mode 100644 index 0000000..191da6d --- /dev/null +++ b/test/GovernanceAttacks.t.sol @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {Test} from "forge-std/Test.sol"; +import {VmSafe} from "forge-std/Vm.sol"; +import {console} from "forge-std/console.sol"; + +import {IERC20} from "openzeppelin-contracts/contracts/interfaces/IERC20.sol"; + +import {IGovernance} from "../src/interfaces/IGovernance.sol"; +import {ILQTY} from "../src/interfaces/ILQTY.sol"; + +import {BribeInitiative} from "../src/BribeInitiative.sol"; +import {Governance} from "../src/Governance.sol"; +import {UserProxy} from "../src/UserProxy.sol"; + +import {PermitParams} from "../src/utils/Types.sol"; + +import {MaliciousInitiative} from "./mocks/MaliciousInitiative.sol"; + + +contract GovernanceTest is Test { + IERC20 private constant lqty = IERC20(address(0x6DEA81C8171D0bA574754EF6F8b412F2Ed88c54D)); + IERC20 private constant lusd = IERC20(address(0x5f98805A4E8be255a32880FDeC7F6728C6568bA0)); + address private constant stakingV1 = address(0x4f9Fbb3f1E99B56e0Fe2892e623Ed36A76Fc605d); + address private constant user = address(0xF977814e90dA44bFA03b6295A0616a897441aceC); + address private constant user2 = address(0x10C9cff3c4Faa8A60cB8506a7A99411E6A199038); + address private constant lusdHolder = address(0xcA7f01403C4989d2b1A9335A2F09dD973709957c); + + uint128 private constant REGISTRATION_FEE = 1e18; + uint128 private constant REGISTRATION_THRESHOLD_FACTOR = 0.01e18; + uint128 private constant UNREGISTRATION_THRESHOLD_FACTOR = 4e18; + uint16 private constant REGISTRATION_WARM_UP_PERIOD = 4; + uint16 private constant UNREGISTRATION_AFTER_EPOCHS = 4; + uint128 private constant VOTING_THRESHOLD_FACTOR = 0.04e18; + uint88 private constant MIN_CLAIM = 500e18; + uint88 private constant MIN_ACCRUAL = 1000e18; + uint32 private constant EPOCH_DURATION = 604800; + uint32 private constant EPOCH_VOTING_CUTOFF = 518400; + + Governance private governance; + address[] private initialInitiatives; + + MaliciousInitiative private maliciousInitiative1; + MaliciousInitiative private maliciousInitiative2; + MaliciousInitiative private maliciousInitiative3; + + function setUp() public { + vm.createSelectFork(vm.rpcUrl("mainnet"), 20430000); + + maliciousInitiative1 = new MaliciousInitiative(); + maliciousInitiative2 = new MaliciousInitiative(); + maliciousInitiative3 = new MaliciousInitiative(); + + initialInitiatives.push(address(maliciousInitiative1)); + + governance = new Governance( + address(lqty), + address(lusd), + stakingV1, + address(lusd), + IGovernance.Configuration({ + registrationFee: REGISTRATION_FEE, + registrationThresholdFactor: REGISTRATION_THRESHOLD_FACTOR, + unregistrationThresholdFactor: UNREGISTRATION_THRESHOLD_FACTOR, + registrationWarmUpPeriod: REGISTRATION_WARM_UP_PERIOD, + unregistrationAfterEpochs: UNREGISTRATION_AFTER_EPOCHS, + votingThresholdFactor: VOTING_THRESHOLD_FACTOR, + minClaim: MIN_CLAIM, + minAccrual: MIN_ACCRUAL, + epochStart: uint32(block.timestamp), + epochDuration: EPOCH_DURATION, + epochVotingCutoff: EPOCH_VOTING_CUTOFF + }), + initialInitiatives + ); + + } + + // forge test --match-test test_deposit_attack -vv + // All calls should never revert due to malicious initiative + function test_all_revert_attacks_hardcoded() public { + uint256 zeroSnapshot = vm.snapshot(); + uint256 timeIncrease = 86400 * 30; + vm.warp(block.timestamp + timeIncrease); + + vm.startPrank(user); + + // should not revert if the user doesn't have a UserProxy deployed yet + address userProxy = governance.deriveUserProxyAddress(user); + lqty.approve(address(userProxy), 1e18); + + // deploy and deposit 1 LQTY + governance.depositLQTY(1e18); + assertEq(UserProxy(payable(userProxy)).staked(), 1e18); + (uint88 allocatedLQTY, uint32 averageStakingTimestamp) = governance.userStates(user); + assertEq(allocatedLQTY, 0); + // first deposit should have an averageStakingTimestamp if block.timestamp + assertEq(averageStakingTimestamp, block.timestamp); + vm.warp(block.timestamp + timeIncrease); + vm.stopPrank(); + + + vm.startPrank(lusdHolder); + lusd.transfer(address(governance), 10000e18); + vm.stopPrank(); + + address maliciousWhale = address(0xb4d); + deal(address(lusd), maliciousWhale, 2000e18); + vm.startPrank(maliciousWhale); + lusd.approve(address(governance), type(uint256).max); + + /// === REGISTRATION REVERTS === /// + uint256 registerNapshot = vm.snapshot(); + + maliciousInitiative2.setRevertBehaviour(MaliciousInitiative.FunctionType.REGISTER, MaliciousInitiative.RevertType.THROW); + governance.registerInitiative(address(maliciousInitiative2)); + vm.revertTo(registerNapshot); + + maliciousInitiative2.setRevertBehaviour(MaliciousInitiative.FunctionType.REGISTER, MaliciousInitiative.RevertType.OOG); + governance.registerInitiative(address(maliciousInitiative2)); + vm.revertTo(registerNapshot); + + maliciousInitiative2.setRevertBehaviour(MaliciousInitiative.FunctionType.REGISTER, MaliciousInitiative.RevertType.RETURN_BOMB); + governance.registerInitiative(address(maliciousInitiative2)); + vm.revertTo(registerNapshot); + + maliciousInitiative2.setRevertBehaviour(MaliciousInitiative.FunctionType.REGISTER, MaliciousInitiative.RevertType.REVERT_BOMB); + governance.registerInitiative(address(maliciousInitiative2)); + vm.revertTo(registerNapshot); + + // Reset and continue + maliciousInitiative2.setRevertBehaviour(MaliciousInitiative.FunctionType.REGISTER, MaliciousInitiative.RevertType.NONE); + governance.registerInitiative(address(maliciousInitiative2)); + + vm.stopPrank(); + + vm.warp(block.timestamp + governance.EPOCH_DURATION()); + + vm.startPrank(user); + address[] memory initiatives = new address[](1); + initiatives[0] = address(maliciousInitiative2); + int176[] memory deltaVoteLQTY = new int176[](1); + deltaVoteLQTY[0] = 5e17; + int176[] memory deltaVetoLQTY = new int176[](1); + + /// === Allocate LQTY REVERTS === /// + uint256 allocateSnapshot = vm.snapshot(); + + maliciousInitiative2.setRevertBehaviour(MaliciousInitiative.FunctionType.ALLOCATE, MaliciousInitiative.RevertType.THROW); + governance.allocateLQTY(initiatives, deltaVoteLQTY, deltaVetoLQTY); + vm.revertTo(allocateSnapshot); + + maliciousInitiative2.setRevertBehaviour(MaliciousInitiative.FunctionType.ALLOCATE, MaliciousInitiative.RevertType.OOG); + governance.allocateLQTY(initiatives, deltaVoteLQTY, deltaVetoLQTY); + vm.revertTo(allocateSnapshot); + + maliciousInitiative2.setRevertBehaviour(MaliciousInitiative.FunctionType.ALLOCATE, MaliciousInitiative.RevertType.RETURN_BOMB); + governance.allocateLQTY(initiatives, deltaVoteLQTY, deltaVetoLQTY); + vm.revertTo(allocateSnapshot); + + maliciousInitiative2.setRevertBehaviour(MaliciousInitiative.FunctionType.ALLOCATE, MaliciousInitiative.RevertType.REVERT_BOMB); + governance.allocateLQTY(initiatives, deltaVoteLQTY, deltaVetoLQTY); + vm.revertTo(allocateSnapshot); + + maliciousInitiative2.setRevertBehaviour(MaliciousInitiative.FunctionType.ALLOCATE, MaliciousInitiative.RevertType.NONE); + governance.allocateLQTY(initiatives, deltaVoteLQTY, deltaVetoLQTY); + + + + vm.warp(block.timestamp + governance.EPOCH_DURATION() + 1); + + /// === Claim for initiative REVERTS === /// + uint256 claimShapsnot = vm.snapshot(); + + maliciousInitiative2.setRevertBehaviour(MaliciousInitiative.FunctionType.CLAIM, MaliciousInitiative.RevertType.THROW); + governance.claimForInitiative(address(maliciousInitiative2)); + vm.revertTo(claimShapsnot); + + maliciousInitiative2.setRevertBehaviour(MaliciousInitiative.FunctionType.CLAIM, MaliciousInitiative.RevertType.OOG); + governance.claimForInitiative(address(maliciousInitiative2)); + vm.revertTo(claimShapsnot); + + maliciousInitiative2.setRevertBehaviour(MaliciousInitiative.FunctionType.CLAIM, MaliciousInitiative.RevertType.RETURN_BOMB); + governance.claimForInitiative(address(maliciousInitiative2)); + vm.revertTo(claimShapsnot); + + maliciousInitiative2.setRevertBehaviour(MaliciousInitiative.FunctionType.CLAIM, MaliciousInitiative.RevertType.REVERT_BOMB); + governance.claimForInitiative(address(maliciousInitiative2)); + vm.revertTo(claimShapsnot); + + maliciousInitiative2.setRevertBehaviour(MaliciousInitiative.FunctionType.CLAIM, MaliciousInitiative.RevertType.NONE); + uint256 claimed = governance.claimForInitiative(address(maliciousInitiative2)); + assertEq(claimed, 10001e18); + + + /// === Unregister Reverts === /// + + vm.startPrank(user); + initiatives = new address[](2); + initiatives[0] = address(maliciousInitiative2); + initiatives[1] = address(maliciousInitiative1); + deltaVoteLQTY = new int176[](2); + deltaVoteLQTY[0] = -5e17; + deltaVoteLQTY[1] = 5e17; + deltaVetoLQTY = new int176[](2); + governance.allocateLQTY(initiatives, deltaVoteLQTY, deltaVetoLQTY); + + (Governance.VoteSnapshot memory v, Governance.InitiativeVoteSnapshot memory initData) = governance.snapshotVotesForInitiative(address(maliciousInitiative2)); + uint256 currentEpoch = governance.epoch(); + assertEq(initData.lastCountedEpoch, currentEpoch - 1, "Epoch Matches"); + + // Inactive for 4 epochs + // Add another proposal + + vm.warp(block.timestamp + governance.EPOCH_DURATION() * 4); + (v, initData) = governance.snapshotVotesForInitiative(address(maliciousInitiative2)); + assertEq(initData.lastCountedEpoch, currentEpoch - 1, "Epoch Matches"); /// @audit This fails if you have 0 votes, see QA + + uint256 unregisterSnapshot = vm.snapshot(); + + maliciousInitiative2.setRevertBehaviour(MaliciousInitiative.FunctionType.UNREGISTER, MaliciousInitiative.RevertType.THROW); + governance.unregisterInitiative(address(maliciousInitiative2)); + vm.revertTo(unregisterSnapshot); + + maliciousInitiative2.setRevertBehaviour(MaliciousInitiative.FunctionType.UNREGISTER, MaliciousInitiative.RevertType.OOG); + governance.unregisterInitiative(address(maliciousInitiative2)); + vm.revertTo(unregisterSnapshot); + + maliciousInitiative2.setRevertBehaviour(MaliciousInitiative.FunctionType.UNREGISTER, MaliciousInitiative.RevertType.RETURN_BOMB); + governance.unregisterInitiative(address(maliciousInitiative2)); + vm.revertTo(unregisterSnapshot); + + maliciousInitiative2.setRevertBehaviour(MaliciousInitiative.FunctionType.UNREGISTER, MaliciousInitiative.RevertType.REVERT_BOMB); + governance.unregisterInitiative(address(maliciousInitiative2)); + vm.revertTo(unregisterSnapshot); + + maliciousInitiative2.setRevertBehaviour(MaliciousInitiative.FunctionType.UNREGISTER, MaliciousInitiative.RevertType.NONE); + governance.unregisterInitiative(address(maliciousInitiative2)); + } + + +} diff --git a/test/mocks/MaliciousInitiative.sol b/test/mocks/MaliciousInitiative.sol new file mode 100644 index 0000000..2dbd60c --- /dev/null +++ b/test/mocks/MaliciousInitiative.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; +import {IInitiative} from "src/interfaces/IInitiative.sol"; + +contract MaliciousInitiative is IInitiative { + + enum FunctionType { + NONE, + REGISTER, + UNREGISTER, + ALLOCATE, + CLAIM + } + + enum RevertType { + NONE, + THROW, + OOG, + RETURN_BOMB, + REVERT_BOMB + } + + mapping (FunctionType => RevertType) revertBehaviours; + + /// @dev specify the revert behaviour on each function + function setRevertBehaviour(FunctionType ft, RevertType rt) external { + revertBehaviours[ft] = rt; + } + + // Do stuff on each hook + function onRegisterInitiative(uint16 _atEpoch) external override { + _performRevertBehaviour(revertBehaviours[FunctionType.REGISTER]); + } + + function onUnregisterInitiative(uint16 _atEpoch) external override { + _performRevertBehaviour(revertBehaviours[FunctionType.UNREGISTER]); + } + + + function onAfterAllocateLQTY(uint16 _currentEpoch, address _user, uint88 _voteLQTY, uint88 _vetoLQTY) external override { + _performRevertBehaviour(revertBehaviours[FunctionType.ALLOCATE]); + } + + function onClaimForInitiative(uint16 _claimEpoch, uint256 _bold) external override { + _performRevertBehaviour(revertBehaviours[FunctionType.CLAIM]); + } + + function _performRevertBehaviour(RevertType action) internal { + if(action == RevertType.THROW) { + revert("A normal Revert"); + } + + // 3 gas per iteration, consider changing to storage changes if traces are cluttered + if(action == RevertType.OOG) { + uint256 i; + while(true) { + ++i; + } + } + + if(action == RevertType.RETURN_BOMB) { + uint256 _bytes = 2_000_000; + assembly { + return(0, _bytes) + } + } + + if(action == RevertType.REVERT_BOMB) { + uint256 _bytes = 2_000_000; + assembly { + revert(0, _bytes) + } + } + + return; // NONE + } +} \ No newline at end of file From 08d897c9fe06ea7cccb390cea02ee11fd654b431 Mon Sep 17 00:00:00 2001 From: gallo Date: Tue, 8 Oct 2024 18:35:14 +0200 Subject: [PATCH 10/22] feat: eoa checks as well --- test/GovernanceAttacks.t.sol | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/test/GovernanceAttacks.t.sol b/test/GovernanceAttacks.t.sol index 191da6d..73a4b0a 100644 --- a/test/GovernanceAttacks.t.sol +++ b/test/GovernanceAttacks.t.sol @@ -43,14 +43,14 @@ contract GovernanceTest is Test { MaliciousInitiative private maliciousInitiative1; MaliciousInitiative private maliciousInitiative2; - MaliciousInitiative private maliciousInitiative3; + MaliciousInitiative private eoaInitiative; function setUp() public { vm.createSelectFork(vm.rpcUrl("mainnet"), 20430000); maliciousInitiative1 = new MaliciousInitiative(); maliciousInitiative2 = new MaliciousInitiative(); - maliciousInitiative3 = new MaliciousInitiative(); + eoaInitiative = MaliciousInitiative(address(0x123123123123)); initialInitiatives.push(address(maliciousInitiative1)); @@ -133,16 +133,22 @@ contract GovernanceTest is Test { maliciousInitiative2.setRevertBehaviour(MaliciousInitiative.FunctionType.REGISTER, MaliciousInitiative.RevertType.NONE); governance.registerInitiative(address(maliciousInitiative2)); + // Register EOA + governance.registerInitiative(address(eoaInitiative)); + + vm.stopPrank(); vm.warp(block.timestamp + governance.EPOCH_DURATION()); vm.startPrank(user); - address[] memory initiatives = new address[](1); + address[] memory initiatives = new address[](2); initiatives[0] = address(maliciousInitiative2); - int176[] memory deltaVoteLQTY = new int176[](1); + initiatives[1] = address(eoaInitiative); + int176[] memory deltaVoteLQTY = new int176[](2); deltaVoteLQTY[0] = 5e17; - int176[] memory deltaVetoLQTY = new int176[](1); + deltaVoteLQTY[1] = 5e17; + int176[] memory deltaVetoLQTY = new int176[](2); /// === Allocate LQTY REVERTS === /// uint256 allocateSnapshot = vm.snapshot(); @@ -191,19 +197,22 @@ contract GovernanceTest is Test { maliciousInitiative2.setRevertBehaviour(MaliciousInitiative.FunctionType.CLAIM, MaliciousInitiative.RevertType.NONE); uint256 claimed = governance.claimForInitiative(address(maliciousInitiative2)); - assertEq(claimed, 10001e18); + + governance.claimForInitiative(address(eoaInitiative)); /// === Unregister Reverts === /// vm.startPrank(user); - initiatives = new address[](2); + initiatives = new address[](3); initiatives[0] = address(maliciousInitiative2); - initiatives[1] = address(maliciousInitiative1); - deltaVoteLQTY = new int176[](2); + initiatives[1] = address(eoaInitiative); + initiatives[2] = address(maliciousInitiative1); + deltaVoteLQTY = new int176[](3); deltaVoteLQTY[0] = -5e17; - deltaVoteLQTY[1] = 5e17; - deltaVetoLQTY = new int176[](2); + deltaVoteLQTY[1] = -5e17; + deltaVoteLQTY[2] = 5e17; + deltaVetoLQTY = new int176[](3); governance.allocateLQTY(initiatives, deltaVoteLQTY, deltaVetoLQTY); (Governance.VoteSnapshot memory v, Governance.InitiativeVoteSnapshot memory initData) = governance.snapshotVotesForInitiative(address(maliciousInitiative2)); @@ -237,7 +246,10 @@ contract GovernanceTest is Test { maliciousInitiative2.setRevertBehaviour(MaliciousInitiative.FunctionType.UNREGISTER, MaliciousInitiative.RevertType.NONE); governance.unregisterInitiative(address(maliciousInitiative2)); + + governance.unregisterInitiative(address(eoaInitiative)); } + } From 6360513667968500ac7622e18c70bd2c56325afc Mon Sep 17 00:00:00 2001 From: jlqty <172397380+jltqy@users.noreply.github.com> Date: Wed, 9 Oct 2024 13:25:09 +0100 Subject: [PATCH 11/22] Further simplify onAllocateLQTY logic --- src/BribeInitiative.sol | 42 ++++++++++++++++------------------------- 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/src/BribeInitiative.sol b/src/BribeInitiative.sol index 1d261a3..8da4d51 100644 --- a/src/BribeInitiative.sol +++ b/src/BribeInitiative.sol @@ -176,7 +176,6 @@ contract BribeInitiative is IInitiative, IBribeInitiative { return _decodeLQTYAllocation(lqtyAllocationByUserAtEpoch[_user].items[_epoch].value); } - /// @inheritdoc IInitiative function onAfterAllocateLQTY( uint16 _currentEpoch, address _user, @@ -184,34 +183,25 @@ contract BribeInitiative is IInitiative, IBribeInitiative { IGovernance.Allocation calldata _allocation, IGovernance.InitiativeState calldata _initiativeState ) external virtual onlyGovernance { + if (_currentEpoch == 0) return; + uint16 mostRecentUserEpoch = lqtyAllocationByUserAtEpoch[_user].getHead(); + uint16 mostRecentTotalEpoch = totalLQTYAllocationByEpoch.getHead(); - if (_currentEpoch == 0) return; + _setTotalLQTYAllocationByEpoch( + _currentEpoch, + _initiativeState.voteLQTY, + _initiativeState.averageStakingTimestampVoteLQTY, + mostRecentTotalEpoch != _currentEpoch // Insert if current > recent + ); - // if this is the first user allocation in the epoch, then insert a new item into the user allocation DLL - if (mostRecentUserEpoch != _currentEpoch) { - uint16 mostRecentTotalEpoch = totalLQTYAllocationByEpoch.getHead(); - // if this is the first allocation in the epoch, then insert a new item into the total allocation DLL - if (mostRecentTotalEpoch != _currentEpoch) { - _setTotalLQTYAllocationByEpoch( - _currentEpoch, _initiativeState.voteLQTY, _initiativeState.averageStakingTimestampVoteLQTY, true - ); - } else { - _setTotalLQTYAllocationByEpoch( - _currentEpoch, _initiativeState.voteLQTY, _initiativeState.averageStakingTimestampVoteLQTY, false - ); - } - _setLQTYAllocationByUserAtEpoch( - _user, _currentEpoch, _allocation.voteLQTY, _userState.averageStakingTimestamp, true - ); - } else { - _setTotalLQTYAllocationByEpoch( - _currentEpoch, _initiativeState.voteLQTY, _initiativeState.averageStakingTimestampVoteLQTY, false - ); - _setLQTYAllocationByUserAtEpoch( - _user, _currentEpoch, _allocation.voteLQTY, _userState.averageStakingTimestamp, false - ); - } + _setLQTYAllocationByUserAtEpoch( + _user, + _currentEpoch, + _allocation.voteLQTY, + _userState.averageStakingTimestamp, + mostRecentUserEpoch != _currentEpoch // Insert if user current > recent + ); } /// @inheritdoc IInitiative From 3c1dd7e141c133f6ae3b9549820dfa0cfbb82376 Mon Sep 17 00:00:00 2001 From: jlqty <172397380+jltqy@users.noreply.github.com> Date: Wed, 9 Oct 2024 17:02:24 +0100 Subject: [PATCH 12/22] Update tests --- test/BribeInitiativeAllocate.t.sol | 361 +++++++++++++++++++++++------ 1 file changed, 291 insertions(+), 70 deletions(-) diff --git a/test/BribeInitiativeAllocate.t.sol b/test/BribeInitiativeAllocate.t.sol index bb62e5d..3bfa44f 100644 --- a/test/BribeInitiativeAllocate.t.sol +++ b/test/BribeInitiativeAllocate.t.sol @@ -595,100 +595,321 @@ contract BribeInitiativeAllocateTest is Test { bribeInitiative.claimBribes(claimData); } - // function test_onAfterAllocateLQTY_sameEpoch_NoVetoToVeto() public { - // vm.startPrank(lusdHolder); - // lqty.approve(address(bribeInitiative), 1000e18); - // lusd.approve(address(bribeInitiative), 1000e18); - // bribeInitiative.depositBribe(1000e18, 1000e18, governance.epoch() + 1); - // vm.stopPrank(); + function test_onAfterAllocateLQTY_sameEpoch_NoVetoToVeto() public { + vm.startPrank(lusdHolder); + lqty.approve(address(bribeInitiative), 1000e18); + lusd.approve(address(bribeInitiative), 1000e18); + bribeInitiative.depositBribe(1000e18, 1000e18, governance.epoch() + 1); + vm.stopPrank(); - // governance.setEpoch(1); + governance.setEpoch(1); - // vm.startPrank(address(governance)); + vm.startPrank(address(governance)); - // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user2, 1e18, 0); - // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); - // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()), 1e18); + { + IGovernance.UserState memory userState = + IGovernance.UserState({allocatedLQTY: 1e18, averageStakingTimestamp: uint32(block.timestamp)}); + IGovernance.Allocation memory allocation = + IGovernance.Allocation({voteLQTY: 1e18, vetoLQTY: 0, atEpoch: uint16(governance.epoch())}); + IGovernance.InitiativeState memory initiativeState = IGovernance.InitiativeState({ + voteLQTY: 1e18, + vetoLQTY: 0, + averageStakingTimestampVoteLQTY: uint32(block.timestamp), + averageStakingTimestampVetoLQTY: 0, + counted: 1 + }); + bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user2, userState, allocation, initiativeState); - // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 1000e18, 0); - // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1001e18); - // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 1000e18); + (uint88 totalLQTYAllocated, uint32 totalAverageTimestamp) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + assertEq(totalLQTYAllocated, 1e18); + assertEq(totalAverageTimestamp, uint32(block.timestamp)); + (uint88 userLQTYAllocated, uint32 userAverageTimestamp) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()); + assertEq(userLQTYAllocated, 1e18); + assertEq(userAverageTimestamp, uint32(block.timestamp)); + } - // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 2000e18, 1); - // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); - // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 0); + { + IGovernance.UserState memory userState = + IGovernance.UserState({allocatedLQTY: 1000e18, averageStakingTimestamp: uint32(block.timestamp)}); + IGovernance.Allocation memory allocation = + IGovernance.Allocation({voteLQTY: 1000e18, vetoLQTY: 0, atEpoch: uint16(governance.epoch())}); + IGovernance.InitiativeState memory initiativeState = IGovernance.InitiativeState({ + voteLQTY: 1001e18, + vetoLQTY: 0, + averageStakingTimestampVoteLQTY: uint32(block.timestamp), + averageStakingTimestampVetoLQTY: 0, + counted: 1 + }); + bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, userState, allocation, initiativeState); - // governance.setEpoch(2); + (uint88 totalLQTYAllocated, uint32 totalAverageTimestamp) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + assertEq(totalLQTYAllocated, 1001e18); + assertEq(totalAverageTimestamp, uint32(block.timestamp)); + (uint88 userLQTYAllocated, uint32 userAverageTimestamp) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()); + assertEq(userLQTYAllocated, 1000e18); + assertEq(userAverageTimestamp, uint32(block.timestamp)); + } - // vm.startPrank(address(user)); + { + IGovernance.UserState memory userState = + IGovernance.UserState({allocatedLQTY: 1, averageStakingTimestamp: uint32(block.timestamp)}); + IGovernance.Allocation memory allocation = + IGovernance.Allocation({voteLQTY: 0, vetoLQTY: 1, atEpoch: uint16(governance.epoch())}); + IGovernance.InitiativeState memory initiativeState = IGovernance.InitiativeState({ + voteLQTY: 1e18, + vetoLQTY: 0, + averageStakingTimestampVoteLQTY: uint32(block.timestamp), + averageStakingTimestampVetoLQTY: 0, + counted: 1 + }); + bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, userState, allocation, initiativeState); - // BribeInitiative.ClaimData[] memory claimData = new BribeInitiative.ClaimData[](1); - // claimData[0].epoch = 1; - // claimData[0].prevLQTYAllocationEpoch = 1; - // claimData[0].prevTotalLQTYAllocationEpoch = 1; - // vm.expectRevert("BribeInitiative: invalid-prev-lqty-allocation-epoch"); // nothing to claim - // bribeInitiative.claimBribes(claimData); - // } + (uint88 totalLQTYAllocated, uint32 totalAverageTimestamp) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + assertEq(totalLQTYAllocated, 1e18); + assertEq(totalAverageTimestamp, uint32(block.timestamp)); + (uint88 userLQTYAllocated, uint32 userAverageTimestamp) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()); + assertEq(userLQTYAllocated, 0); + assertEq(userAverageTimestamp, uint32(block.timestamp)); + } - // function test_onAfterAllocateLQTY_sameEpoch_VetoToNoVeto() public { - // vm.startPrank(lusdHolder); - // lqty.approve(address(bribeInitiative), 1000e18); - // lusd.approve(address(bribeInitiative), 1000e18); - // bribeInitiative.depositBribe(1000e18, 1000e18, governance.epoch() + 1); - // vm.stopPrank(); + governance.setEpoch(2); - // governance.setEpoch(1); + vm.startPrank(address(user)); - // vm.startPrank(address(governance)); + BribeInitiative.ClaimData[] memory claimData = new BribeInitiative.ClaimData[](1); + claimData[0].epoch = 1; + claimData[0].prevLQTYAllocationEpoch = 1; + claimData[0].prevTotalLQTYAllocationEpoch = 1; + (uint256 boldAmount, uint256 bribeTokenAmount) = bribeInitiative.claimBribes(claimData); + assertEq(boldAmount, 0); + assertEq(bribeTokenAmount, 0); + } - // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user2, 1e18, 0); - // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); - // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()), 1e18); + function test_onAfterAllocateLQTY_sameEpoch_VetoToNoVeto() public { + vm.startPrank(lusdHolder); + lqty.approve(address(bribeInitiative), 1000e18); + lusd.approve(address(bribeInitiative), 1000e18); + bribeInitiative.depositBribe(1000e18, 1000e18, governance.epoch() + 1); + vm.stopPrank(); - // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 1000e18, 0); - // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1001e18); - // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 1000e18); + governance.setEpoch(1); - // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 2000e18, 1); - // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); - // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 0); + vm.startPrank(address(governance)); - // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 2000e18, 0); - // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 2001e18); - // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 2000e18); + { + IGovernance.UserState memory userState = + IGovernance.UserState({allocatedLQTY: 1e18, averageStakingTimestamp: uint32(block.timestamp)}); + IGovernance.Allocation memory allocation = + IGovernance.Allocation({voteLQTY: 1e18, vetoLQTY: 0, atEpoch: uint16(governance.epoch())}); + IGovernance.InitiativeState memory initiativeState = IGovernance.InitiativeState({ + voteLQTY: 1e18, + vetoLQTY: 0, + averageStakingTimestampVoteLQTY: uint32(block.timestamp), + averageStakingTimestampVetoLQTY: 0, + counted: 1 + }); + bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user2, userState, allocation, initiativeState); - // governance.setEpoch(2); + (uint88 totalLQTYAllocated, uint32 totalAverageTimestamp) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + assertEq(totalLQTYAllocated, 1e18); + assertEq(totalAverageTimestamp, uint32(block.timestamp)); + (uint88 userLQTYAllocated, uint32 userAverageTimestamp) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()); + assertEq(userLQTYAllocated, 1e18); + assertEq(userAverageTimestamp, uint32(block.timestamp)); + } - // vm.startPrank(address(user)); + { + IGovernance.UserState memory userState = + IGovernance.UserState({allocatedLQTY: 1000e18, averageStakingTimestamp: uint32(block.timestamp)}); + IGovernance.Allocation memory allocation = + IGovernance.Allocation({voteLQTY: 1000e18, vetoLQTY: 0, atEpoch: uint16(governance.epoch())}); + IGovernance.InitiativeState memory initiativeState = IGovernance.InitiativeState({ + voteLQTY: 1001e18, + vetoLQTY: 0, + averageStakingTimestampVoteLQTY: uint32(block.timestamp), + averageStakingTimestampVetoLQTY: 0, + counted: 1 + }); + bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, userState, allocation, initiativeState); - // BribeInitiative.ClaimData[] memory claimData = new BribeInitiative.ClaimData[](1); - // claimData[0].epoch = 1; - // claimData[0].prevLQTYAllocationEpoch = 1; - // claimData[0].prevTotalLQTYAllocationEpoch = 1; - // bribeInitiative.claimBribes(claimData); - // } + (uint88 totalLQTYAllocated, uint32 totalAverageTimestamp) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + assertEq(totalLQTYAllocated, 1001e18); + assertEq(totalAverageTimestamp, uint32(block.timestamp)); + (uint88 userLQTYAllocated, uint32 userAverageTimestamp) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()); + assertEq(userLQTYAllocated, 1000e18); + assertEq(userAverageTimestamp, uint32(block.timestamp)); + } - // function test_onAfterAllocateLQTY_sameEpoch_VetoToVeto() public { - // governance.setEpoch(1); + { + IGovernance.UserState memory userState = + IGovernance.UserState({allocatedLQTY: 1, averageStakingTimestamp: uint32(block.timestamp)}); + IGovernance.Allocation memory allocation = + IGovernance.Allocation({voteLQTY: 0, vetoLQTY: 1, atEpoch: uint16(governance.epoch())}); + IGovernance.InitiativeState memory initiativeState = IGovernance.InitiativeState({ + voteLQTY: 1e18, + vetoLQTY: 0, + averageStakingTimestampVoteLQTY: uint32(block.timestamp), + averageStakingTimestampVetoLQTY: 0, + counted: 1 + }); + bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, userState, allocation, initiativeState); - // vm.startPrank(address(governance)); + (uint88 totalLQTYAllocated, uint32 totalAverageTimestamp) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + assertEq(totalLQTYAllocated, 1e18); + assertEq(totalAverageTimestamp, uint32(block.timestamp)); + (uint88 userLQTYAllocated, uint32 userAverageTimestamp) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()); + assertEq(userLQTYAllocated, 0); + assertEq(userAverageTimestamp, uint32(block.timestamp)); + } - // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user2, 1e18, 0); - // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); - // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()), 1e18); + { + IGovernance.UserState memory userState = + IGovernance.UserState({allocatedLQTY: 2000e18, averageStakingTimestamp: uint32(block.timestamp)}); + IGovernance.Allocation memory allocation = + IGovernance.Allocation({voteLQTY: 2000e18, vetoLQTY: 0, atEpoch: uint16(governance.epoch())}); + IGovernance.InitiativeState memory initiativeState = IGovernance.InitiativeState({ + voteLQTY: 2001e18, + vetoLQTY: 0, + averageStakingTimestampVoteLQTY: uint32(block.timestamp), + averageStakingTimestampVetoLQTY: 0, + counted: 1 + }); + bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, userState, allocation, initiativeState); - // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 1000e18, 0); - // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1001e18); - // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 1000e18); + (uint88 totalLQTYAllocated, uint32 totalAverageTimestamp) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + assertEq(totalLQTYAllocated, 2001e18); + assertEq(totalAverageTimestamp, uint32(block.timestamp)); + (uint88 userLQTYAllocated, uint32 userAverageTimestamp) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()); + assertEq(userLQTYAllocated, 2000e18); + assertEq(userAverageTimestamp, uint32(block.timestamp)); + } + + governance.setEpoch(2); - // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 2000e18, 1); - // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); - // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 0); + vm.startPrank(address(user)); - // bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, 1000e18, 1); - // assertEq(bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()), 1e18); - // assertEq(bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()), 0); - // } + BribeInitiative.ClaimData[] memory claimData = new BribeInitiative.ClaimData[](1); + claimData[0].epoch = 1; + claimData[0].prevLQTYAllocationEpoch = 1; + claimData[0].prevTotalLQTYAllocationEpoch = 1; + bribeInitiative.claimBribes(claimData); + } + + function test_onAfterAllocateLQTY_sameEpoch_VetoToVeto() public { + governance.setEpoch(1); + + vm.startPrank(address(governance)); + + { + IGovernance.UserState memory userState = + IGovernance.UserState({allocatedLQTY: 1e18, averageStakingTimestamp: uint32(block.timestamp)}); + IGovernance.Allocation memory allocation = + IGovernance.Allocation({voteLQTY: 1e18, vetoLQTY: 0, atEpoch: uint16(governance.epoch())}); + IGovernance.InitiativeState memory initiativeState = IGovernance.InitiativeState({ + voteLQTY: 1e18, + vetoLQTY: 0, + averageStakingTimestampVoteLQTY: uint32(block.timestamp), + averageStakingTimestampVetoLQTY: 0, + counted: 1 + }); + bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user2, userState, allocation, initiativeState); + + (uint88 totalLQTYAllocated, uint32 totalAverageTimestamp) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + assertEq(totalLQTYAllocated, 1e18); + assertEq(totalAverageTimestamp, uint32(block.timestamp)); + (uint88 userLQTYAllocated, uint32 userAverageTimestamp) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()); + assertEq(userLQTYAllocated, 1e18); + assertEq(userAverageTimestamp, uint32(block.timestamp)); + } + + { + IGovernance.UserState memory userState = + IGovernance.UserState({allocatedLQTY: 1000e18, averageStakingTimestamp: uint32(block.timestamp)}); + IGovernance.Allocation memory allocation = + IGovernance.Allocation({voteLQTY: 1000e18, vetoLQTY: 0, atEpoch: uint16(governance.epoch())}); + IGovernance.InitiativeState memory initiativeState = IGovernance.InitiativeState({ + voteLQTY: 1001e18, + vetoLQTY: 0, + averageStakingTimestampVoteLQTY: uint32(block.timestamp), + averageStakingTimestampVetoLQTY: 0, + counted: 1 + }); + bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, userState, allocation, initiativeState); + + (uint88 totalLQTYAllocated, uint32 totalAverageTimestamp) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + assertEq(totalLQTYAllocated, 1001e18); + assertEq(totalAverageTimestamp, uint32(block.timestamp)); + (uint88 userLQTYAllocated, uint32 userAverageTimestamp) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()); + assertEq(userLQTYAllocated, 1000e18); + assertEq(userAverageTimestamp, uint32(block.timestamp)); + } + + { + IGovernance.UserState memory userState = + IGovernance.UserState({allocatedLQTY: 1, averageStakingTimestamp: uint32(block.timestamp)}); + IGovernance.Allocation memory allocation = + IGovernance.Allocation({voteLQTY: 0, vetoLQTY: 1, atEpoch: uint16(governance.epoch())}); + IGovernance.InitiativeState memory initiativeState = IGovernance.InitiativeState({ + voteLQTY: 1e18, + vetoLQTY: 0, + averageStakingTimestampVoteLQTY: uint32(block.timestamp), + averageStakingTimestampVetoLQTY: 0, + counted: 1 + }); + bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, userState, allocation, initiativeState); + + (uint88 totalLQTYAllocated, uint32 totalAverageTimestamp) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + assertEq(totalLQTYAllocated, 1e18); + assertEq(totalAverageTimestamp, uint32(block.timestamp)); + (uint88 userLQTYAllocated, uint32 userAverageTimestamp) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()); + assertEq(userLQTYAllocated, 0); + assertEq(userAverageTimestamp, uint32(block.timestamp)); + } + + { + IGovernance.UserState memory userState = + IGovernance.UserState({allocatedLQTY: 2, averageStakingTimestamp: uint32(block.timestamp)}); + IGovernance.Allocation memory allocation = + IGovernance.Allocation({voteLQTY: 0, vetoLQTY: 2, atEpoch: uint16(governance.epoch())}); + IGovernance.InitiativeState memory initiativeState = IGovernance.InitiativeState({ + voteLQTY: 1e18, + vetoLQTY: 0, + averageStakingTimestampVoteLQTY: uint32(block.timestamp), + averageStakingTimestampVetoLQTY: 0, + counted: 1 + }); + bribeInitiative.onAfterAllocateLQTY(governance.epoch(), user, userState, allocation, initiativeState); + + (uint88 totalLQTYAllocated, uint32 totalAverageTimestamp) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + assertEq(totalLQTYAllocated, 1e18); + assertEq(totalAverageTimestamp, uint32(block.timestamp)); + (uint88 userLQTYAllocated, uint32 userAverageTimestamp) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()); + assertEq(userLQTYAllocated, 0); + assertEq(userAverageTimestamp, uint32(block.timestamp)); + } + } // function test_onAfterAllocateLQTY() public { // governance.setEpoch(1); From faed8534a6b41ea95240a21b33e1ed37663a8168 Mon Sep 17 00:00:00 2001 From: nelson-pereira8 <94120714+nican0r@users.noreply.github.com> Date: Wed, 9 Oct 2024 21:05:31 -0300 Subject: [PATCH 13/22] chore: tests for BribeInitiative --- test/BribeInitiative.t.sol | 87 +++++++++++++++++++++++++++++++++++--- 1 file changed, 82 insertions(+), 5 deletions(-) diff --git a/test/BribeInitiative.t.sol b/test/BribeInitiative.t.sol index 35ad992..7ca6ee6 100644 --- a/test/BribeInitiative.t.sol +++ b/test/BribeInitiative.t.sol @@ -29,7 +29,7 @@ contract BribeInitiativeTest is Test { uint128 private constant VOTING_THRESHOLD_FACTOR = 0.04e18; uint88 private constant MIN_CLAIM = 500e18; uint88 private constant MIN_ACCRUAL = 1000e18; - uint32 private constant EPOCH_DURATION = 604800; + uint32 private constant EPOCH_DURATION = 7 days; // 7 days uint32 private constant EPOCH_VOTING_CUTOFF = 518400; Governance private governance; @@ -41,8 +41,8 @@ contract BribeInitiativeTest is Test { lqty = deployMockERC20("Liquity", "LQTY", 18); lusd = deployMockERC20("Liquity USD", "LUSD", 18); - vm.store(address(lqty), keccak256(abi.encode(address(lusdHolder), 4)), bytes32(abi.encode(10000e18))); - vm.store(address(lusd), keccak256(abi.encode(address(lusdHolder), 4)), bytes32(abi.encode(10000e18))); + vm.store(address(lqty), keccak256(abi.encode(address(lusdHolder), 4)), bytes32(abi.encode(10_000_000e18))); + vm.store(address(lusd), keccak256(abi.encode(address(lusdHolder), 4)), bytes32(abi.encode(10_000_000e18))); stakingV1 = address(new MockStakingV1(address(lqty))); @@ -76,11 +76,64 @@ contract BribeInitiativeTest is Test { ); vm.startPrank(lusdHolder); - lqty.transfer(user, 1e18); - lusd.transfer(user, 1e18); + lqty.transfer(user, 1_000_000e18); + lusd.transfer(user, 1_000_000e18); vm.stopPrank(); } + // test total allocation vote case + function test_totalLQTYAllocatedByEpoch_vote() public { + // staking LQTY into governance for user in first epoch + _stakeLQTY(user, 10e18); + + // fast forward to second epoch + vm.warp(block.timestamp + EPOCH_DURATION); + + // allocate LQTY to the bribeInitiative + _allocateLQTY(user, 10e18, 0); + // total LQTY allocated for this epoch should increase + (uint88 totalLQTYAllocated, ) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + assertEq(totalLQTYAllocated, 10e18); + } + + // test total allocation veto case + function test_totalLQTYAllocatedByEpoch_veto() public { + _stakeLQTY(user, 10e18); + + // fast forward to second epoch + vm.warp(block.timestamp + EPOCH_DURATION); + + // allocate LQTY to the bribeInitiative + _allocateLQTY(user, 0, 10e18); + // total LQTY allocated for this epoch should not increase + (uint88 totalLQTYAllocated, ) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + assertEq(totalLQTYAllocated, 0); + } + + // TODO: test total allocations at start/end of epoch + + // user tries to allocate multiple times in different epochs + function test_allocating_same_initiative_multiple_epochs_reverts() public { + _stakeLQTY(user, 10e18); + + // fast forward to second epoch + vm.warp(block.timestamp + EPOCH_DURATION); + + // allocate LQTY to the bribeInitiative + _allocateLQTY(user, 10e18, 0); + // total LQTY allocated for this epoch should increase + (uint88 totalLQTYAllocated1, ) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + + // fast forward to third epoch + vm.warp(block.timestamp + EPOCH_DURATION); + + vm.expectRevert(); + _allocateLQTY(user, 10e18, 0); + } + function test_claimBribes() public { vm.startPrank(user); address userProxy = governance.deployUserProxy(); @@ -156,4 +209,28 @@ contract BribeInitiativeTest is Test { assertEq(totalLQTYAllocated, 0); vm.stopPrank(); } + + function _stakeLQTY(address staker, uint88 amount) public { + vm.startPrank(staker); + address userProxy = governance.deployUserProxy(); + lqty.approve(address(userProxy), amount); + governance.depositLQTY(amount); + vm.stopPrank(); + } + + function _allocateLQTY(address staker, int176 deltaVoteLQTYAmt, int176 deltaVetoLQTYAmt) public { + vm.startPrank(staker); + address[] memory initiatives = new address[](1); + initiatives[0] = address(bribeInitiative); + + // voting in favor of the initiative with half of user's stake + int176[] memory deltaVoteLQTY = new int176[](1); + deltaVoteLQTY[0] = deltaVoteLQTYAmt; + + int176[] memory deltaVetoLQTY = new int176[](1); + deltaVetoLQTY[0] = deltaVetoLQTYAmt; + + governance.allocateLQTY(initiatives, deltaVoteLQTY, deltaVetoLQTY); + vm.stopPrank(); + } } From 8eb5bb28549f46ef173b312905bbfafbc7ad299b Mon Sep 17 00:00:00 2001 From: nelson-pereira8 <94120714+nican0r@users.noreply.github.com> Date: Wed, 9 Oct 2024 21:06:09 -0300 Subject: [PATCH 14/22] chore: fix revert statement in allocateLQTY --- src/Governance.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Governance.sol b/src/Governance.sol index a51b443..7e2f860 100644 --- a/src/Governance.sol +++ b/src/Governance.sol @@ -485,7 +485,7 @@ contract Governance is Multicall, UserProxyFactory, ReentrancyGuard, IGovernance require( userState.allocatedLQTY == 0 || userState.allocatedLQTY <= uint88(stakingV1.stakes(deriveUserProxyAddress(msg.sender))), - "Governance: insufficient-or-unallocated-lqty" + "Governance: insufficient-or-allocated-lqty" ); globalState = state; From bcf897e5fcea601178b14a2973b5c6d59f7e951b Mon Sep 17 00:00:00 2001 From: gallo Date: Thu, 10 Oct 2024 18:33:12 +0200 Subject: [PATCH 15/22] fix: always use `safeTransfer` --- src/UserProxy.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UserProxy.sol b/src/UserProxy.sol index 86f4a8c..38d557c 100644 --- a/src/UserProxy.sol +++ b/src/UserProxy.sol @@ -36,7 +36,7 @@ contract UserProxy is IUserProxy { /// @inheritdoc IUserProxy function stake(uint256 _amount, address _lqtyFrom) public onlyStakingV2 { - lqty.transferFrom(_lqtyFrom, address(this), _amount); + lqty.safeTransferFrom(_lqtyFrom, address(this), _amount); lqty.approve(address(stakingV1), _amount); stakingV1.stake(_amount); emit Stake(_amount, _lqtyFrom); From 5812afed647de54a9de04a9d82c6e57da8283557 Mon Sep 17 00:00:00 2001 From: gallo Date: Thu, 10 Oct 2024 18:43:13 +0200 Subject: [PATCH 16/22] fix: counted ts fix --- src/Governance.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Governance.sol b/src/Governance.sol index ef5f850..7c9a8c9 100644 --- a/src/Governance.sol +++ b/src/Governance.sol @@ -456,7 +456,7 @@ contract Governance is Multicall, UserProxyFactory, ReentrancyGuard, IGovernance if (prevInitiativeState.counted == 1) { state.countedVoteLQTYAverageTimestamp = _calculateAverageTimestamp( state.countedVoteLQTYAverageTimestamp, - initiativeState.averageStakingTimestampVoteLQTY, + prevInitiativeState.averageStakingTimestampVoteLQTY, state.countedVoteLQTY, state.countedVoteLQTY - prevInitiativeState.voteLQTY ); From ca5ab958af7564225d7b104bad26718c77a9d938 Mon Sep 17 00:00:00 2001 From: Colin Platt Date: Mon, 7 Oct 2024 17:42:14 +0200 Subject: [PATCH 17/22] basic forwarder --- .gitmodules | 3 +++ lib/solmate | 1 + src/ForwardBribe.sol | 24 ++++++++++++++++++++++++ 3 files changed, 28 insertions(+) create mode 160000 lib/solmate create mode 100644 src/ForwardBribe.sol diff --git a/.gitmodules b/.gitmodules index 5e007a6..ee9c92b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "lib/v4-core"] path = lib/v4-core url = https://github.com/Uniswap/v4-core +[submodule "lib/solmate"] + path = lib/solmate + url = git@github.com:Transmissions11/solmate diff --git a/lib/solmate b/lib/solmate new file mode 160000 index 0000000..97bdb20 --- /dev/null +++ b/lib/solmate @@ -0,0 +1 @@ +Subproject commit 97bdb2003b70382996a79a406813f76417b1cf90 diff --git a/src/ForwardBribe.sol b/src/ForwardBribe.sol new file mode 100644 index 0000000..234c740 --- /dev/null +++ b/src/ForwardBribe.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {BribeInitiative} from "./BribeInitiative.sol"; + +contract ForwardBribe is BribeInitiative { + address public immutable receiver; + + constructor(address _governance, address _bold, address _bribeToken, address _receiver) + BribeInitiative(_governance, _bold, _bribeToken) + { + receiver = _receiver; + } + + function forwardBribe() external { + governance.claimForInitiative(address(this)); + + uint boldAmount = bold.balanceOf(address(this)); + uint bribeTokenAmount = bribeToken.balanceOf(address(this)); + + if (boldAmount != 0) bold.safeTransfer(receiver, boldAmount); + if (bribeTokenAmount != 0) bribeToken.safeTransfer(receiver, bribeTokenAmount); + } +} From e92c181cbbaebfc6bcc303411c5c40d0c87da324 Mon Sep 17 00:00:00 2001 From: nelson-pereira8 <94120714+nican0r@users.noreply.github.com> Date: Thu, 10 Oct 2024 13:35:12 -0300 Subject: [PATCH 18/22] test: BribeInitiative --- test/BribeInitiative.t.sol | 352 +++++++++++++++++++++++++++++-------- 1 file changed, 279 insertions(+), 73 deletions(-) diff --git a/test/BribeInitiative.t.sol b/test/BribeInitiative.t.sol index 7ca6ee6..fc2245e 100644 --- a/test/BribeInitiative.t.sol +++ b/test/BribeInitiative.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.24; -import {Test} from "forge-std/Test.sol"; +import {Test, console2} from "forge-std/Test.sol"; import {MockERC20} from "forge-std/mocks/MockERC20.sol"; import {IGovernance} from "../src/interfaces/IGovernance.sol"; @@ -11,11 +11,13 @@ import {BribeInitiative} from "../src/BribeInitiative.sol"; import {MockStakingV1} from "./mocks/MockStakingV1.sol"; +// coverage: forge coverage --mc BribeInitiativeTest --report lcov contract BribeInitiativeTest is Test { MockERC20 private lqty; MockERC20 private lusd; address private stakingV1; - address private constant user = address(0xF977814e90dA44bFA03b6295A0616a897441aceC); + address private constant user1 = address(0xF977814e90dA44bFA03b6295A0616a897441aceC); + address private constant user2 = address(0x10C9cff3c4Faa8A60cB8506a7A99411E6A199038); address private constant lusdHolder = address(0xcA7f01403C4989d2b1A9335A2F09dD973709957c); address private constant initiative = address(0x1); address private constant initiative2 = address(0x2); @@ -41,6 +43,8 @@ contract BribeInitiativeTest is Test { lqty = deployMockERC20("Liquity", "LQTY", 18); lusd = deployMockERC20("Liquity USD", "LUSD", 18); + vm.store(address(lqty), keccak256(abi.encode(address(lusdHolder), 4)), bytes32(abi.encode(10_000_000e18))); + vm.store(address(lusd), keccak256(abi.encode(address(lusdHolder), 4)), bytes32(abi.encode(10_000_000e18))); vm.store(address(lqty), keccak256(abi.encode(address(lusdHolder), 4)), bytes32(abi.encode(10_000_000e18))); vm.store(address(lusd), keccak256(abi.encode(address(lusdHolder), 4)), bytes32(abi.encode(10_000_000e18))); @@ -76,99 +80,309 @@ contract BribeInitiativeTest is Test { ); vm.startPrank(lusdHolder); - lqty.transfer(user, 1_000_000e18); - lusd.transfer(user, 1_000_000e18); + lqty.transfer(user1, 1_000_000e18); + lusd.transfer(user1, 1_000_000e18); + lqty.transfer(user2, 1_000_000e18); + lusd.transfer(user2, 1_000_000e18); vm.stopPrank(); } // test total allocation vote case function test_totalLQTYAllocatedByEpoch_vote() public { - // staking LQTY into governance for user in first epoch - _stakeLQTY(user, 10e18); + // staking LQTY into governance for user1 in first epoch + _stakeLQTY(user1, 10e18); // fast forward to second epoch vm.warp(block.timestamp + EPOCH_DURATION); // allocate LQTY to the bribeInitiative - _allocateLQTY(user, 10e18, 0); + _allocateLQTY(user1, 10e18, 0); // total LQTY allocated for this epoch should increase - (uint88 totalLQTYAllocated, ) = + (uint88 totalLQTYAllocated) = bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); assertEq(totalLQTYAllocated, 10e18); } // test total allocation veto case function test_totalLQTYAllocatedByEpoch_veto() public { - _stakeLQTY(user, 10e18); + _stakeLQTY(user1, 10e18); // fast forward to second epoch vm.warp(block.timestamp + EPOCH_DURATION); - // allocate LQTY to the bribeInitiative - _allocateLQTY(user, 0, 10e18); + // allocate LQTY to veto bribeInitiative + _allocateLQTY(user1, 0, 10e18); // total LQTY allocated for this epoch should not increase - (uint88 totalLQTYAllocated, ) = + (uint88 totalLQTYAllocated) = bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); assertEq(totalLQTYAllocated, 0); } - // TODO: test total allocations at start/end of epoch - - // user tries to allocate multiple times in different epochs - function test_allocating_same_initiative_multiple_epochs_reverts() public { - _stakeLQTY(user, 10e18); + // user1 allocates multiple times in different epochs + function test_allocating_same_initiative_multiple_epochs() public { + _stakeLQTY(user1, 10e18); // fast forward to second epoch vm.warp(block.timestamp + EPOCH_DURATION); // allocate LQTY to the bribeInitiative - _allocateLQTY(user, 10e18, 0); + _allocateLQTY(user1, 5e18, 0); + // total LQTY allocated for this epoch should increase - (uint88 totalLQTYAllocated1, ) = + (uint88 totalLQTYAllocated1) = bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + (uint88 userLQTYAllocated1) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user1, governance.epoch()); + assertEq(totalLQTYAllocated1, 5e18); + assertEq(userLQTYAllocated1, 5e18); // fast forward to third epoch vm.warp(block.timestamp + EPOCH_DURATION); - vm.expectRevert(); - _allocateLQTY(user, 10e18, 0); + _allocateLQTY(user1, 5e18, 0); + + // total LQTY allocated for this epoch should not change + (uint88 totalLQTYAllocated2) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + (uint88 userLQTYAllocated2) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user1, governance.epoch()); + assertEq(totalLQTYAllocated2, 10e18); + assertEq(userLQTYAllocated1, 5e18); + } - function test_claimBribes() public { - vm.startPrank(user); - address userProxy = governance.deployUserProxy(); - lqty.approve(address(userProxy), 1e18); - governance.depositLQTY(1e18); - vm.stopPrank(); + // user1 allocates multiple times in same epoch + function test_totalLQTYAllocatedByEpoch_vote_same_epoch() public { + _stakeLQTY(user1, 10e18); + + vm.warp(block.timestamp + EPOCH_DURATION); + + // user1 allocates in first epoch + _allocateLQTY(user1, 5e18, 0); + (uint88 totalLQTYAllocated1) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + (uint88 userLQTYAllocated1) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user1, governance.epoch()); + assertEq(totalLQTYAllocated1, 5e18); + assertEq(userLQTYAllocated1, 5e18); + + // vm.warp(block.timestamp + EPOCH_DURATION); + + _allocateLQTY(user1, 5e18, 0); + (uint88 totalLQTYAllocated2) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + (uint88 userLQTYAllocated2) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user1, governance.epoch()); + assertEq(totalLQTYAllocated2, 10e18); + assertEq(userLQTYAllocated2, 10e18); + } - vm.warp(block.timestamp + 365 days); + function test_allocation_stored_in_list() public { + _stakeLQTY(user1, 10e18); + vm.warp(block.timestamp + EPOCH_DURATION); + + // user1 allocates in first epoch + _allocateLQTY(user1, 5e18, 0); + (uint88 totalLQTYAllocated1) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + (uint88 userLQTYAllocated1) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user1, governance.epoch()); + assertEq(totalLQTYAllocated1, 5e18); + assertEq(userLQTYAllocated1, 5e18); + + console2.log("current governance epoch: ", governance.epoch()); + // user's linked-list should be updated to have a value for the current epoch + uint88 allocatedAtEpoch = bribeInitiative.lqtyAllocatedByUserAtEpoch(user1, governance.epoch()); + console2.log("allocatedAtEpoch: ", allocatedAtEpoch); + } + + // test total allocation by multiple users in multiple epochs + function test_totalLQTYAllocatedByEpoch_vote_multiple_epochs() public { + _stakeLQTY(user1, 10e18); + _stakeLQTY(user2, 10e18); + + vm.warp(block.timestamp + EPOCH_DURATION); + + // user1 allocates in first epoch + _allocateLQTY(user1, 10e18, 0); + (uint88 totalLQTYAllocated1) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + (uint88 userLQTYAllocated1) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user1, governance.epoch()); + assertEq(totalLQTYAllocated1, 10e18); + assertEq(userLQTYAllocated1, 10e18); + + // user2 allocates in second epoch + vm.warp(block.timestamp + EPOCH_DURATION); + + // user allocations should be disjoint because they're in separate epochs + _allocateLQTY(user2, 10e18, 0); + (uint88 totalLQTYAllocated2) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + (uint88 userLQTYAllocated2) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()); + assertEq(totalLQTYAllocated2, 20e18); + assertEq(userLQTYAllocated2, 10e18); + + } + + // test total allocations for multiple users in the same epoch + function test_totalLQTYAllocatedByEpoch_vote_same_epoch_multiple() public { + _stakeLQTY(user1, 10e18); + _stakeLQTY(user2, 10e18); + + vm.warp(block.timestamp + EPOCH_DURATION); + + // user1 allocates in first epoch + _allocateLQTY(user1, 10e18, 0); + (uint88 totalLQTYAllocated1) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + (uint88 userLQTYAllocated1) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user1, governance.epoch()); + assertEq(totalLQTYAllocated1, 10e18); + assertEq(userLQTYAllocated1, 10e18); + + _allocateLQTY(user2, 10e18, 0); + (uint88 totalLQTYAllocated2) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + (uint88 userLQTYAllocated2) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()); + assertEq(totalLQTYAllocated2, 20e18); + assertEq(userLQTYAllocated2, 10e18); + } + + // test total allocation doesn't grow from start to end of epoch + function test_totalLQTYAllocatedByEpoch_growth() public { + _stakeLQTY(user1, 10e18); + _stakeLQTY(user2, 10e18); + + vm.warp(block.timestamp + EPOCH_DURATION); + + // user1 allocates in first epoch + _allocateLQTY(user1, 10e18, 0); + (uint88 totalLQTYAllocated1) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + assertEq(totalLQTYAllocated1, 10e18); + + // warp to the end of the epoch + vm.warp(block.timestamp + (EPOCH_VOTING_CUTOFF - 1)); + + (uint88 totalLQTYAllocated2) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + assertEq(totalLQTYAllocated2, 10e18); + } + + // test depositing bribe + function test_depositBribe_success() public { vm.startPrank(lusdHolder); lqty.approve(address(bribeInitiative), 1e18); lusd.approve(address(bribeInitiative), 1e18); bribeInitiative.depositBribe(1e18, 1e18, governance.epoch() + 1); vm.stopPrank(); + } - vm.startPrank(user); + function test_claimBribes() public { + _stakeLQTY(user1, 1e18); - vm.warp(block.timestamp + 365 days); + vm.warp(block.timestamp + EPOCH_DURATION); - address[] memory initiatives = new address[](1); - initiatives[0] = address(bribeInitiative); - int176[] memory deltaVoteLQTY = new int176[](1); - deltaVoteLQTY[0] = 1e18; - int176[] memory deltaVetoLQTY = new int176[](1); - governance.allocateLQTY(initiatives, deltaVoteLQTY, deltaVetoLQTY); - (uint88 totalLQTYAllocated, uint32 averageTimestamp) = + _depositBribe(1e18, 1e18, governance.epoch() + 1); + + vm.warp(block.timestamp + EPOCH_DURATION); + + _allocateLQTY(user1, 1e18, 0); + + (uint88 totalLQTYAllocated) = bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + (uint88 userLQTYAllocated) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user1, governance.epoch()); assertEq(totalLQTYAllocated, 1e18); - assertEq(averageTimestamp, block.timestamp - 365 days - 365 days); - (uint88 userLQTYAllocated, uint32 userAverageTimestamp) = - bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()); assertEq(userLQTYAllocated, 1e18); - assertEq(userAverageTimestamp, block.timestamp - 365 days - 365 days); - // should be zero since user was not deposited at that time + // deposit bribe + _depositBribe(1e18, 1e18, governance.epoch() + 1); + vm.warp(block.timestamp + (EPOCH_DURATION * 2)); + + // user should receive bribe from their allocated stake + (uint256 boldAmount, uint256 bribeTokenAmount) = _claimBribe(user1, governance.epoch() - 1, governance.epoch() - 2, governance.epoch() - 2); + assertEq(boldAmount, 1e18); + assertEq(bribeTokenAmount, 1e18); + } + + function test_decrement_after_claimBribes() public { + _stakeLQTY(user1, 1e18); + + vm.warp(block.timestamp + EPOCH_DURATION); + + _depositBribe(1e18, 1e18, governance.epoch() + 1); + + vm.warp(block.timestamp + EPOCH_DURATION); + + _allocateLQTY(user1, 1e18, 0); + + (uint88 totalLQTYAllocated) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + (uint88 userLQTYAllocated) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user1, governance.epoch()); + assertEq(totalLQTYAllocated, 1e18); + assertEq(userLQTYAllocated, 1e18); + + // deposit bribe + _depositBribe(1e18, 1e18, governance.epoch() + 1); + vm.warp(block.timestamp + (EPOCH_DURATION * 2)); + + // user should receive bribe from their allocated stake + (uint256 boldAmount, uint256 bribeTokenAmount) = _claimBribe(user1, governance.epoch() - 1, governance.epoch() - 2, governance.epoch() - 2); + assertEq(boldAmount, 1e18); + assertEq(bribeTokenAmount, 1e18); + + // decrease user allocation for the initiative + _allocateLQTY(user1, -1e18, 0); + + (userLQTYAllocated) = bribeInitiative.lqtyAllocatedByUserAtEpoch(user1, governance.epoch()); + (totalLQTYAllocated) = bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + assertEq(userLQTYAllocated, 0); + assertEq(totalLQTYAllocated, 0); + } + + /** + Revert Cases + */ + // TODO: come back to this to figure out why revert doesn't get caught + // function test_depositBribe_epoch_too_early_reverts() public { + // vm.startPrank(lusdHolder); + + // lqty.approve(address(bribeInitiative), 1e18); + // lusd.approve(address(bribeInitiative), 1e18); + + // vm.expectRevert(); + // bribeInitiative.depositBribe(1e18, 1e18, governance.epoch()); + + // vm.stopPrank(); + // } + + function test_claimBribes_before_deposit_reverts() public { + _stakeLQTY(user1, 1e18); + + vm.warp(block.timestamp + EPOCH_DURATION); + + _depositBribe(1e18, 1e18, governance.epoch() + 1); + + vm.warp(block.timestamp + EPOCH_DURATION); + + _allocateLQTY(user1, 1e18, 0); + + (uint88 totalLQTYAllocated) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + (uint88 userLQTYAllocated) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user1, governance.epoch()); + assertEq(totalLQTYAllocated, 1e18); + assertEq(userLQTYAllocated, 1e18); + + vm.startPrank(user1); + + // should be zero since user1 was not deposited at that time BribeInitiative.ClaimData[] memory epochs = new BribeInitiative.ClaimData[](1); epochs[0].epoch = governance.epoch() - 1; epochs[0].prevLQTYAllocationEpoch = governance.epoch() - 1; @@ -179,40 +393,14 @@ contract BribeInitiativeTest is Test { assertEq(bribeTokenAmount, 0); vm.stopPrank(); - - vm.startPrank(lusdHolder); - lqty.approve(address(bribeInitiative), 1e18); - lusd.approve(address(bribeInitiative), 1e18); - bribeInitiative.depositBribe(1e18, 1e18, governance.epoch() + 1); - vm.warp(block.timestamp + governance.EPOCH_DURATION()); - vm.warp(block.timestamp + governance.EPOCH_DURATION()); - vm.stopPrank(); - - // should be non zero since user was deposited at that time - vm.startPrank(user); - epochs[0].epoch = governance.epoch() - 1; - epochs[0].prevLQTYAllocationEpoch = governance.epoch() - 2; - epochs[0].prevTotalLQTYAllocationEpoch = governance.epoch() - 2; - (boldAmount, bribeTokenAmount) = bribeInitiative.claimBribes(epochs); - assertEq(boldAmount, 1e18); - assertEq(bribeTokenAmount, 1e18); - vm.stopPrank(); - - vm.startPrank(user); - initiatives[0] = address(bribeInitiative); - deltaVoteLQTY[0] = -0.5e18; - governance.allocateLQTY(initiatives, deltaVoteLQTY, deltaVetoLQTY); - governance.allocateLQTY(initiatives, deltaVoteLQTY, deltaVetoLQTY); - (userLQTYAllocated, userAverageTimestamp) = bribeInitiative.lqtyAllocatedByUserAtEpoch(user, governance.epoch()); - assertEq(userLQTYAllocated, 0); - (totalLQTYAllocated, averageTimestamp) = bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); - assertEq(totalLQTYAllocated, 0); - vm.stopPrank(); } + /** + Helpers + */ function _stakeLQTY(address staker, uint88 amount) public { vm.startPrank(staker); - address userProxy = governance.deployUserProxy(); + address userProxy = governance.deriveUserProxyAddress(staker); lqty.approve(address(userProxy), amount); governance.depositLQTY(amount); vm.stopPrank(); @@ -223,7 +411,7 @@ contract BribeInitiativeTest is Test { address[] memory initiatives = new address[](1); initiatives[0] = address(bribeInitiative); - // voting in favor of the initiative with half of user's stake + // voting in favor of the initiative with half of user1's stake int176[] memory deltaVoteLQTY = new int176[](1); deltaVoteLQTY[0] = deltaVoteLQTYAmt; @@ -233,4 +421,22 @@ contract BribeInitiativeTest is Test { governance.allocateLQTY(initiatives, deltaVoteLQTY, deltaVetoLQTY); vm.stopPrank(); } + + function _depositBribe(uint128 boldAmount, uint128 bribeAmount, uint16 epoch) public { + vm.startPrank(lusdHolder); + lqty.approve(address(bribeInitiative), boldAmount); + lusd.approve(address(bribeInitiative), bribeAmount); + bribeInitiative.depositBribe(1e18, 1e18, epoch); + vm.stopPrank(); + } + + function _claimBribe(address claimer, uint16 epoch, uint16 prevLQTYAllocationEpoch, uint16 prevTotalLQTYAllocationEpoch) public returns (uint256 boldAmount, uint256 bribeTokenAmount){ + vm.startPrank(claimer); + BribeInitiative.ClaimData[] memory epochs = new BribeInitiative.ClaimData[](1); + epochs[0].epoch = epoch; + epochs[0].prevLQTYAllocationEpoch = prevLQTYAllocationEpoch; + epochs[0].prevTotalLQTYAllocationEpoch = prevTotalLQTYAllocationEpoch; + (boldAmount, bribeTokenAmount) = bribeInitiative.claimBribes(epochs); + vm.stopPrank(); + } } From 98c59fbcb6dbf804b59b4b79c53f5ff3e57a5fc5 Mon Sep 17 00:00:00 2001 From: nelson-pereira8 <94120714+nican0r@users.noreply.github.com> Date: Thu, 10 Oct 2024 15:10:52 -0300 Subject: [PATCH 19/22] test: coverage gaps for revert cases in BribeInitiative tests --- test/BribeInitiative.t.sol | 249 +++++++++++++++++++++++++++++++------ 1 file changed, 208 insertions(+), 41 deletions(-) diff --git a/test/BribeInitiative.t.sol b/test/BribeInitiative.t.sol index fc2245e..5333182 100644 --- a/test/BribeInitiative.t.sol +++ b/test/BribeInitiative.t.sol @@ -98,7 +98,7 @@ contract BribeInitiativeTest is Test { // allocate LQTY to the bribeInitiative _allocateLQTY(user1, 10e18, 0); // total LQTY allocated for this epoch should increase - (uint88 totalLQTYAllocated) = + (uint88 totalLQTYAllocated, ) = bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); assertEq(totalLQTYAllocated, 10e18); } @@ -113,7 +113,7 @@ contract BribeInitiativeTest is Test { // allocate LQTY to veto bribeInitiative _allocateLQTY(user1, 0, 10e18); // total LQTY allocated for this epoch should not increase - (uint88 totalLQTYAllocated) = + (uint88 totalLQTYAllocated, ) = bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); assertEq(totalLQTYAllocated, 0); } @@ -129,9 +129,9 @@ contract BribeInitiativeTest is Test { _allocateLQTY(user1, 5e18, 0); // total LQTY allocated for this epoch should increase - (uint88 totalLQTYAllocated1) = + (uint88 totalLQTYAllocated1,) = bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); - (uint88 userLQTYAllocated1) = + (uint88 userLQTYAllocated1,) = bribeInitiative.lqtyAllocatedByUserAtEpoch(user1, governance.epoch()); assertEq(totalLQTYAllocated1, 5e18); assertEq(userLQTYAllocated1, 5e18); @@ -142,9 +142,9 @@ contract BribeInitiativeTest is Test { _allocateLQTY(user1, 5e18, 0); // total LQTY allocated for this epoch should not change - (uint88 totalLQTYAllocated2) = + (uint88 totalLQTYAllocated2, ) = bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); - (uint88 userLQTYAllocated2) = + (uint88 userLQTYAllocated2, ) = bribeInitiative.lqtyAllocatedByUserAtEpoch(user1, governance.epoch()); assertEq(totalLQTYAllocated2, 10e18); assertEq(userLQTYAllocated1, 5e18); @@ -159,9 +159,9 @@ contract BribeInitiativeTest is Test { // user1 allocates in first epoch _allocateLQTY(user1, 5e18, 0); - (uint88 totalLQTYAllocated1) = + (uint88 totalLQTYAllocated1,) = bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); - (uint88 userLQTYAllocated1) = + (uint88 userLQTYAllocated1,) = bribeInitiative.lqtyAllocatedByUserAtEpoch(user1, governance.epoch()); assertEq(totalLQTYAllocated1, 5e18); assertEq(userLQTYAllocated1, 5e18); @@ -169,9 +169,9 @@ contract BribeInitiativeTest is Test { // vm.warp(block.timestamp + EPOCH_DURATION); _allocateLQTY(user1, 5e18, 0); - (uint88 totalLQTYAllocated2) = + (uint88 totalLQTYAllocated2,) = bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); - (uint88 userLQTYAllocated2) = + (uint88 userLQTYAllocated2,) = bribeInitiative.lqtyAllocatedByUserAtEpoch(user1, governance.epoch()); assertEq(totalLQTYAllocated2, 10e18); assertEq(userLQTYAllocated2, 10e18); @@ -184,16 +184,16 @@ contract BribeInitiativeTest is Test { // user1 allocates in first epoch _allocateLQTY(user1, 5e18, 0); - (uint88 totalLQTYAllocated1) = + (uint88 totalLQTYAllocated1,) = bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); - (uint88 userLQTYAllocated1) = + (uint88 userLQTYAllocated1,) = bribeInitiative.lqtyAllocatedByUserAtEpoch(user1, governance.epoch()); assertEq(totalLQTYAllocated1, 5e18); assertEq(userLQTYAllocated1, 5e18); console2.log("current governance epoch: ", governance.epoch()); // user's linked-list should be updated to have a value for the current epoch - uint88 allocatedAtEpoch = bribeInitiative.lqtyAllocatedByUserAtEpoch(user1, governance.epoch()); + (uint88 allocatedAtEpoch,) = bribeInitiative.lqtyAllocatedByUserAtEpoch(user1, governance.epoch()); console2.log("allocatedAtEpoch: ", allocatedAtEpoch); } @@ -206,9 +206,9 @@ contract BribeInitiativeTest is Test { // user1 allocates in first epoch _allocateLQTY(user1, 10e18, 0); - (uint88 totalLQTYAllocated1) = + (uint88 totalLQTYAllocated1,) = bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); - (uint88 userLQTYAllocated1) = + (uint88 userLQTYAllocated1,) = bribeInitiative.lqtyAllocatedByUserAtEpoch(user1, governance.epoch()); assertEq(totalLQTYAllocated1, 10e18); assertEq(userLQTYAllocated1, 10e18); @@ -218,9 +218,9 @@ contract BribeInitiativeTest is Test { // user allocations should be disjoint because they're in separate epochs _allocateLQTY(user2, 10e18, 0); - (uint88 totalLQTYAllocated2) = + (uint88 totalLQTYAllocated2,) = bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); - (uint88 userLQTYAllocated2) = + (uint88 userLQTYAllocated2,) = bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()); assertEq(totalLQTYAllocated2, 20e18); assertEq(userLQTYAllocated2, 10e18); @@ -236,17 +236,17 @@ contract BribeInitiativeTest is Test { // user1 allocates in first epoch _allocateLQTY(user1, 10e18, 0); - (uint88 totalLQTYAllocated1) = + (uint88 totalLQTYAllocated1,) = bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); - (uint88 userLQTYAllocated1) = + (uint88 userLQTYAllocated1,) = bribeInitiative.lqtyAllocatedByUserAtEpoch(user1, governance.epoch()); assertEq(totalLQTYAllocated1, 10e18); assertEq(userLQTYAllocated1, 10e18); _allocateLQTY(user2, 10e18, 0); - (uint88 totalLQTYAllocated2) = + (uint88 totalLQTYAllocated2,) = bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); - (uint88 userLQTYAllocated2) = + (uint88 userLQTYAllocated2,) = bribeInitiative.lqtyAllocatedByUserAtEpoch(user2, governance.epoch()); assertEq(totalLQTYAllocated2, 20e18); assertEq(userLQTYAllocated2, 10e18); @@ -261,14 +261,14 @@ contract BribeInitiativeTest is Test { // user1 allocates in first epoch _allocateLQTY(user1, 10e18, 0); - (uint88 totalLQTYAllocated1) = + (uint88 totalLQTYAllocated1,) = bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); assertEq(totalLQTYAllocated1, 10e18); // warp to the end of the epoch vm.warp(block.timestamp + (EPOCH_VOTING_CUTOFF - 1)); - (uint88 totalLQTYAllocated2) = + (uint88 totalLQTYAllocated2,) = bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); assertEq(totalLQTYAllocated2, 10e18); } @@ -293,9 +293,9 @@ contract BribeInitiativeTest is Test { _allocateLQTY(user1, 1e18, 0); - (uint88 totalLQTYAllocated) = + (uint88 totalLQTYAllocated,) = bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); - (uint88 userLQTYAllocated) = + (uint88 userLQTYAllocated,) = bribeInitiative.lqtyAllocatedByUserAtEpoch(user1, governance.epoch()); assertEq(totalLQTYAllocated, 1e18); assertEq(userLQTYAllocated, 1e18); @@ -321,9 +321,9 @@ contract BribeInitiativeTest is Test { _allocateLQTY(user1, 1e18, 0); - (uint88 totalLQTYAllocated) = + (uint88 totalLQTYAllocated,) = bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); - (uint88 userLQTYAllocated) = + (uint88 userLQTYAllocated,) = bribeInitiative.lqtyAllocatedByUserAtEpoch(user1, governance.epoch()); assertEq(totalLQTYAllocated, 1e18); assertEq(userLQTYAllocated, 1e18); @@ -340,8 +340,8 @@ contract BribeInitiativeTest is Test { // decrease user allocation for the initiative _allocateLQTY(user1, -1e18, 0); - (userLQTYAllocated) = bribeInitiative.lqtyAllocatedByUserAtEpoch(user1, governance.epoch()); - (totalLQTYAllocated) = bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + (userLQTYAllocated,) = bribeInitiative.lqtyAllocatedByUserAtEpoch(user1, governance.epoch()); + (totalLQTYAllocated,) = bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); assertEq(userLQTYAllocated, 0); assertEq(totalLQTYAllocated, 0); } @@ -349,20 +349,19 @@ contract BribeInitiativeTest is Test { /** Revert Cases */ - // TODO: come back to this to figure out why revert doesn't get caught - // function test_depositBribe_epoch_too_early_reverts() public { - // vm.startPrank(lusdHolder); + function test_depositBribe_epoch_too_early_reverts() public { + vm.startPrank(lusdHolder); - // lqty.approve(address(bribeInitiative), 1e18); - // lusd.approve(address(bribeInitiative), 1e18); + lqty.approve(address(bribeInitiative), 1e18); + lusd.approve(address(bribeInitiative), 1e18); - // vm.expectRevert(); - // bribeInitiative.depositBribe(1e18, 1e18, governance.epoch()); + vm.expectRevert("BribeInitiative: only-future-epochs"); + bribeInitiative.depositBribe(1e18, 1e18, uint16(0)); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - function test_claimBribes_before_deposit_reverts() public { + function test_claimBribes_before_deposit_reverts() public { _stakeLQTY(user1, 1e18); vm.warp(block.timestamp + EPOCH_DURATION); @@ -373,9 +372,9 @@ contract BribeInitiativeTest is Test { _allocateLQTY(user1, 1e18, 0); - (uint88 totalLQTYAllocated) = + (uint88 totalLQTYAllocated,) = bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); - (uint88 userLQTYAllocated) = + (uint88 userLQTYAllocated,) = bribeInitiative.lqtyAllocatedByUserAtEpoch(user1, governance.epoch()); assertEq(totalLQTYAllocated, 1e18); assertEq(userLQTYAllocated, 1e18); @@ -395,6 +394,174 @@ contract BribeInitiativeTest is Test { vm.stopPrank(); } + function test_claimBribes_current_epoch_reverts() public { + _stakeLQTY(user1, 1e18); + + vm.warp(block.timestamp + EPOCH_DURATION); + + _depositBribe(1e18, 1e18, governance.epoch() + 1); + + vm.warp(block.timestamp + EPOCH_DURATION); + + _allocateLQTY(user1, 1e18, 0); + + (uint88 totalLQTYAllocated,) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + (uint88 userLQTYAllocated,) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user1, governance.epoch()); + assertEq(totalLQTYAllocated, 1e18); + assertEq(userLQTYAllocated, 1e18); + + vm.startPrank(user1); + + // should be zero since user1 was not deposited at that time + BribeInitiative.ClaimData[] memory epochs = new BribeInitiative.ClaimData[](1); + epochs[0].epoch = governance.epoch(); + epochs[0].prevLQTYAllocationEpoch = governance.epoch() - 1; + epochs[0].prevTotalLQTYAllocationEpoch = governance.epoch() - 1; + vm.expectRevert("BribeInitiative: cannot-claim-for-current-epoch"); + (uint256 boldAmount, uint256 bribeTokenAmount) = bribeInitiative.claimBribes(epochs); + assertEq(boldAmount, 0); + assertEq(bribeTokenAmount, 0); + + vm.stopPrank(); + } + + function test_claimBribes_same_epoch_reverts() public { + _stakeLQTY(user1, 1e18); + + vm.warp(block.timestamp + EPOCH_DURATION); + + _depositBribe(1e18, 1e18, governance.epoch() + 1); + + vm.warp(block.timestamp + EPOCH_DURATION); + + _allocateLQTY(user1, 1e18, 0); + + (uint88 totalLQTYAllocated,) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + (uint88 userLQTYAllocated,) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user1, governance.epoch()); + assertEq(totalLQTYAllocated, 1e18); + assertEq(userLQTYAllocated, 1e18); + + // deposit bribe + _depositBribe(1e18, 1e18, governance.epoch() + 1); + vm.warp(block.timestamp + (EPOCH_DURATION * 2)); + + // user should receive bribe from their allocated stake + (uint256 boldAmount1, uint256 bribeTokenAmount1) = _claimBribe(user1, governance.epoch() - 1, governance.epoch() - 2, governance.epoch() - 2); + assertEq(boldAmount1, 1e18); + assertEq(bribeTokenAmount1, 1e18); + + vm.startPrank(user1); + BribeInitiative.ClaimData[] memory epochs = new BribeInitiative.ClaimData[](1); + epochs[0].epoch = governance.epoch() - 1; + epochs[0].prevLQTYAllocationEpoch = governance.epoch() - 2; + epochs[0].prevTotalLQTYAllocationEpoch = governance.epoch() - 2; + vm.expectRevert("BribeInitiative: already-claimed"); + (uint256 boldAmount2, uint256 bribeTokenAmount2) = bribeInitiative.claimBribes(epochs); + vm.stopPrank(); + } + + function test_claimBribes_no_bribe_reverts() public { + _stakeLQTY(user1, 1e18); + + vm.warp(block.timestamp + EPOCH_DURATION); + + _allocateLQTY(user1, 1e18, 0); + + (uint88 totalLQTYAllocated,) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + (uint88 userLQTYAllocated,) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user1, governance.epoch()); + assertEq(totalLQTYAllocated, 1e18); + assertEq(userLQTYAllocated, 1e18); + + vm.startPrank(user1); + BribeInitiative.ClaimData[] memory epochs = new BribeInitiative.ClaimData[](1); + epochs[0].epoch = governance.epoch() - 1; + epochs[0].prevLQTYAllocationEpoch = governance.epoch() - 2; + epochs[0].prevTotalLQTYAllocationEpoch = governance.epoch() - 2; + vm.expectRevert("BribeInitiative: no-bribe"); + (uint256 boldAmount1, uint256 bribeTokenAmount1) = bribeInitiative.claimBribes(epochs); + vm.stopPrank(); + + assertEq(boldAmount1, 0); + assertEq(bribeTokenAmount1, 0); + } + + function test_claimBribes_no_allocation_reverts() public { + _stakeLQTY(user1, 1e18); + + vm.warp(block.timestamp + EPOCH_DURATION); + + _depositBribe(1e18, 1e18, governance.epoch() + 1); + + vm.warp(block.timestamp + EPOCH_DURATION); + + _allocateLQTY(user1, 0, 0); + + (uint88 totalLQTYAllocated,) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + (uint88 userLQTYAllocated,) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user1, governance.epoch()); + assertEq(totalLQTYAllocated, 0); + assertEq(userLQTYAllocated, 0); + + // deposit bribe + _depositBribe(1e18, 1e18, governance.epoch() + 1); + vm.warp(block.timestamp + (EPOCH_DURATION * 2)); + + vm.startPrank(user1); + BribeInitiative.ClaimData[] memory epochs = new BribeInitiative.ClaimData[](1); + epochs[0].epoch = governance.epoch() - 1; + epochs[0].prevLQTYAllocationEpoch = governance.epoch() - 2; + epochs[0].prevTotalLQTYAllocationEpoch = governance.epoch() - 2; + vm.expectRevert("BribeInitiative: invalid-prev-total-lqty-allocation-epoch"); + (uint256 boldAmount, uint256 bribeTokenAmount) = bribeInitiative.claimBribes(epochs); + vm.stopPrank(); + + assertEq(boldAmount, 0); + assertEq(bribeTokenAmount, 0); + } + + // requires: no allocation, previousAllocationEpoch > current, next < epoch or next = 0 + function test_claimBribes_invalid_previous_allocation_epoch_reverts() public { + _stakeLQTY(user1, 1e18); + + vm.warp(block.timestamp + EPOCH_DURATION); + + _depositBribe(1e18, 1e18, governance.epoch() + 1); + + vm.warp(block.timestamp + EPOCH_DURATION); + + _allocateLQTY(user1, 0, 0); + + (uint88 totalLQTYAllocated,) = + bribeInitiative.totalLQTYAllocatedByEpoch(governance.epoch()); + (uint88 userLQTYAllocated,) = + bribeInitiative.lqtyAllocatedByUserAtEpoch(user1, governance.epoch()); + assertEq(totalLQTYAllocated, 0); + assertEq(userLQTYAllocated, 0); + + // deposit bribe + _depositBribe(1e18, 1e18, governance.epoch() + 1); + vm.warp(block.timestamp + (EPOCH_DURATION * 2)); + + vm.startPrank(user1); + BribeInitiative.ClaimData[] memory epochs = new BribeInitiative.ClaimData[](1); + epochs[0].epoch = governance.epoch() - 1; + epochs[0].prevLQTYAllocationEpoch = governance.epoch(); + epochs[0].prevTotalLQTYAllocationEpoch = governance.epoch() - 2; + vm.expectRevert("BribeInitiative: invalid-prev-lqty-allocation-epoch"); + (uint256 boldAmount, uint256 bribeTokenAmount) = bribeInitiative.claimBribes(epochs); + vm.stopPrank(); + + assertEq(boldAmount, 0); + assertEq(bribeTokenAmount, 0); + } + /** Helpers */ From 08a7ef54246ddb276a4737d3f47d6a3c6ed67bdd Mon Sep 17 00:00:00 2001 From: nelson-pereira8 <94120714+nican0r@users.noreply.github.com> Date: Thu, 10 Oct 2024 19:15:54 -0300 Subject: [PATCH 20/22] test: test_check_correct_vote_calculation for issue 5.3 --- test/Governance.t.sol | 54 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/test/Governance.t.sol b/test/Governance.t.sol index eb2a555..9bb1172 100644 --- a/test/Governance.t.sol +++ b/test/Governance.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.24; -import {Test} from "forge-std/Test.sol"; +import {Test, console2} from "forge-std/Test.sol"; import {VmSafe} from "forge-std/Vm.sol"; import {console} from "forge-std/console.sol"; @@ -388,6 +388,58 @@ contract GovernanceTest is Test { governance.lqtyToVotes(_lqtyAmount, _currentTimestamp, _averageTimestamp); } + // NOTE: issue 5.3 from CS audit + function test_check_correct_vote_calculation() public { + // objective: verify that the voting power from an allocation is the same as the lqtyToVotes calculated using an average timestamp + // 1. allocate and reach the specific path prevInitiativeState.counted == 1 by allocating a sufficient amount to reach the threshold + + // votingThreshold = 5e17 + vm.startPrank(user); + + address userProxy = governanceInternal.deployUserProxy(); + + // stake LQTY + console.log("user lqty balance: %e", lqty.balanceOf(user)); + lqty.approve(address(userProxy), 20e18); + governanceInternal.depositLQTY(20e18); + + (uint88 allocatedLQTY, uint32 averageStakingTimestampUser) = governanceInternal.userStates(user); + (uint88 countedVoteLQTY,) = governanceInternal.globalState(); + + address[] memory initiatives = new address[](1); + initiatives[0] = baseInitiative1; + int176[] memory deltaLQTYVotes = new int176[](1); + deltaLQTYVotes[0] = 10e18; + int176[] memory deltaLQTYVetos = new int176[](1); + + vm.warp(block.timestamp + 365 days); + governanceInternal.allocateLQTY(initiatives, deltaLQTYVotes, deltaLQTYVetos); + + (uint88 voteLQTY1,, uint32 averageStakingTimestampVoteLQTY1,,) = governanceInternal.initiativeStates(address(baseInitiative1)); + console2.log("averageStakingTimestampVoteLQTY1: ", averageStakingTimestampVoteLQTY1); + + // 2. allocate again to reach the needed path + address[] memory initiatives2 = new address[](1); + initiatives2[0] = baseInitiative1; + int176[] memory deltaLQTYVotes2 = new int176[](1); + deltaLQTYVotes2[0] = 5e18; + int176[] memory deltaLQTYVetos2 = new int176[](1); + + vm.warp(block.timestamp + 4 days); + governanceInternal.allocateLQTY(initiatives2, deltaLQTYVotes2, deltaLQTYVetos2); + + // 3. calculate voting power as state.countedVoteLQTYAverageTimestamp * state.countedVoteLQTY + // need to get the latest initiative state + (uint88 voteLQTY2,, uint32 averageStakingTimestampVoteLQTY2,,) = governanceInternal.initiativeStates(address(baseInitiative1)); + console2.log("averageStakingTimestampVoteLQTY2: ", averageStakingTimestampVoteLQTY2); + uint240 votingPower1 = governanceInternal.lqtyToVotes(voteLQTY2, block.timestamp, averageStakingTimestampVoteLQTY2); + + // 4. compare with voting power returned from lqtyToVotes using correct average timestamp + uint32 averageTimestamp = governanceInternal.calculateAverageTimestamp(averageStakingTimestampVoteLQTY1, averageStakingTimestampVoteLQTY2, voteLQTY1, voteLQTY2); + uint240 votingPower2 = governanceInternal.lqtyToVotes(voteLQTY2, block.timestamp, averageTimestamp); + assertEq(votingPower1, votingPower2); + } + function test_calculateVotingThreshold() public { governance = new Governance( address(lqty), From 8ceea3a4519884a1e123b123eb9e4549ce0e6739 Mon Sep 17 00:00:00 2001 From: nelson-pereira8 <94120714+nican0r@users.noreply.github.com> Date: Fri, 11 Oct 2024 08:55:51 -0300 Subject: [PATCH 21/22] chore: removing 5.3 check to add in separate branch --- test/Governance.t.sol | 52 ------------------------------------------- 1 file changed, 52 deletions(-) diff --git a/test/Governance.t.sol b/test/Governance.t.sol index 9bb1172..e7db94f 100644 --- a/test/Governance.t.sol +++ b/test/Governance.t.sol @@ -388,58 +388,6 @@ contract GovernanceTest is Test { governance.lqtyToVotes(_lqtyAmount, _currentTimestamp, _averageTimestamp); } - // NOTE: issue 5.3 from CS audit - function test_check_correct_vote_calculation() public { - // objective: verify that the voting power from an allocation is the same as the lqtyToVotes calculated using an average timestamp - // 1. allocate and reach the specific path prevInitiativeState.counted == 1 by allocating a sufficient amount to reach the threshold - - // votingThreshold = 5e17 - vm.startPrank(user); - - address userProxy = governanceInternal.deployUserProxy(); - - // stake LQTY - console.log("user lqty balance: %e", lqty.balanceOf(user)); - lqty.approve(address(userProxy), 20e18); - governanceInternal.depositLQTY(20e18); - - (uint88 allocatedLQTY, uint32 averageStakingTimestampUser) = governanceInternal.userStates(user); - (uint88 countedVoteLQTY,) = governanceInternal.globalState(); - - address[] memory initiatives = new address[](1); - initiatives[0] = baseInitiative1; - int176[] memory deltaLQTYVotes = new int176[](1); - deltaLQTYVotes[0] = 10e18; - int176[] memory deltaLQTYVetos = new int176[](1); - - vm.warp(block.timestamp + 365 days); - governanceInternal.allocateLQTY(initiatives, deltaLQTYVotes, deltaLQTYVetos); - - (uint88 voteLQTY1,, uint32 averageStakingTimestampVoteLQTY1,,) = governanceInternal.initiativeStates(address(baseInitiative1)); - console2.log("averageStakingTimestampVoteLQTY1: ", averageStakingTimestampVoteLQTY1); - - // 2. allocate again to reach the needed path - address[] memory initiatives2 = new address[](1); - initiatives2[0] = baseInitiative1; - int176[] memory deltaLQTYVotes2 = new int176[](1); - deltaLQTYVotes2[0] = 5e18; - int176[] memory deltaLQTYVetos2 = new int176[](1); - - vm.warp(block.timestamp + 4 days); - governanceInternal.allocateLQTY(initiatives2, deltaLQTYVotes2, deltaLQTYVetos2); - - // 3. calculate voting power as state.countedVoteLQTYAverageTimestamp * state.countedVoteLQTY - // need to get the latest initiative state - (uint88 voteLQTY2,, uint32 averageStakingTimestampVoteLQTY2,,) = governanceInternal.initiativeStates(address(baseInitiative1)); - console2.log("averageStakingTimestampVoteLQTY2: ", averageStakingTimestampVoteLQTY2); - uint240 votingPower1 = governanceInternal.lqtyToVotes(voteLQTY2, block.timestamp, averageStakingTimestampVoteLQTY2); - - // 4. compare with voting power returned from lqtyToVotes using correct average timestamp - uint32 averageTimestamp = governanceInternal.calculateAverageTimestamp(averageStakingTimestampVoteLQTY1, averageStakingTimestampVoteLQTY2, voteLQTY1, voteLQTY2); - uint240 votingPower2 = governanceInternal.lqtyToVotes(voteLQTY2, block.timestamp, averageTimestamp); - assertEq(votingPower1, votingPower2); - } - function test_calculateVotingThreshold() public { governance = new Governance( address(lqty), From 0b211f5098d7b20995aca8453b37cf1e08c9947d Mon Sep 17 00:00:00 2001 From: gallo Date: Mon, 14 Oct 2024 09:41:40 +0200 Subject: [PATCH 22/22] fix: compilation --- src/ForwardBribe.sol | 7 ++++++- test/mocks/MaliciousInitiative.sol | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/ForwardBribe.sol b/src/ForwardBribe.sol index 234c740..fc317e6 100644 --- a/src/ForwardBribe.sol +++ b/src/ForwardBribe.sol @@ -1,9 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; +import {IERC20} from "openzeppelin-contracts/contracts/interfaces/IERC20.sol"; +import {SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; + import {BribeInitiative} from "./BribeInitiative.sol"; contract ForwardBribe is BribeInitiative { + using SafeERC20 for IERC20; + address public immutable receiver; constructor(address _governance, address _bold, address _bribeToken, address _receiver) @@ -21,4 +26,4 @@ contract ForwardBribe is BribeInitiative { if (boldAmount != 0) bold.safeTransfer(receiver, boldAmount); if (bribeTokenAmount != 0) bribeToken.safeTransfer(receiver, bribeTokenAmount); } -} +} \ No newline at end of file diff --git a/test/mocks/MaliciousInitiative.sol b/test/mocks/MaliciousInitiative.sol index 2dbd60c..1d45524 100644 --- a/test/mocks/MaliciousInitiative.sol +++ b/test/mocks/MaliciousInitiative.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import {IInitiative} from "src/interfaces/IInitiative.sol"; +import {IGovernance} from "src/interfaces/IGovernance.sol"; contract MaliciousInitiative is IInitiative { @@ -37,7 +38,7 @@ contract MaliciousInitiative is IInitiative { } - function onAfterAllocateLQTY(uint16 _currentEpoch, address _user, uint88 _voteLQTY, uint88 _vetoLQTY) external override { + function onAfterAllocateLQTY(uint16 _currentEpoch, address _user, IGovernance.UserState calldata _userState, IGovernance.Allocation calldata _allocation, IGovernance.InitiativeState calldata _initiativeState) external override { _performRevertBehaviour(revertBehaviours[FunctionType.ALLOCATE]); }