-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
### Description Currently the voting power formula looks like this (slightly simplified): ```math P(t,s,c) = t * (0.2 + 0.8 * c / c_{max} + 0.6 * s / s_{max}) ``` We want to bring the system closer to the classical veCurve formula, by changing it to: ```math P(t,s,c) = t * min(s/s_{max} + c/c_{max}, 1) ``` More details can be seen here #373 Since this formula sits in the core of the voting power calculations, reviews should be on a broader level. Please go through code changes, test calculations and more importantly how this may affect the whole locking mechanism. Known issues: - users can lock with a suboptimal schedule if the summed multiplier goes above 1. ### Other changes Updated the tests to use the exact amounts and not approximations Removed unused imports ### Tested Fixed the tests related to change. Actual calculations are added as comments. Some extra fuzz tests are WIP and will be added with a separate PR ### Related issues - Fixes #373 --------- Co-authored-by: baroooo <[email protected]>
- Loading branch information
Showing
8 changed files
with
380 additions
and
156 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
170 changes: 170 additions & 0 deletions
170
test/governance/IntegrationTests/LockingIntegration.fuzz.t.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
pragma solidity 0.8.18; | ||
// solhint-disable func-name-mixedcase, max-line-length, max-states-count | ||
|
||
import { TestSetup } from "../TestSetup.sol"; | ||
import { Vm } from "forge-std-next/Vm.sol"; | ||
import { VmExtension } from "test/utils/VmExtension.sol"; | ||
import { Arrays } from "test/utils/Arrays.sol"; | ||
|
||
import { GovernanceFactory } from "contracts/governance/GovernanceFactory.sol"; | ||
import { MentoToken } from "contracts/governance/MentoToken.sol"; | ||
import { Locking } from "contracts/governance/locking/Locking.sol"; | ||
import { TimelockController } from "contracts/governance/TimelockController.sol"; | ||
import { console } from "forge-std-next/console.sol"; | ||
|
||
/** | ||
* @title Fuzz Testing for Locking Integration | ||
* @dev Fuzz tests to ensure the locking mechanism integrates correctly with the governance system, providing the expected voting power based on token lock amount and duration. | ||
*/ | ||
contract FuzzLockingIntegrationTest is TestSetup { | ||
using VmExtension for Vm; | ||
|
||
GovernanceFactory public factory; | ||
|
||
MentoToken public mentoToken; | ||
TimelockController public governanceTimelock; | ||
Locking public locking; | ||
|
||
address public celoGovernance = makeAddr("CeloGovernance"); | ||
address public celoCommunityFund = makeAddr("CeloCommunityFund"); | ||
address public watchdogMultisig = makeAddr("WatchdogMultisig"); | ||
address public mentoLabsMultisig = makeAddr("MentoLabsMultisig"); | ||
address public fractalSigner = makeAddr("FractalSigner"); | ||
|
||
bytes32 public merkleRoot = bytes32("MockMerkleRoot"); | ||
|
||
function setUp() public { | ||
vm.roll(21871402); // (Oct-11-2023 WED 12:00:01 PM +UTC) | ||
vm.warp(1697025601); // (Oct-11-2023 WED 12:00:01 PM +UTC) | ||
|
||
vm.prank(owner); | ||
factory = new GovernanceFactory(celoGovernance); | ||
|
||
GovernanceFactory.MentoTokenAllocationParams memory allocationParams = GovernanceFactory | ||
.MentoTokenAllocationParams({ | ||
airgrabAllocation: 50, | ||
mentoTreasuryAllocation: 100, | ||
additionalAllocationRecipients: Arrays.addresses(address(mentoLabsMultisig)), | ||
additionalAllocationAmounts: Arrays.uints(200) | ||
}); | ||
|
||
vm.prank(celoGovernance); | ||
factory.createGovernance(watchdogMultisig, celoCommunityFund, merkleRoot, fractalSigner, allocationParams); | ||
mentoToken = factory.mentoToken(); | ||
governanceTimelock = factory.governanceTimelock(); | ||
locking = factory.locking(); | ||
|
||
vm.prank(alice); | ||
mentoToken.approve(address(locking), type(uint256).max); | ||
} | ||
|
||
/** | ||
* @dev Fuzz test to verify correct voting power allocation for locked tokens over a shorter timeframe. | ||
*/ | ||
function test_lock_shouldGiveCorrectVotingPower_whenShorterTimeframe_Fuzz( | ||
uint96 amount, | ||
uint32 slope, | ||
uint32 cliff, | ||
uint96 period | ||
) public { | ||
vm.assume(amount < mentoToken.balanceOf(address(address(governanceTimelock)))); | ||
vm.assume(amount > 1000); | ||
vm.assume(slope >= locking.minSlopePeriod()); | ||
vm.assume(slope <= 104); | ||
vm.assume(cliff >= locking.minCliffPeriod()); | ||
vm.assume(cliff <= 103); | ||
vm.assume(period <= 208); // 4 years | ||
|
||
vm.prank(address(governanceTimelock)); | ||
mentoToken.transfer(alice, amount); | ||
|
||
vm.prank(alice); | ||
locking.lock(alice, alice, amount, slope, cliff); | ||
|
||
assertEq(locking.getVotes(alice), calculateVotes(amount, slope, cliff)); | ||
|
||
vm.timeTravel(BLOCKS_WEEK * period); | ||
|
||
uint256 balanceBefore = mentoToken.balanceOf(alice); | ||
|
||
vm.prank(alice); | ||
locking.withdraw(); | ||
|
||
uint256 balanceAfter = mentoToken.balanceOf(alice); | ||
|
||
if (period > cliff) { | ||
assert(balanceAfter > balanceBefore); | ||
} else { | ||
assertEq(balanceAfter, balanceBefore); | ||
} | ||
|
||
if (period > slope + cliff) { | ||
assertEq(locking.getVotes(alice), 0); | ||
assertEq(balanceAfter, amount); | ||
} | ||
} | ||
|
||
/** | ||
* @dev Fuzz test to verify correct voting power allocation for locked tokens over a longer timeframe. | ||
* Focuses on longer periods to avoid sparse coverage for more frequent usecase: period being < 4 years | ||
*/ | ||
function test_lock_shouldGiveCorrectVotingPower_whenLongerTimeframe_Fuzz( | ||
uint96 amount, | ||
uint32 slope, | ||
uint32 cliff, | ||
uint96 period | ||
) public { | ||
vm.assume(amount < mentoToken.balanceOf(address(address(governanceTimelock)))); | ||
vm.assume(amount > 1000); | ||
vm.assume(slope >= locking.minSlopePeriod()); | ||
vm.assume(slope <= 104); | ||
vm.assume(cliff >= locking.minCliffPeriod()); | ||
vm.assume(cliff <= 103); | ||
vm.assume(period <= 2080); // 40 years | ||
|
||
vm.prank(address(governanceTimelock)); | ||
mentoToken.transfer(alice, amount); | ||
|
||
vm.prank(alice); | ||
locking.lock(alice, alice, amount, slope, cliff); | ||
|
||
assertEq(locking.getVotes(alice), calculateVotes(amount, slope, cliff)); | ||
|
||
vm.timeTravel(BLOCKS_WEEK * period); | ||
|
||
uint256 balanceBefore = mentoToken.balanceOf(alice); | ||
|
||
vm.prank(alice); | ||
locking.withdraw(); | ||
|
||
uint256 balanceAfter = mentoToken.balanceOf(alice); | ||
|
||
if (period > cliff) { | ||
assert(balanceAfter > balanceBefore); | ||
} else { | ||
assertEq(balanceAfter, balanceBefore); | ||
} | ||
|
||
if (period > slope + cliff) { | ||
assertEq(locking.getVotes(alice), 0); | ||
assertEq(balanceAfter, amount); | ||
} | ||
} | ||
|
||
/** | ||
* @dev Calculates the expected voting power based on lock amount, slope, and cliff. | ||
*/ | ||
function calculateVotes( | ||
uint96 amount, | ||
uint32 slope, | ||
uint32 cliff | ||
) public pure returns (uint96) { | ||
uint96 cliffSide = (uint96(cliff) * 1e8) / 103; | ||
uint96 slopeSide = (uint96(slope) * 1e8) / 104; | ||
uint96 multiplier = cliffSide + slopeSide; | ||
if (multiplier > 1e8) multiplier = 1e8; | ||
|
||
return uint96((uint256(amount) * uint256(multiplier)) / 1e8); | ||
} | ||
} |
Oops, something went wrong.