{"content":{"title":"uniswap-v4 fees&hooks 合约代码分析","body":"# uniswap-v4 fees&hooks 合约代码分析\r\n\r\n## 备注\r\n\r\n时间：2023 年 9 月 1 日\r\n\r\n作者：[33357](https://github.com/33357)\r\n\r\n## 正文\r\n\r\nfees 和 hooks 是 Uniswap-v4 core 的手续费和钩子合约，通过对该合约的解析可以了解其功能和对接方法。\r\n\r\n### Fees.sol\r\n#### 设置 protocolFeeController 地址\r\n- 外部函数（仅合约内部可以调用）\r\n    - setProtocolFeeController\r\n        - 代码解析\r\n            ``` javascript\r\n            function setProtocolFeeController(\r\n                IProtocolFeeController controller // controller 合约地址\r\n            ) external onlyOwner {\r\n                // 设置 controller 合约地址\r\n                protocolFeeController = controller;\r\n                // 触发 controller 合约地址更新事件\r\n                emit ProtocolFeeControllerUpdated(address(controller));\r\n            }\r\n            ```\r\n        - 总结\r\n            函数 `setProtocolFeeController` 用于更新协议手续费控制合约地址。\r\n\r\n#### 提取协议手续费\r\n- 外部函数（仅合约内部可以调用）\r\n    - collectProtocolFees\r\n        - 代码解析\r\n            ``` javascript\r\n            function collectProtocolFees(\r\n                address recipient, // 接收地址\r\n                Currency currency, // 提取货币\r\n                uint256 amount // 提取数量\r\n            ) external returns (\r\n                uint256 amountCollected // 返回已收数量\r\n            ) {\r\n                // 只允许 owner 和 protocolFeeController 调用\r\n                if (msg.sender != owner && msg.sender != address(protocolFeeController)) revert InvalidCaller();\r\n                // 如果 amount 为 0 就提取全部协议手续费\r\n                amountCollected = (amount == 0) ? protocolFeesAccrued[currency] : amount;\r\n                // 应计协议手续费更新\r\n                protocolFeesAccrued[currency] -= amountCollected;\r\n                // 转账 currency\r\n                currency.transfer(recipient, amountCollected);\r\n            }\r\n            ```\r\n        - 总结\r\n            函数 `collectProtocolFees` 用于提取协议手续费。\r\n\r\n#### 提取 hook 手续费\r\n- 外部函数（仅合约内部可以调用）\r\n    - collectHookFees\r\n        - 代码解析\r\n            ``` javascript\r\n            function collectHookFees(\r\n                address recipient, // 接收地址\r\n                Currency currency, // 提取货币\r\n                uint256 amount // 提取数量\r\n            ) external returns (\r\n                uint256 amountCollected // 返回已收数量\r\n            ) {\r\n                address hookAddress = msg.sender;\r\n                // 如果提取数量为 0 就提取全部 hook 手续费\r\n                amountCollected = (amount == 0) ? hookFeesAccrued[hookAddress][currency] : amount;\r\n                // 如果 recipient 为 0 地址，就提取到 hook 地址\r\n                recipient = (recipient == address(0)) ? hookAddress : recipient;\r\n                // 应计 hook 手续费更新\r\n                hookFeesAccrued[hookAddress][currency] -= amountCollected;\r\n                // 转账 currency\r\n                currency.transfer(recipient, amountCollected);\r\n            }\r\n            ```\r\n        - 总结\r\n            函数 `collectHookFees` 用于提取 hook 手续费。\r\n\r\n#### 获取协议手续费\r\n- 内部函数（仅合约内部可以调用）\r\n    - _fetchProtocolFees\r\n        - 代码解析\r\n            ``` javascript\r\n            function _fetchProtocolFees(\r\n                PoolKey memory key\r\n                /* struct PoolKey {\r\n                    Currency currency0; // pool 中数值较小的 currency 地址\r\n                    Currency currency1; // pool 中数值较大的 currency 地址\r\n                    uint24 fee; // 手续费\r\n                    int24 tickSpacing; // tick 单位间距\r\n                    IHooks hooks; // hooks 地址\r\n                } */\r\n            ) internal view returns (\r\n                uint8 protocolSwapFee, // 协议 swap 手续费\r\n                uint8 protocolWithdrawFee // 协议提取流动性手续费\r\n            ) {\r\n                // protocolFeeController 不能为 0 地址\r\n                if (address(protocolFeeController) != address(0)) {\r\n                    // 剩余 gaslimit 必须大于等于 controllerGasLimit\r\n                    if (gasleft() < controllerGasLimit) revert ProtocolFeeCannotBeFetched();\r\n                    // 获取协议 swap 手续费和协议提取流动性手续费\r\n                    try protocolFeeController.protocolFeesForPool{gas: controllerGasLimit}(key) returns (\r\n                        uint8 updatedProtocolSwapFee, uint8 updatedProtocolWithdrawFee\r\n                    ) {\r\n                        protocolSwapFee = updatedProtocolSwapFee;\r\n                        protocolWithdrawFee = updatedProtocolWithdrawFee;\r\n                    } catch {}\r\n                    // 检查协议 swap 手续费\r\n                    _checkProtocolFee(protocolSwapFee);\r\n                    // 检查协议提取流动性手续费\r\n                    _checkProtocolFee(protocolWithdrawFee);\r\n                }\r\n            }\r\n            ```\r\n        - 总结\r\n            函数 `_fetchProtocolFees` 用于获取协议手续费数量。\r\n\r\n    - _checkProtocolFee\r\n        - 代码解析\r\n            ``` javascript\r\n            function _checkProtocolFee(\r\n                uint8 fee // 协议手续费\r\n            ) internal pure {\r\n                if (fee != 0) {\r\n                    // fee0 是 fee 后 4 位数值\r\n                    uint8 fee0 = fee % 16;\r\n                    // fee1 是 fee 前 4 位数值\r\n                    uint8 fee1 = fee >> 4;\r\n                    // fee0 和 fee1 不为 0 的情况下不能低于 4\r\n                    if (\r\n                        (fee0 != 0 && fee0 < MIN_PROTOCOL_FEE_DENOMINATOR) || (fee1 != 0 && fee1 < MIN_PROTOCOL_FEE_DENOMINATOR)\r\n                    ) {\r\n                        revert FeeTooLarge();\r\n                    }\r\n                }\r\n            }\r\n            ```\r\n        - 总结\r\n            函数 `_checkProtocolFee` 用于检查协议手续费是否合规。\r\n\r\n#### 获取 hook 手续费\r\n- 内部函数（仅合约内部可以调用）\r\n    - _fetchHookFees\r\n        - 代码解析\r\n            ``` javascript\r\n            function _fetchHookFees(\r\n                PoolKey memory key\r\n                /* struct PoolKey {\r\n                    Currency currency0; // pool 中数值较小的 currency 地址\r\n                    Currency currency1; // pool 中数值较大的 currency 地址\r\n                    uint24 fee; // 手续费\r\n                    int24 tickSpacing; // tick 单位间距\r\n                    IHooks hooks; // hooks 地址\r\n                } */\r\n            ) internal view returns (\r\n                uint8 hookSwapFee, // hook swap 手续费\r\n                uint8 hookWithdrawFee // hook 提取流动性手续费\r\n            ) {\r\n                // 获取 hook swap 手续费\r\n                if (key.fee.hasHookSwapFee()) {\r\n                    hookSwapFee = IHookFeeManager(address(key.hooks)).getHookSwapFee(key);\r\n                }\r\n                // 获取 hook 提取流动性手续费\r\n                if (key.fee.hasHookWithdrawFee()) {\r\n                    hookWithdrawFee = IHookFeeManager(address(key.hooks)).getHookWithdrawFee(key);\r\n                }\r\n            }\r\n            ```\r\n        - 总结\r\n            函数 `_fetchHookFees` 用于获取 hook 手续费数量。\r\n\r\n### FeeLibrary.sol\r\n#### 设置 protocolFeeController 地址\r\n- 内部函数（仅合约内部可以调用）\r\n    - isDynamicFee\r\n        - 代码解析\r\n            ``` javascript\r\n            function isDynamicFee(\r\n                uint24 self // 手续费\r\n            ) internal pure returns (\r\n                bool // 返回是否是动态费用\r\n            ) {\r\n                // 是否是动态费用\r\n                return self & DYNAMIC_FEE_FLAG != 0;\r\n            }\r\n            ```\r\n        - 总结\r\n            函数 `isDynamicFee` 用于检查手续费是否是动态费用。\r\n\r\n    - hasHookSwapFee\r\n        - 代码解析\r\n            ``` javascript\r\n            function hasHookSwapFee(\r\n                uint24 self // 手续费\r\n            ) internal pure returns (\r\n                bool // 返回是否有 hook swap 手续费\r\n            ) {\r\n                // 是否有 hook swap 手续费\r\n                return self & HOOK_SWAP_FEE_FLAG != 0;\r\n            }\r\n            ```\r\n        - 总结\r\n            函数 `hasHookSwapFee` 用于检查是否有 hook swap 手续费。\r\n        \r\n    - hasHookWithdrawFee\r\n        - 代码解析\r\n            ``` javascript\r\n            function hasHookWithdrawFee(\r\n                uint24 self // 手续费\r\n            ) internal pure returns (\r\n                bool // 返回是否有 hook 提取流动性手续费\r\n            ) {\r\n                // 是否有 hook 提取流动性手续费\r\n                return self & HOOK_WITHDRAW_FEE_FLAG != 0;\r\n            }\r\n            ```\r\n        - 总结\r\n            函数 `hasHookWithdrawFee` 用于检查手续费是否有 hook 提取流动性手续费。\r\n    \r\n    - isStaticFeeTooLarge\r\n        - 代码解析\r\n            ``` javascript\r\n            function isStaticFeeTooLarge(\r\n                uint24 self // 手续费\r\n            ) internal pure returns (\r\n                bool // 返回是否静态手续费太高\r\n            ) {\r\n                // 是否静态手续费太高\r\n                return self & STATIC_FEE_MASK >= 1000000;\r\n            }\r\n            ```\r\n        - 总结\r\n            函数 `isStaticFeeTooLarge` 用于检查手续费是否静态手续费太高。\r\n    \r\n    - getStaticFee\r\n        - 代码解析\r\n            ``` javascript\r\n            function getStaticFee(\r\n                uint24 self // 手续费\r\n            ) internal pure returns (\r\n                uint24 // 返回静态手续费\r\n            ) {\r\n                // 获取静态手续费\r\n                return self & STATIC_FEE_MASK;\r\n            }\r\n            ```\r\n        - 总结\r\n            函数 `getStaticFee` 用于获取手续费的静态手续费数量。\r\n\r\n### Hooks.sol\r\n#### 设置 protocolFeeController 地址\r\n- 内部函数（仅合约内部可以调用）\r\n    - validateHookAddress\r\n        - 代码解析\r\n            ``` javascript\r\n            function validateHookAddress(\r\n                IHooks self, // hook 地址\r\n                Calls memory calls\r\n                /* struct Calls {\r\n                    bool beforeInitialize; // 是否有 beforeInitialize 的 hook\r\n                    bool afterInitialize; // 是否有 afterInitialize 的 hook\r\n                    bool beforeModifyPosition; // 是否有 beforeModifyPosition 的 hook\r\n                    bool afterModifyPosition; // 是否有 afterModifyPosition 的 hook\r\n                    bool beforeSwap; // 是否有 beforeSwap 的 hook\r\n                    bool afterSwap; // 是否有 afterSwap 的 hook\r\n                    bool beforeDonate; // 是否有 beforeDonate 的 hook\r\n                    bool afterDonate; // 是否有 afterDonate 的 hook\r\n                } */\r\n            ) internal pure {\r\n                // 检查 hook 地址是否匹配 calls\r\n                if (\r\n                    calls.beforeInitialize != shouldCallBeforeInitialize(self)\r\n                        || calls.afterInitialize != shouldCallAfterInitialize(self)\r\n                        || calls.beforeModifyPosition != shouldCallBeforeModifyPosition(self)\r\n                        || calls.afterModifyPosition != shouldCallAfterModifyPosition(self)\r\n                        || calls.beforeSwap != shouldCallBeforeSwap(self) || calls.afterSwap != shouldCallAfterSwap(self)\r\n                        || calls.beforeDonate != shouldCallBeforeDonate(self) || calls.afterDonate != shouldCallAfterDonate(self)\r\n                ) {\r\n                    revert HookAddressNotValid(address(self));\r\n                }\r\n            }\r\n            ```\r\n        - 总结\r\n            函数 `validateHookAddress` 用于检查 hook 地址是否匹配 calls。\r\n\r\n#### 设置 protocolFeeController 地址\r\n- 内部函数（仅合约内部可以调用）\r\n    - isValidHookAddress\r\n        - 代码解析\r\n            ``` javascript\r\n            function isValidHookAddress(\r\n                IHooks hook, // hook 地址\r\n                uint24 fee // 手续费\r\n            ) internal pure returns (\r\n                bool // 返回是否是正确的 hook 地址\r\n            ) {\r\n                // \r\n                return address(hook) == address(0)\r\n                    ? !fee.isDynamicFee() && !fee.hasHookSwapFee() && !fee.hasHookWithdrawFee()\r\n                    : (\r\n                        uint160(address(hook)) >= AFTER_DONATE_FLAG || fee.isDynamicFee() || fee.hasHookSwapFee()\r\n                            || fee.hasHookWithdrawFee()\r\n                    );\r\n            }\r\n            ```\r\n        - 总结\r\n            函数 `isValidHookAddress` 用于返回是否是有效的 hook 地址。\r\n\r\n#### 检查是否设置 hooks\r\n- 内部函数（仅合约内部可以调用）\r\n    - shouldCallBeforeInitialize\r\n        - 代码解析\r\n            ``` javascript\r\n            function shouldCallBeforeInitialize(\r\n                IHooks self // hook 地址\r\n            ) internal pure returns (\r\n                bool // 返回是否有 beforeInitialize 的 hook\r\n            ) {\r\n                // 是否有 beforeInitialize 的 hook\r\n                return uint256(uint160(address(self))) & BEFORE_INITIALIZE_FLAG != 0;\r\n            }\r\n            ```\r\n        - 总结\r\n            函数 `shouldCallBeforeInitialize` 用于检查 hook 地址是否有 beforeInitialize 的 hook。\r\n    \r\n    - shouldCallAfterInitialize\r\n        - 代码解析\r\n            ``` javascript\r\n            function shouldCallAfterInitialize(\r\n                IHooks self // hook 地址\r\n            ) internal pure returns (\r\n                bool // 返回是否有 afterInitialize 的 hook\r\n            ) {\r\n                // 是否有 afterInitialize 的 hook\r\n                return uint256(uint160(address(self))) & AFTER_INITIALIZE_FLAG != 0;\r\n            }\r\n            ```\r\n        - 总结\r\n            函数 `shouldCallAfterInitialize` 用于检查 hook 地址是否有 afterInitialize 的 hook。\r\n    \r\n    - shouldCallBeforeModifyPosition\r\n        - 代码解析\r\n            ``` javascript\r\n            function shouldCallBeforeModifyPosition(\r\n                IHooks self // hook 地址\r\n            ) internal pure returns (\r\n                bool // 返回是否有 beforeModifyPosition 的 hook\r\n            ) {\r\n                // 是否有 beforeModifyPosition 的 hook\r\n                return uint256(uint160(address(self))) & BEFORE_MODIFY_POSITION_FLAG != 0;\r\n            }\r\n            ```\r\n        - 总结\r\n            函数 `shouldCallBeforeModifyPosition` 用于检查 hook 地址是否有 beforeModifyPosition 的 hook。\r\n\r\n    - shouldCallAfterModifyPosition\r\n        - 代码解析\r\n            ``` javascript\r\n            function shouldCallAfterModifyPosition(\r\n                IHooks self // hook 地址\r\n            ) internal pure returns (\r\n                bool // 返回是否有 afterModifyPosition 的 hook\r\n            ) {\r\n                // 是否有 afterModifyPosition 的 hook\r\n                return uint256(uint160(address(self))) & AFTER_MODIFY_POSITION_FLAG != 0;\r\n            }\r\n            ```\r\n        - 总结\r\n            函数 `shouldCallAfterModifyPosition` 用于检查 hook 地址是否有 afterModifyPosition 的 hook。\r\n\r\n    - shouldCallBeforeSwap\r\n        - 代码解析\r\n            ``` javascript\r\n            function shouldCallBeforeSwap(\r\n                IHooks self // hook 地址\r\n            ) internal pure returns (\r\n                bool // 返回是否有 beforeSwap 的 hook\r\n            ) {\r\n                // 是否有 beforeSwap 的 hook\r\n                return uint256(uint160(address(self))) & BEFORE_SWAP_FLAG != 0;\r\n            }\r\n            ```\r\n        - 总结\r\n            函数 `shouldCallBeforeSwap` 用于检查 hook 地址是否有 beforeSwap 的 hook。\r\n\r\n    - shouldCallAfterSwap\r\n        - 代码解析\r\n            ``` javascript\r\n            function shouldCallAfterSwap(\r\n                IHooks self // hook 地址\r\n            ) internal pure returns (\r\n                bool // 返回是否有 afterSwap 的 hook\r\n            ) {\r\n                // 是否有 afterSwap 的 hook\r\n                return uint256(uint160(address(self))) & AFTER_SWAP_FLAG != 0;\r\n            }\r\n            ```\r\n        - 总结\r\n            函数 `shouldCallAfterSwap` 用于检查 hook 地址是否有 afterSwap 的 hook。\r\n\r\n    - shouldCallBeforeDonate\r\n        - 代码解析\r\n            ``` javascript\r\n            function shouldCallBeforeDonate(\r\n                IHooks self // hook 地址\r\n            ) internal pure returns (\r\n                bool // 返回是否有 beforeDonate 的 hook\r\n            ) {\r\n                // 是否有 beforeDonate 的 hook\r\n                return uint256(uint160(address(self))) & BEFORE_DONATE_FLAG != 0;\r\n            }\r\n            ```\r\n        - 总结\r\n            函数 `shouldCallBeforeDonate` 用于检查 hook 地址是否有 beforeDonate 的 hook。\r\n\r\n    - shouldCallAfterDonate\r\n        - 代码解析\r\n            ``` javascript\r\n            function shouldCallAfterDonate(\r\n                IHooks self // hook 地址\r\n            ) internal pure returns (\r\n                bool // 返回是否有 afterDonate 的 hook\r\n            ) {\r\n                // 是否有 afterDonate 的 hook\r\n                return uint256(uint160(address(self))) & AFTER_DONATE_FLAG != 0;\r\n            }\r\n            ```\r\n        - 总结\r\n            函数 `shouldCallAfterDonate` 用于检查 hook 地址是否有 afterDonate 的 hook。\r\n            \r\n           ## 原文发布在 <https://github.com/33357/smartcontract-apps> 这是一个面向中文社区，分析市面上智能合约应用的架构与实现的仓库。欢迎关注开源知识项目！"},"author":{"user":"https://learnblockchain.cn/people/3877","address":"0x1f2479ee1b4aFE789e19D257D2D50810ac90fa59"},"history":"QmSMh5aqxZUMnTvffcuTLVewHhoSPLYK3DUMaMywrkWMAj","timestamp":1695960845,"version":1}