diff --git a/script/HookDonation.s.sol b/script/HookDonation.s.sol index 97c0dc5..18041e3 100644 --- a/script/HookDonation.s.sol +++ b/script/HookDonation.s.sol @@ -3,17 +3,19 @@ pragma solidity ^0.8.13; import {Script, console} from "forge-std/Script.sol"; import {AfterSwapDonationHook} from "../src/HookDonation.sol"; -import {Hooks} from "lib/v4-periphery/lib/v4-core/src/libraries/Hooks.sol"; -import {Deployers} from "lib/v4-core/test/utils/Deployers.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; import {DonationTest} from "../test/HookDonation.t.sol"; import {HookMiner} from "../test/utils/HookMiner.sol"; -import {PoolKey} from "lib/v4-core/src/types/PoolKey.sol"; +// K:\Development\Ethereum\UniswapHook-Donations\lib\v4-periphery\lib\v4-core\src\interfaces\IHooks.sol +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {Constants} from "lib/v4-core/test/utils/Constants.sol"; -import {IHooks} from "lib/v4-core/src/interfaces/IHooks.sol"; -import {CurrencyLibrary, Currency} from "lib/v4-core/src/types/Currency.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +// K:\Development\Ethereum\UniswapHook-Donations\lib\v4-periphery\lib\v4-core\src\interfaces\IPoolManager.sol import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; -contract AfterSwapDonationHookScript is Script, Deployers { +contract AfterSwapDonationHookDeployScript is Script, Deployers { address constant CREATE2_DEPLOYER = address(0x4e59b44847b379578588920cA78FbF26c0B4956C); AfterSwapDonationHook public donationHook; @@ -29,9 +31,8 @@ contract AfterSwapDonationHookScript is Script, Deployers { uint24 fee = 3000; // Fee in basis points, ie, 0.30% uint160 flags = uint160(Hooks.AFTER_SWAP_FLAG); - uint256 seed = 0; (address hookAddress, bytes32 salt) = HookMiner.find( - CREATE2_DEPLOYER, flags, seed, type(AfterSwapDonationHook).creationCode, abi.encode(address(manager)) + CREATE2_DEPLOYER, flags, type(AfterSwapDonationHook).creationCode, abi.encode(address(manager)) ); donationHook = new AfterSwapDonationHook{salt: salt}(IPoolManager(address(manager))); @@ -49,5 +50,4 @@ contract AfterSwapDonationHookScript is Script, Deployers { vm.stopBroadcast(); } - } diff --git a/script/V4Deployer.s.sol b/script/V4Deployer.s.sol index 58361ab..24f67c7 100644 --- a/script/V4Deployer.s.sol +++ b/script/V4Deployer.s.sol @@ -1,67 +1,185 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.26; - -import {Script} from "forge-std/Script.sol"; -import {PoolManager} from "v4-core/PoolManager.sol"; -import {PoolSwapTest} from "v4-core/test/PoolSwapTest.sol"; -import {PoolModifyLiquidityTest} from "v4-core/test/PoolModifyLiquidityTest.sol"; -import {PoolDonateTest} from "v4-core/test/PoolDonateTest.sol"; -import {PoolTakeTest} from "v4-core/test/PoolTakeTest.sol"; -import {PoolClaimsTest} from "v4-core/test/PoolClaimsTest.sol"; -import {Hooks} from "lib/v4-periphery/lib/v4-core/src/libraries/Hooks.sol"; -import {HookMiner} from "../test/utils/HookMiner.sol"; -import {AfterSwapDonationHook} from "../src/HookDonation.sol"; -import {PoolKey} from "lib/v4-core/src/types/PoolKey.sol"; -import {IHooks} from "lib/v4-core/src/interfaces/IHooks.sol"; -import {CurrencyLibrary, Currency} from "lib/v4-core/src/types/Currency.sol"; -import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; -import {console} from "lib/v4-periphery/lib/v4-core/lib/forge-std/src/console.sol"; - - -contract V4Deployer is Script { - address constant CREATE2_DEPLOYER = address(0x4e59b44847b379578588920cA78FbF26c0B4956C); - AfterSwapDonationHook public donationHook; - - function run() public { - vm.startBroadcast(); - - PoolManager manager = new PoolManager(); - PoolSwapTest swapRouter = new PoolSwapTest(manager); - - // Anything else you need to do like minting mock ERC20s or initializing a pool - // you need to do directly here as well without using Deployers - // Define tokens and pool parameters - address tokenA = 0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9; // WETH - address tokenB = 0xaA8E23Fb1079EA71e0a56F48a2aA51851D8433D0; // USDT - uint24 fee = 3000; // Fee in basis points, ie, 0.30% - - uint160 flags = uint160(Hooks.AFTER_SWAP_FLAG); - uint256 tempValue = 0; - uint256 seed = tempValue; - address hookAddress = address(0); - bytes32 salt; - while (hookAddress == address(0)) { - (hookAddress, salt) = HookMiner.find( - CREATE2_DEPLOYER, flags, seed, type(AfterSwapDonationHook).creationCode, abi.encode(address(manager)) - ); - if (hookAddress == address(0)) { - seed = uint256(salt)+1; - } - } - console.log("Hook Address: %s", hookAddress); - donationHook = new AfterSwapDonationHook{salt: salt}(IPoolManager(address(manager))); - - // Create PoolKey - PoolKey memory key = PoolKey({ - currency0: Currency.wrap(tokenA), - currency1: Currency.wrap(tokenB), - fee: fee, - tickSpacing: 60, - hooks: IHooks(hookAddress) - }); - - - - vm.stopBroadcast(); - } -} \ No newline at end of file +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +import {Script} from "forge-std/Script.sol"; +// import {PoolManager} from "v4-core/PoolManager.sol"; +import {PoolSwapTest} from "@uniswap/v4-core/src/test/PoolSwapTest.sol"; +// import {PoolModifyLiquidityTest} from "v4-core/test/PoolModifyLiquidityTest.sol"; +import {PoolDonateTest} from "@uniswap/v4-core/src/test/PoolDonateTest.sol"; +import {PoolTakeTest} from "@uniswap/v4-core/src/test/PoolTakeTest.sol"; +import {PoolClaimsTest} from "@uniswap/v4-core/src/test/PoolClaimsTest.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {HookMiner} from "../test/utils/HookMiner.sol"; +import {AfterSwapDonationHook} from "../src/HookDonation.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {console} from "@uniswap/v4-core/lib/forge-std/src/console.sol"; +import {MockERC20} from "solmate/src/test/utils/mocks/MockERC20.sol"; +import {Constants} from "lib/v4-core/test/utils/Constants.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; +import {PoolModifyLiquidityTest} from "@uniswap/v4-core/src/test/PoolModifyLiquidityTest.sol"; +// K:\Development\Ethereum\UniswapHook-Donations\lib\v4-periphery\lib\v4-core\src\types\PoolId.sol +import {PoolId} from "@uniswap/v4-core/src/types/PoolId.sol"; +// K:\Development\Ethereum\UniswapHook-Donations\lib\v4-periphery\lib\v4-core\src\libraries\LPFeeLibrary.sol +import {LPFeeLibrary} from "@uniswap/v4-core/src/libraries/LPFeeLibrary.sol"; + +contract V4Deployer is Script { + using LPFeeLibrary for uint24; + Currency internal currency0; + Currency internal currency1; + + address constant CREATE2_DEPLOYER = address(0x4e59b44847b379578588920cA78FbF26c0B4956C); + IPoolManager.ModifyLiquidityParams public LIQUIDITY_PARAMS = + IPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: 1e18, salt: 0}); + + AfterSwapDonationHook public donationHook; + + bytes constant ZERO_BYTES = Constants.ZERO_BYTES; + IPoolManager _manager; + PoolModifyLiquidityTest modifyLiquidityRouter; + + function run() public payable { + vm.startBroadcast(); + + // https://discord.com/channels/1202009457014349844/1283886646604988437 + PoolManager manager = PoolManager(address(0xCa6DBBe730e31fDaACaA096821199EEED5AD84aE)); + IPoolManager iManager = IPoolManager(address(0xCa6DBBe730e31fDaACaA096821199EEED5AD84aE)); + modifyLiquidityRouter = PoolModifyLiquidityTest(address(0x1f03f235e371202e49194F63C7096F5697848822)); + PoolSwapTest swapRouter = PoolSwapTest(address(0xEc9537B6D66c14E872365AB0EAE50dF7b254D4Fc)); + + // Mint mock ERC20s or initialize a pool + // Do it directly here as well without using Deployers + // Define tokens and pool parameters + MockERC20 token0 = new MockERC20("Wrapped Ether", "WETH", 18); + MockERC20 token1 = new MockERC20("Tether", "USDT", 6); + + address wallet = 0x0080614a1B5821340C73c5A0455e55CF20b1a164; + token0.mint(wallet, 1001 ether); + + uint256 MAX_TOKENS = 2 ** 255; + token0.mint(address(this), MAX_TOKENS); + token1.mint(address(this), MAX_TOKENS); + + uint160 flags = uint160(Hooks.AFTER_SWAP_FLAG); + + uint24 fee = 3000; // Fee in basis points, ie, 0.30% + (address hookAddress, bytes32 salt) = HookMiner.find( + CREATE2_DEPLOYER, flags, type(AfterSwapDonationHook).creationCode, abi.encode(address(iManager)) + ); + console.log("Hook Address: %s", hookAddress); + donationHook = new AfterSwapDonationHook{salt: salt}(iManager); + console.log("AfterSwapDonationHook address: %s", address(donationHook)); + console.log("Token0: %s", address(token0)); + console.log("Token1: %s", address(token1)); + + token0.approve(address(modifyLiquidityRouter), type(uint256).max); + token0.approve(address(swapRouter), type(uint256).max); + token0.approve(address(manager), type(uint256).max); + + token1.approve(address(modifyLiquidityRouter), type(uint256).max); + token1.approve(address(swapRouter), type(uint256).max); + token1.approve(address(manager), type(uint256).max); + + token0.approve(address(manager), type(uint256).max); + token1.approve(address(manager), type(uint256).max); + + // Create PoolKey + PoolKey memory key = PoolKey({ + currency0: Currency.wrap(address(token0)), + currency1: Currency.wrap(address(token1)), + fee: fee, + tickSpacing: 60, + hooks: IHooks(hookAddress) + }); + + // IPoolManager.ModifyLiquidityParams memory LIQUIDITY_PARAMS = + // IPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: 1e18, salt: 0}); + + // initPoolAndAddLiquidity + manager.initialize(key, Constants.SQRT_PRICE_1_1, ""); + console.log("Initialize successful"); + uint256 balance; + balance = token0.balanceOf(wallet); + console.log("Wallet balance: %s", balance); + modifyLiquidityRouter.modifyLiquidity{value: msg.value}(key, LIQUIDITY_PARAMS, ZERO_BYTES); + + // initPoolAndAddLiquidity( + // Currency.wrap(address(token0)), Currency.wrap(address(token1)), IHooks(hookAddress), + // fee, Constants.SQRT_PRICE_1_1, Constants.ZERO_BYTES + // ); + + vm.stopBroadcast(); + } + + // function initPool( + // Currency _currency0, + // Currency _currency1, + // IHooks hooks, + // uint24 fee, + // uint160 sqrtPriceX96, + // bytes memory initData + // ) internal returns (PoolKey memory _key, PoolId id) { + // _key = PoolKey(_currency0, _currency1, fee, fee.isDynamicFee() ? int24(60) : int24(fee / 100 * 2), hooks); + // id = _key.toId(); + // _manager.initialize(_key, sqrtPriceX96, initData); + // } + + // function initPool( + // Currency _currency0, + // Currency _currency1, + // IHooks hooks, + // uint24 fee, + // int24 tickSpacing, + // uint160 sqrtPriceX96, + // bytes memory initData + // ) internal returns (PoolKey memory _key, PoolId id) { + // _key = PoolKey(_currency0, _currency1, fee, tickSpacing, hooks); + // id = _key.toId(); + // _manager.initialize(_key, sqrtPriceX96, initData); + // } + + // function initPoolAndAddLiquidity( + // Currency _currency0, + // Currency _currency1, + // IHooks hooks, + // uint24 fee, + // uint160 sqrtPriceX96, + // bytes memory initData + // ) internal returns (PoolKey memory _key, PoolId id) { + // (_key, id) = initPool(_currency0, _currency1, hooks, fee, sqrtPriceX96, initData); + // modifyLiquidityRouter.modifyLiquidity{value: msg.value}(_key, LIQUIDITY_PARAMS, ZERO_BYTES); + // } + + // function deployMintAndApprove2Currencies() internal returns (Currency, Currency) { + // Currency _currencyA = deployMintAndApproveCurrency(); + // Currency _currencyB = deployMintAndApproveCurrency(); + + // (currency0, currency1) = + // SortTokens.sort(MockERC20(Currency.unwrap(_currencyA)), MockERC20(Currency.unwrap(_currencyB))); + // return (currency0, currency1); + // } + + // function deployMintAndApproveCurrency() internal returns (Currency currency) { + // MockERC20 token = deployTokens(1, 2 ** 255)[0]; + + // address[9] memory toApprove = [ + // address(swapRouter), + // address(swapRouterNoChecks), + // address(modifyLiquidityRouter), + // address(modifyLiquidityNoChecks), + // address(donateRouter), + // address(takeRouter), + // address(claimsRouter), + // address(nestedActionRouter.executor()), + // address(actionsRouter) + // ]; + + // for (uint256 i = 0; i < toApprove.length; i++) { + // token.approve(toApprove[i], Constants.MAX_UINT256); + // } + + // return Currency.wrap(address(token)); + // } +} diff --git a/src/HookDonation.sol b/src/HookDonation.sol index e8387de..a3dc1cd 100644 --- a/src/HookDonation.sol +++ b/src/HookDonation.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import {BaseHook} from "lib/v4-periphery/src/base/hooks/BaseHook.sol"; -import {PoolKey} from "lib/v4-periphery/lib/v4-core/src/types/PoolKey.sol"; +import {BaseHook} from "v4-periphery/src/base/hooks/BaseHook.sol"; +import {PoolKey} from "v4-periphery/lib/v4-core/src/types/PoolKey.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; -import {BalanceDelta} from "lib/v4-periphery/lib/v4-core/src/types/BalanceDelta.sol"; -import {Hooks} from "lib/v4-periphery/lib/v4-core/src/libraries/Hooks.sol"; -import {CurrencyLibrary, Currency} from "lib/v4-periphery/lib/v4-core/src/types/Currency.sol"; +import {BalanceDelta} from "v4-periphery/lib/v4-core/src/types/BalanceDelta.sol"; +import {Hooks} from "v4-periphery/lib/v4-core/src/libraries/Hooks.sol"; +import {CurrencyLibrary, Currency} from "v4-periphery/lib/v4-core/src/types/Currency.sol"; import {ERC20} from "lib/v4-core/lib/solmate/src/tokens/ERC20.sol"; import {IERC20Minimal} from "lib/v4-core/src/interfaces/external/IERC20Minimal.sol"; diff --git a/test/HookDonation.t.sol b/test/HookDonation.t.sol index ed1c49c..83a0840 100644 --- a/test/HookDonation.t.sol +++ b/test/HookDonation.t.sol @@ -132,7 +132,7 @@ contract DonationTest is Test, Deployers { // Approve the donationHook contract to spend on behalf of tx.origin, which is the user / EOA // This is essential, otherwise, in afterSwap, token.transferFrom will fail // Workflow: The user will need to call approve for token0 on their own. - t0.approve(address(donationHook), type(uint256).max); + t0.approve(address(donationHook), type(uint256).max); vm.stopPrank(); diff --git a/test/utils/HookMiner.sol b/test/utils/HookMiner.sol index 0b9323e..d6b30c4 100644 --- a/test/utils/HookMiner.sol +++ b/test/utils/HookMiner.sol @@ -1,60 +1,47 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.19; +pragma solidity ^0.8.21; /// @title HookMiner - a library for mining hook addresses /// @dev This library is intended for `forge test` environments. There may be gotchas when using salts in `forge script` or `forge create` library HookMiner { - // mask to slice out the top 10 bits of the address - uint160 constant FLAG_MASK = 0x3FF << 150; + // mask to slice out the bottom 14 bit of the address + uint160 constant FLAG_MASK = 0x3FFF; // Maximum number of iterations to find a salt, avoid infinite loops - uint256 constant MAX_LOOP = 10_000; + uint256 constant MAX_LOOP = 100_000; /// @notice Find a salt that produces a hook address with the desired `flags` - /// @param deployer The address that will deploy the hook. - /// In `forge test`, this will be the test contract `address(this)` or the pranking address - /// In `forge script`, this should be `0x4e59b44847b379578588920cA78FbF26c0B4956C` (CREATE2 Deployer Proxy) + /// @param deployer The address that will deploy the hook. In `forge test`, this will be the test contract `address(this)` or the pranking address + /// In `forge script`, this should be `0x4e59b44847b379578588920cA78FbF26c0B4956C` (CREATE2 Deployer Proxy) /// @param flags The desired flags for the hook address - /// @param seed Use 0 for as a default. An optional starting salt when linearly searching for a salt - /// Useful for finding salts for multiple hooks with the same flags /// @param creationCode The creation code of a hook contract. Example: `type(Counter).creationCode` /// @param constructorArgs The encoded constructor arguments of a hook contract. Example: `abi.encode(address(manager))` - /// @return hookAddress salt and corresponding address that was found - /// The salt can be used in `new Hook{salt: salt}()` - function find( - address deployer, - uint160 flags, - uint256 seed, - bytes memory creationCode, - bytes memory constructorArgs - ) external pure returns (address, bytes32) { + /// @return hookAddress salt and corresponding address that was found. The salt can be used in `new Hook{salt: salt}()` + function find(address deployer, uint160 flags, bytes memory creationCode, bytes memory constructorArgs) + internal + view + returns (address, bytes32) + { address hookAddress; bytes memory creationCodeWithArgs = abi.encodePacked(creationCode, constructorArgs); - uint256 salt = seed; - for (salt; salt < MAX_LOOP+seed;) { + uint256 salt; + for (salt; salt < MAX_LOOP; salt++) { hookAddress = computeAddress(deployer, salt, creationCodeWithArgs); - if (uint160(hookAddress) & FLAG_MASK == flags) { + if (uint160(hookAddress) & FLAG_MASK == flags && hookAddress.code.length == 0) { return (hookAddress, bytes32(salt)); } - - unchecked { - ++salt; - } } - - // revert("HookMiner: could not find salt"); - return (address(0), bytes32(salt)); + revert("HookMiner: could not find salt"); } /// @notice Precompute a contract address deployed via CREATE2 - /// @param deployer The address that will deploy the hook - /// In `forge test`, this will be the test contract `address(this)` or the pranking address - /// In `forge script`, this should be `0x4e59b44847b379578588920cA78FbF26c0B4956C` (CREATE2 Deployer Proxy) + /// @param deployer The address that will deploy the hook. In `forge test`, this will be the test contract `address(this)` or the pranking address + /// In `forge script`, this should be `0x4e59b44847b379578588920cA78FbF26c0B4956C` (CREATE2 Deployer Proxy) /// @param salt The salt used to deploy the hook /// @param creationCode The creation code of a hook contract function computeAddress(address deployer, uint256 salt, bytes memory creationCode) - public + internal pure returns (address hookAddress) {