From 972e4e86619adba49a6a0e0b442e21d5b7aee7a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Fingen?= Date: Tue, 7 Jan 2025 17:55:45 +0000 Subject: [PATCH] Remove UniV4 initiative --- script/DeploySepolia.s.sol | 45 ------ src/UniV4Donations.sol | 191 ------------------------- test/UniV4Donations.t.sol | 280 ------------------------------------- 3 files changed, 516 deletions(-) delete mode 100644 src/UniV4Donations.sol delete mode 100644 test/UniV4Donations.t.sol diff --git a/script/DeploySepolia.s.sol b/script/DeploySepolia.s.sol index cb20465..06e0c83 100644 --- a/script/DeploySepolia.s.sol +++ b/script/DeploySepolia.s.sol @@ -11,7 +11,6 @@ import {ILiquidityGauge} from "./../src/interfaces/ILiquidityGauge.sol"; import {IGovernance} from "../src/interfaces/IGovernance.sol"; import {Governance} from "../src/Governance.sol"; -import {UniV4Donations} from "../src/UniV4Donations.sol"; import {CurveV2GaugeRewards} from "../src/CurveV2GaugeRewards.sol"; import {Hooks} from "../src/utils/BaseHook.sol"; @@ -42,17 +41,12 @@ contract DeploySepoliaScript is Script, Deployers, MockStakingV1Deployer { uint32 private constant EPOCH_DURATION = 604800; uint32 private constant EPOCH_VOTING_CUTOFF = 518400; - // UniV4Donations Constants - uint24 private constant FEE = 400; - int24 constant MAX_TICK_SPACING = 32767; - // CurveV2GaugeRewards Constants uint256 private constant DURATION = 7 days; // Contracts Governance private governance; address[] private initialInitiatives; - UniV4Donations private uniV4Donations; CurveV2GaugeRewards private curveV2GaugeRewards; ICurveStableswapNG private curvePool; ILiquidityGauge private gauge; @@ -96,44 +90,6 @@ contract DeploySepoliaScript is Script, Deployers, MockStakingV1Deployer { deployer, initialInitiatives ); - assert(governance == uniV4Donations.governance()); - } - - function deployUniV4Donations(uint256 _nonce) private { - address gov = address(vm.computeCreateAddress(deployer, _nonce)); - uint160 flags = uint160(Hooks.AFTER_INITIALIZE_FLAG | Hooks.AFTER_ADD_LIQUIDITY_FLAG); - - (, bytes32 salt) = HookMiner.find( - 0x4e59b44847b379578588920cA78FbF26c0B4956C, - // address(this), - flags, - type(UniV4Donations).creationCode, - abi.encode( - gov, - address(bold), - address(lqty), - block.timestamp, - EPOCH_DURATION, - address(poolManager), - address(usdc), - FEE, - MAX_TICK_SPACING - ) - ); - - uniV4Donations = new UniV4Donations{salt: salt}( - gov, - address(bold), - address(lqty), - block.timestamp, - EPOCH_DURATION, - address(poolManager), - address(usdc), - FEE, - MAX_TICK_SPACING - ); - - initialInitiatives.push(address(uniV4Donations)); } function deployCurveV2GaugeRewards(uint256 _nonce) private { @@ -172,7 +128,6 @@ contract DeploySepoliaScript is Script, Deployers, MockStakingV1Deployer { function run() public { vm.startBroadcast(privateKey); deployEnvironment(); - deployUniV4Donations(nonce + 8); deployGovernance(); vm.stopBroadcast(); } diff --git a/src/UniV4Donations.sol b/src/UniV4Donations.sol deleted file mode 100644 index 8c80a09..0000000 --- a/src/UniV4Donations.sol +++ /dev/null @@ -1,191 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.24; - -import {IERC20} from "openzeppelin/contracts/interfaces/IERC20.sol"; -import {SafeERC20} from "openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; - -import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol"; -import {IHooks} from "v4-core/src/interfaces/IHooks.sol"; -import {PoolKey} from "v4-core/src/types/PoolKey.sol"; -import {PoolId, PoolIdLibrary} from "v4-core/src/types/PoolId.sol"; -import {Currency, CurrencyLibrary} from "v4-core/src/types/Currency.sol"; -import {BalanceDelta} from "v4-core/src/types/BalanceDelta.sol"; - -import {BaseHook, Hooks} from "./utils/BaseHook.sol"; -import {BribeInitiative} from "./BribeInitiative.sol"; - -contract UniV4Donations is BribeInitiative, BaseHook { - using SafeERC20 for IERC20; - using CurrencyLibrary for Currency; - using PoolIdLibrary for PoolKey; - - event DonateToPool(uint256 amount); - event RestartVesting(uint256 epoch, uint256 amount); - - uint256 public immutable VESTING_EPOCH_START; - uint256 public immutable VESTING_EPOCH_DURATION; - - address private immutable currency0; - address private immutable currency1; - uint24 private immutable fee; - int24 private immutable tickSpacing; - - struct Vesting { - uint256 amount; - uint256 epoch; - uint256 released; - } - - Vesting public vesting; - - constructor( - address _governance, - address _bold, - address _bribeToken, - uint256 _vestingEpochStart, - uint256 _vestingEpochDuration, - address _poolManager, - address _token, - uint24 _fee, - int24 _tickSpacing - ) BribeInitiative(_governance, _bold, _bribeToken) BaseHook(IPoolManager(_poolManager)) { - VESTING_EPOCH_START = _vestingEpochStart; - VESTING_EPOCH_DURATION = _vestingEpochDuration; - - if (uint256(uint160(address(_bold))) <= uint256(uint160(address(_token)))) { - currency0 = _bold; - currency1 = _token; - } else { - currency0 = _token; - currency1 = _bold; - } - fee = _fee; - tickSpacing = _tickSpacing; - } - - function vestingEpoch() public view returns (uint256) { - return ((block.timestamp - VESTING_EPOCH_START) / VESTING_EPOCH_DURATION) + 1; - } - - function vestingEpochStart() public view returns (uint256) { - return VESTING_EPOCH_START + ((vestingEpoch() - 1) * VESTING_EPOCH_DURATION); - } - - function _restartVesting(uint256 claimed) internal returns (Vesting memory) { - uint256 epoch = vestingEpoch(); - Vesting memory _vesting = vesting; - if (_vesting.epoch < epoch) { - _vesting.amount = claimed + _vesting.amount - uint256(_vesting.released); // roll over unclaimed amount - _vesting.epoch = epoch; - _vesting.released = 0; - vesting = _vesting; - emit RestartVesting(epoch, _vesting.amount); - } - return _vesting; - } - - /// @dev TO FIX - uint256 public received; - - /// @notice On claim we deposit the rewards - This is to prevent a griefing - function onClaimForInitiative(uint256, uint256 _bold) external override onlyGovernance { - received += _bold; - } - - function _donateToPool() internal returns (uint256) { - /// @audit TODO: Need to use storage value here I think - /// TODO: Test and fix release speed, which looks off - - // Claim again // NOTE: May be grifed - governance.claimForInitiative(address(this)); - - /// @audit Includes the queued rewards - uint256 toUse = received; - - // Reset - received = 0; - - // Rest of logic - Vesting memory _vesting = _restartVesting(toUse); - uint256 amount = - (_vesting.amount * (block.timestamp - vestingEpochStart()) / VESTING_EPOCH_DURATION) - _vesting.released; - - if (amount != 0) { - PoolKey memory key = poolKey(); - - manager.donate(key, amount, 0, bytes("")); - manager.sync(key.currency0); - IERC20(Currency.unwrap(key.currency0)).safeTransfer(address(manager), amount); - manager.settle(key.currency0); - - vesting.released += amount; - - emit DonateToPool(amount); - } - - return amount; - } - - function donateToPool() public returns (uint256) { - return abi.decode(manager.unlock(abi.encode(address(this), poolKey())), (uint256)); - } - - function poolKey() public view returns (PoolKey memory key) { - key = PoolKey({ - currency0: Currency.wrap(currency0), - currency1: Currency.wrap(currency1), - fee: fee, - tickSpacing: tickSpacing, - hooks: IHooks(address(this)) - }); - } - - function getHookPermissions() public pure override returns (Hooks.Permissions memory) { - return Hooks.Permissions({ - beforeInitialize: false, - afterInitialize: true, - beforeAddLiquidity: false, - beforeRemoveLiquidity: false, - afterAddLiquidity: true, - afterRemoveLiquidity: false, - beforeSwap: false, - afterSwap: false, - beforeDonate: false, - afterDonate: false, - beforeSwapReturnDelta: false, - afterSwapReturnDelta: false, - afterAddLiquidityReturnDelta: false, - afterRemoveLiquidityReturnDelta: false - }); - } - - function afterInitialize(address, PoolKey calldata key, uint160, int24, bytes calldata) - external - view - override - onlyByManager - returns (bytes4) - { - require(PoolId.unwrap(poolKey().toId()) == PoolId.unwrap(key.toId()), "UniV4Donations: invalid-pool-id"); - return this.afterInitialize.selector; - } - - function afterAddLiquidity( - address, - PoolKey calldata key, - IPoolManager.ModifyLiquidityParams calldata, - BalanceDelta delta, - bytes calldata - ) external override onlyByManager returns (bytes4, BalanceDelta) { - require(PoolId.unwrap(poolKey().toId()) == PoolId.unwrap(key.toId()), "UniV4Donations: invalid-pool-id"); - _donateToPool(); - return (this.afterAddLiquidity.selector, delta); - } - - function _unlockCallback(bytes calldata data) internal override returns (bytes memory) { - (address sender, PoolKey memory key) = abi.decode(data, (address, PoolKey)); - require(sender == address(this), "UniV4Donations: invalid-sender"); - require(PoolId.unwrap(poolKey().toId()) == PoolId.unwrap(key.toId()), "UniV4Donations: invalid-pool-id"); - return abi.encode(_donateToPool()); - } -} diff --git a/test/UniV4Donations.t.sol b/test/UniV4Donations.t.sol deleted file mode 100644 index e66c740..0000000 --- a/test/UniV4Donations.t.sol +++ /dev/null @@ -1,280 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.24; - -import {Test} from "forge-std/Test.sol"; - -import {IERC20} from "openzeppelin-contracts/contracts/interfaces/IERC20.sol"; - -import {IPoolManager, PoolManager, Deployers, TickMath} from "v4-core/test/utils/Deployers.sol"; -import {PoolModifyLiquidityTest} from "v4-core/src/test/PoolModifyLiquidityTest.sol"; - -import {IGovernance} from "../src/interfaces/IGovernance.sol"; -import {ILQTYStaking} from "../src/interfaces/ILQTYStaking.sol"; - -import {UniV4Donations} from "../src/UniV4Donations.sol"; -import {Governance} from "../src/Governance.sol"; -import {BaseHook, Hooks} from "../src/utils/BaseHook.sol"; - -import {MockERC20Tester} from "./mocks/MockERC20Tester.sol"; -import {MockStakingV1} from "./mocks/MockStakingV1.sol"; -import {MockStakingV1Deployer} from "./mocks/MockStakingV1Deployer.sol"; -import "./constants.sol"; - -contract UniV4DonationsImpl is UniV4Donations { - constructor( - address _governance, - address _bold, - address _bribeToken, - uint256 _vestingEpochStart, - uint256 _vestingEpochDuration, - address _poolManager, - address _token, - uint24 _fee, - int24 _tickSpacing, - BaseHook addressToEtch - ) - UniV4Donations( - _governance, - _bold, - _bribeToken, - _vestingEpochStart, - _vestingEpochDuration, - _poolManager, - _token, - _fee, - _tickSpacing - ) - { - BaseHook.validateHookAddress(addressToEtch); - } - - // make this a no-op in testing - function validateHookAddress(BaseHook _this) internal pure override {} -} - -abstract contract UniV4DonationsTest is Test, Deployers { - IERC20 internal lqty; - IERC20 internal lusd; - IERC20 internal usdc; - ILQTYStaking internal stakingV1; - - address internal constant user = address(0xF977814e90dA44bFA03b6295A0616a897441aceC); - address internal constant lusdHolder = address(0xcA7f01403C4989d2b1A9335A2F09dD973709957c); - - uint256 private constant REGISTRATION_FEE = 1e18; - uint256 private constant REGISTRATION_THRESHOLD_FACTOR = 0.01e18; - uint256 private constant UNREGISTRATION_THRESHOLD_FACTOR = 4e18; - uint256 private constant UNREGISTRATION_AFTER_EPOCHS = 4; - uint256 private constant VOTING_THRESHOLD_FACTOR = 0.04e18; - uint256 private constant MIN_CLAIM = 500e18; - uint256 private constant MIN_ACCRUAL = 1000e18; - uint256 private constant EPOCH_DURATION = 604800; - uint256 private constant EPOCH_VOTING_CUTOFF = 518400; - - Governance private governance; - address[] private initialInitiatives; - - UniV4Donations private uniV4Donations = - UniV4Donations(address(uint160(Hooks.AFTER_INITIALIZE_FLAG | Hooks.AFTER_ADD_LIQUIDITY_FLAG))); - - int24 constant MAX_TICK_SPACING = 32767; - - function setUp() public virtual { - initialInitiatives.push(address(uniV4Donations)); - - IGovernance.Configuration memory config = IGovernance.Configuration({ - registrationFee: REGISTRATION_FEE, - registrationThresholdFactor: REGISTRATION_THRESHOLD_FACTOR, - unregistrationThresholdFactor: UNREGISTRATION_THRESHOLD_FACTOR, - 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 - }); - - governance = new Governance( - address(lqty), address(lusd), address(stakingV1), address(lusd), config, address(this), initialInitiatives - ); - - manager = new PoolManager(500000); - modifyLiquidityRouter = new PoolModifyLiquidityTest(manager); - - UniV4DonationsImpl impl = new UniV4DonationsImpl( - address(governance), - address(lusd), - address(lqty), - block.timestamp, - EPOCH_DURATION, - address(manager), - address(usdc), - 400, - MAX_TICK_SPACING, - BaseHook(address(uniV4Donations)) - ); - - (, bytes32[] memory writes) = vm.accesses(address(impl)); - vm.etch(address(uniV4Donations), address(impl).code); - // for each storage key that was written during the hook implementation, copy the value over - unchecked { - for (uint256 i = 0; i < writes.length; i++) { - bytes32 slot = writes[i]; - vm.store(address(uniV4Donations), slot, vm.load(address(impl), slot)); - } - } - } - - function test_afterInitializeState() public { - manager.initialize(uniV4Donations.poolKey(), SQRT_PRICE_1_1, ZERO_BYTES); - } - - //// TODO: e2e test - With real governance and proposals - - function test_modifyPositionFuzz() public { - manager.initialize(uniV4Donations.poolKey(), SQRT_PRICE_1_1, ZERO_BYTES); - - vm.startPrank(lusdHolder); - lusd.transfer(address(uniV4Donations), 1000e18); - vm.stopPrank(); - - /// TODO: This is a mock call, we need a E2E test as well - vm.prank(address(governance)); - uniV4Donations.onClaimForInitiative(0, 1000e18); - - vm.startPrank(lusdHolder); - assertEq(uniV4Donations.donateToPool(), 0, "d"); - (uint256 amount, uint256 epoch, uint256 released) = uniV4Donations.vesting(); - assertEq(amount, 1000e18, "amt"); - assertEq(epoch, 1, "epoch"); - assertEq(released, 0, "released"); - - vm.warp(block.timestamp + uniV4Donations.VESTING_EPOCH_DURATION() / 2); - lusd.approve(address(modifyLiquidityRouter), type(uint256).max); - usdc.approve(address(modifyLiquidityRouter), type(uint256).max); - modifyLiquidityRouter.modifyLiquidity( - uniV4Donations.poolKey(), - IPoolManager.ModifyLiquidityParams( - TickMath.minUsableTick(MAX_TICK_SPACING), TickMath.maxUsableTick(MAX_TICK_SPACING), 1000, 0 - ), - bytes("") - ); - (amount, epoch, released) = uniV4Donations.vesting(); - assertEq(amount, 1000e18); - assertEq(released, amount * 50 / 100); - assertEq(epoch, 1); - - vm.warp(block.timestamp + (uniV4Donations.VESTING_EPOCH_DURATION() / 2) - 1); - uint256 donated = uniV4Donations.donateToPool(); - assertGt(donated, amount * 49 / 100); - assertLt(donated, amount * 50 / 100); - (amount, epoch, released) = uniV4Donations.vesting(); - assertEq(amount, 1000e18); - assertEq(epoch, 1); - assertGt(released, amount * 99 / 100); - - vm.warp(block.timestamp + 1); - vm.mockCall(address(governance), abi.encode(IGovernance.claimForInitiative.selector), abi.encode(uint256(0))); - uniV4Donations.donateToPool(); - (amount, epoch, released) = uniV4Donations.vesting(); - assertLt(amount, 0.01e18); - assertEq(epoch, 2); - assertEq(released, 0); - - vm.stopPrank(); - } - - function test_modifyPositionFuzz(uint128 amt) public { - manager.initialize(uniV4Donations.poolKey(), SQRT_PRICE_1_1, ZERO_BYTES); - - deal(address(lusd), address(uniV4Donations), amt); - - /// TODO: This is a mock call, we need a E2E test as well - vm.prank(address(governance)); - uniV4Donations.onClaimForInitiative(0, amt); - - vm.startPrank(lusdHolder); - assertEq(uniV4Donations.donateToPool(), 0, "d"); - (uint256 amount, uint256 epoch, uint256 released) = uniV4Donations.vesting(); - assertEq(amount, amt, "amt"); - assertEq(epoch, 1, "epoch"); - assertEq(released, 0, "released"); - - vm.warp(block.timestamp + uniV4Donations.VESTING_EPOCH_DURATION() / 2); - lusd.approve(address(modifyLiquidityRouter), type(uint256).max); - usdc.approve(address(modifyLiquidityRouter), type(uint256).max); - modifyLiquidityRouter.modifyLiquidity( - uniV4Donations.poolKey(), - IPoolManager.ModifyLiquidityParams( - TickMath.minUsableTick(MAX_TICK_SPACING), TickMath.maxUsableTick(MAX_TICK_SPACING), 1000, 0 - ), - bytes("") - ); - (amount, epoch, released) = uniV4Donations.vesting(); - assertEq(amount, amt); - assertEq(released, amount * 50 / 100); - assertEq(epoch, 1); - - vm.warp(block.timestamp + (uniV4Donations.VESTING_EPOCH_DURATION() / 2) - 1); - uint256 donated = uniV4Donations.donateToPool(); - assertGe(donated, amount * 49 / 100); - /// @audit Used to be Gt - assertLe(donated, amount * 50 / 100, "less than 50%"); - /// @audit Used to be Lt - (amount, epoch, released) = uniV4Donations.vesting(); - assertEq(amount, amt); - assertEq(epoch, 1); - assertGe(released, amount * 99 / 100); - /// @audit Used to be Gt - - vm.warp(block.timestamp + 1); - vm.mockCall(address(governance), abi.encode(IGovernance.claimForInitiative.selector), abi.encode(uint256(0))); - uniV4Donations.donateToPool(); - (amount, epoch, released) = uniV4Donations.vesting(); - - /// @audit Counterexample - // [FAIL. Reason: end results in dust: 1 > 0; counterexample: calldata=0x38b4b04f000000000000000000000000000000000000000000000000000000000000000c args=[12]] test_modifyPositionFuzz(uint128) (runs: 4, μ: 690381, ~: 690381) - if (amount > 1) { - assertLe(amount, amt / 100, "end results in dust"); - /// @audit Used to be Lt - } - - assertEq(epoch, 2); - assertEq(released, 0); - - vm.stopPrank(); - } -} - -contract MockedUniV4DonationsTest is UniV4DonationsTest, MockStakingV1Deployer { - function setUp() public override { - (MockStakingV1 mockStakingV1, MockERC20Tester mockLQTY, MockERC20Tester mockLUSD) = deployMockStakingV1(); - - MockERC20Tester mockUSDC = new MockERC20Tester("USD Coin", "USDC"); - vm.label(address(mockUSDC), "USDC"); - - mockLUSD.mint(lusdHolder, 1_000 + 1_000e18); - mockUSDC.mint(lusdHolder, 1_000); - - lqty = mockLQTY; - lusd = mockLUSD; - usdc = mockUSDC; - stakingV1 = mockStakingV1; - - super.setUp(); - } -} - -contract ForkedUniV4DonationsTest is UniV4DonationsTest { - function setUp() public override { - vm.createSelectFork(vm.rpcUrl("mainnet"), 20430000); - - lqty = IERC20(MAINNET_LQTY); - lusd = IERC20(MAINNET_LUSD); - usdc = IERC20(MAINNET_USDC); - stakingV1 = ILQTYStaking(MAINNET_LQTY_STAKING); - - super.setUp(); - } -}