From d4399fdc9d0d207d5592d09ac3ca8b4868a9e16a Mon Sep 17 00:00:00 2001 From: Jatin Jain Date: Fri, 25 Jul 2025 15:27:51 +0530 Subject: [PATCH 1/2] add: hook upgradeable --- remappings.txt | 3 ++- src/RenzoStability.sol | 26 ++++++++++++++------------ src/RenzoStabilityStorage.sol | 14 ++++++++++++++ 3 files changed, 30 insertions(+), 13 deletions(-) create mode 100644 src/RenzoStabilityStorage.sol diff --git a/remappings.txt b/remappings.txt index 9e5432f..9e649a9 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,4 +1,5 @@ @uniswap/v4-core/=lib/v4-core/ v4-core/=lib/v4-core/ v4-periphery/=lib/v4-periphery/ -v4-periphery/=lib/v4-periphery/ \ No newline at end of file +v4-periphery/=lib/v4-periphery/ +openzeppelin-contracts/=lib/v4-core/lib/openzeppelin-contracts/contracts/ \ No newline at end of file diff --git a/src/RenzoStability.sol b/src/RenzoStability.sol index 27cecf8..ffb0a8b 100644 --- a/src/RenzoStability.sol +++ b/src/RenzoStability.sol @@ -8,8 +8,9 @@ import {PoolKey} from "v4-core/src/types/PoolKey.sol"; import {Currency} from "v4-core/src/types/Currency.sol"; import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol"; import {SqrtPriceLibrary} from "./libraries/SqrtPriceLibrary.sol"; -import {IRateProvider} from "./interfaces/IRateProvider.sol"; import {LPFeeLibrary} from "v4-core/src/libraries/LPFeeLibrary.sol"; +import {Initializable} from "openzeppelin-contracts/proxy/utils/Initializable.sol"; +import "./RenzoStabilityStorage.sol"; /// @title RenzoStability /// @notice A peg stability hook, for pairs that trade at a 1:1 ratio @@ -17,20 +18,17 @@ import {LPFeeLibrary} from "v4-core/src/libraries/LPFeeLibrary.sol"; /// otherwise it charges a linearly-scaled fee based on the distance from the peg /// i.e. if the pool price is off by 0.05% the fee is 0.05%, if the price is off by 0.50% the fee is 0.5% /// In the associated pool, Token 0 should be ETH and Token 1 should be ezETH -contract RenzoStability is PegStabilityHook { +contract RenzoStability is + PegStabilityHook, + Initializable, + RenzoStabilityStorageV1 +{ using LPFeeLibrary for uint24; - IRateProvider public immutable rateProvider; - // Fee bps range where 1_000_000 = 100 % uint24 public constant MAX_FEE_BPS = 10_000; // 1% max fee allowed, 1% = 10_000 uint24 public constant MIN_FEE_BPS = 100; // 0.01% mix fee allowed - uint24 public immutable maxFeeBps; - uint24 public immutable minFeeBps; - - address public immutable ezETH; - // Errors // @dev error when Invalid zero input params error InvalidZeroInput(); @@ -44,13 +42,17 @@ contract RenzoStability is PegStabilityHook { /// @dev Error when Invalid Currency in Pool error InvalidPoolCurrency(); - constructor( - IPoolManager _poolManager, + /// @custom:oz-upgrades-unsafe-allow constructor + constructor(IPoolManager _poolManager) PegStabilityHook(_poolManager) { + _disableInitializers(); + } + + function initialize( IRateProvider _rateProvider, uint24 _minFee, uint24 _maxFee, address _ezETH - ) PegStabilityHook(_poolManager) { + ) public initializer { // check for 0 value inputs if ( address(_rateProvider) == address(0) || diff --git a/src/RenzoStabilityStorage.sol b/src/RenzoStabilityStorage.sol new file mode 100644 index 0000000..68a3974 --- /dev/null +++ b/src/RenzoStabilityStorage.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {IRateProvider} from "./interfaces/IRateProvider.sol"; + +abstract contract RenzoStabilityStorageV1 { + IRateProvider public rateProvider; + + uint24 public maxFeeBps; + + uint24 public minFeeBps; + + address public ezETH; +} From 8d78dd15c7f9b7e829b29925f300f954d54e8b67 Mon Sep 17 00:00:00 2001 From: Jatin Jain Date: Tue, 29 Jul 2025 14:12:56 +0530 Subject: [PATCH 2/2] add: upgradeable hook changes --- script/deployPoolWithRenzoStability.s.sol | 40 ++++++++++++------- ...olWithRenzoStabilityAndMintLiquidity.s.sol | 38 ++++++++++++------ test/RenzoStability.t.sol | 27 ++++++++++--- 3 files changed, 73 insertions(+), 32 deletions(-) diff --git a/script/deployPoolWithRenzoStability.s.sol b/script/deployPoolWithRenzoStability.s.sol index 965a903..4407f61 100644 --- a/script/deployPoolWithRenzoStability.s.sol +++ b/script/deployPoolWithRenzoStability.s.sol @@ -13,7 +13,7 @@ import {SqrtPriceLibrary} from "../src/libraries/SqrtPriceLibrary.sol"; import {LPFeeLibrary} from "v4-core/src/libraries/LPFeeLibrary.sol"; import {IRateProvider} from "../src/interfaces/IRateProvider.sol"; import {PoolId} from "v4-core/src/types/PoolId.sol"; - +import {TransparentUpgradeableProxy} from "openzeppelin-contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {Constants} from "./sepolia/Constants.sol"; import {Config} from "./sepolia/Config.sol"; @@ -26,6 +26,7 @@ contract RenzoStabilityScript is Script, Constants, Config { uint24 minFee = 100; uint24 maxFee = 10_000; address ezETH = 0x8d7F20137041334FBd7c87796f03b1999770Cc5f; + address proxyAdmin = 0x91625601e2BbBEb7171C40c79FadBCFbFf6A1982; // Pool configs // TODO: configure 0 zero values @@ -33,6 +34,17 @@ contract RenzoStabilityScript is Script, Constants, Config { uint160 startingPrice; // starting price in sqrtPriceX96 function run() public { + // Deploy the hook implementation + RenzoStability renzoStabilityImpl = new RenzoStability(POOLMANAGER); + + bytes memory initData = abi.encodeWithSelector( + RenzoStability.initialize.selector, + rateProvider, + minFee, + maxFee, + ezETH + ); + // hook contracts must have specific flags encoded in the address uint160 flags = uint160( Hooks.AFTER_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG @@ -40,30 +52,30 @@ contract RenzoStabilityScript is Script, Constants, Config { // Mine a salt that will produce a hook address with the correct flags bytes memory constructorArgs = abi.encode( - POOLMANAGER, - rateProvider, - minFee, - maxFee, - ezETH + address(renzoStabilityImpl), + proxyAdmin, + initData ); (address hookAddress, bytes32 salt) = HookMiner.find( CREATE2_FACTORY, flags, - type(RenzoStability).creationCode, + type(TransparentUpgradeableProxy).creationCode, constructorArgs ); startingPrice = SqrtPriceLibrary.exchangeRateToSqrtPriceX96( rateProvider.getRate() ); - // Deploy the hook using CREATE2 + // Deploy the hook proxy using CREATE2 vm.startBroadcast(); - RenzoStability renzoStability = new RenzoStability{salt: salt}( - POOLMANAGER, - rateProvider, - minFee, - maxFee, - ezETH + RenzoStability renzoStability = RenzoStability( + address( + new TransparentUpgradeableProxy{salt: salt}( + address(renzoStabilityImpl), + proxyAdmin, + initData + ) + ) ); require( address(renzoStability) == hookAddress, diff --git a/script/deployPoolWithRenzoStabilityAndMintLiquidity.s.sol b/script/deployPoolWithRenzoStabilityAndMintLiquidity.s.sol index e26c407..8abe861 100644 --- a/script/deployPoolWithRenzoStabilityAndMintLiquidity.s.sol +++ b/script/deployPoolWithRenzoStabilityAndMintLiquidity.s.sol @@ -21,6 +21,7 @@ import {Constants} from "./unichain/Constants.sol"; import {PoolId} from "v4-core/src/types/PoolId.sol"; import {Config} from "./unichain/Config.sol"; import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol"; +import {TransparentUpgradeableProxy} from "openzeppelin-contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; contract CreatePoolAndAddLiquidityScript is Script, Constants, Config { using CurrencyLibrary for Currency; @@ -37,6 +38,7 @@ contract CreatePoolAndAddLiquidityScript is Script, Constants, Config { uint24 minFee = 100; uint24 maxFee = 10_000; address ezETH = 0x2416092f143378750bb29b79eD961ab195CcEea5; + address proxyAdmin = 0x91625601e2BbBEb7171C40c79FadBCFbFf6A1982; address payable recipient = payable(0xAdef586efB3287Da4d7d1cbe15F12E0Be69e0DF0); @@ -145,6 +147,18 @@ contract CreatePoolAndAddLiquidityScript is Script, Constants, Config { } function _deployHook() internal returns (address) { + // Deploy the hook implementation + RenzoStability renzoStabilityImpl = new RenzoStability(POOLMANAGER); + + // Initialize data for the hook + bytes memory initData = abi.encodeWithSelector( + RenzoStability.initialize.selector, + rateProvider, + minFee, + maxFee, + ezETH + ); + // hook contracts must have specific flags encoded in the address uint160 flags = uint160( Hooks.AFTER_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG @@ -152,16 +166,14 @@ contract CreatePoolAndAddLiquidityScript is Script, Constants, Config { // Mine a salt that will produce a hook address with the correct flags bytes memory constructorArgs = abi.encode( - POOLMANAGER, - rateProvider, - minFee, - maxFee, - ezETH + address(renzoStabilityImpl), + proxyAdmin, + initData ); (address hookAddress, bytes32 salt) = HookMiner.find( CREATE2_DEPLOYER, flags, - type(RenzoStability).creationCode, + type(TransparentUpgradeableProxy).creationCode, constructorArgs ); @@ -170,12 +182,14 @@ contract CreatePoolAndAddLiquidityScript is Script, Constants, Config { ); // Deploy the hook using CREATE2 vm.startBroadcast(); - RenzoStability renzoStability = new RenzoStability{salt: salt}( - POOLMANAGER, - rateProvider, - minFee, - maxFee, - ezETH + RenzoStability renzoStability = RenzoStability( + address( + new TransparentUpgradeableProxy{salt: salt}( + address(renzoStabilityImpl), + proxyAdmin, + initData + ) + ) ); vm.stopBroadcast(); // check that the hook was deployed at the expected address diff --git a/test/RenzoStability.t.sol b/test/RenzoStability.t.sol index e27205f..2aa7bac 100644 --- a/test/RenzoStability.t.sol +++ b/test/RenzoStability.t.sol @@ -22,10 +22,13 @@ import {IRateProvider} from "../src/interfaces/IRateProvider.sol"; import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol"; import {SwapFeeEventAsserter} from "./utils/SwapFeeEventAsserter.sol"; import {SqrtPriceLibrary} from "../src/libraries/SqrtPriceLibrary.sol"; +import {TransparentUpgradeableProxy, ProxyAdmin} from "openzeppelin-contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; contract RenzoStabilityTest is Deployers { + address owner = makeAddr("owner"); // Hook configs. TODO: configure IRateProvider rateProvider = IRateProvider(makeAddr("rateProvider")); + uint256 exchangeRate = 1046726277868365115; uint24 minFee = 100; uint24 maxFee = 10_000; @@ -51,20 +54,32 @@ contract RenzoStabilityTest is Deployers { vm.deal(address(this), 1_000_000e18); + // Deploy ProxyAdmin + ProxyAdmin proxyAdmin = new ProxyAdmin(owner); + + // Deploy the hook implementation + RenzoStability renzoStabilityImpl = new RenzoStability(manager); + + bytes memory initData = abi.encodeWithSelector( + RenzoStability.initialize.selector, + rateProvider, + minFee, + maxFee, + Currency.unwrap(currency1) + ); + // Deploy the hook to an address with the correct flags address flags = address( uint160(Hooks.AFTER_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG) ^ (0x4444 << 144) // Namespace the hook to avoid collisions ); bytes memory constructorArgs = abi.encode( - manager, - rateProvider, - minFee, - maxFee, - Currency.unwrap(currency1) + address(renzoStabilityImpl), + address(proxyAdmin), + initData ); //Add all the necessary constructor arguments from the hook deployCodeTo( - "RenzoStability.sol:RenzoStability", + "openzeppelin-contracts/proxy/transparent/TransparentUpgradeableProxy.sol:TransparentUpgradeableProxy", constructorArgs, flags );