{"content":{"title":"uniswap-v4 poolManager 合约分析","body":"# uniswap-v4 poolManager 合约分析\r\n\r\n## 备注\r\n\r\n时间：2023 年 8 月 13 日\r\n\r\n作者：[33357](https://github.com/33357)\r\n\r\n## 正文\r\n\r\npoolManager 是 Uniswap-v4 core 的主合约，通过对改合约的解析可以了解其核心功能和对接方法。\r\n\r\n### 创建流动池\r\n- 公共函数（合约内外部都可以调用）\r\n    - initialize\r\n        - 代码解析\r\n            ``` javascript\r\n            function initialize(\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                uint160 sqrtPriceX96 // 价格\r\n            ) external override returns (\r\n                int24 tick // 返回 tick\r\n            ) {\r\n                // 固定手续费不能大于最大值\r\n                if (key.fee.isStaticFeeTooLarge()) revert FeeTooLarge();\r\n                // tick 单位间距不能大于最大值\r\n                if (key.tickSpacing > MAX_TICK_SPACING) revert TickSpacingTooLarge();\r\n                // tick 单位间距不能小于最小值\r\n                if (key.tickSpacing < MIN_TICK_SPACING) revert TickSpacingTooSmall();\r\n                // 是否是合法的 hook 地址\r\n                if (!key.hooks.isValidHookAddress(key.fee)) revert Hooks.HookAddressNotValid(address(key.hooks));\r\n                // 如果 hooks 地址的 shouldCallBeforeInitialize 检查通过\r\n                if (key.hooks.shouldCallBeforeInitialize()) {\r\n                    // 需要 beforeInitialize 返回 IHooks.beforeInitialize 的 selector\r\n                    if (key.hooks.beforeInitialize(msg.sender, key, sqrtPriceX96) != IHooks.beforeInitialize.selector) {\r\n                        revert Hooks.InvalidHookResponse();\r\n                    }\r\n                }\r\n                // 获取 poolId\r\n                PoolId id = key.toId();\r\n                // 获取协议手续费\r\n                (uint8 protocolSwapFee, uint8 protocolWithdrawFee) = _fetchProtocolFees(key);\r\n                // 获取 hook 手续费\r\n                (uint8 hookSwapFee, uint8 hookWithdrawFee) = _fetchHookFees(key);\r\n                // 创建资金池\r\n                tick = pools[id].initialize(sqrtPriceX96, protocolSwapFee, hookSwapFee, protocolWithdrawFee, hookWithdrawFee);\r\n                // 如果 hooks 地址的 shouldCallAfterInitialize 检查通过\r\n                if (key.hooks.shouldCallAfterInitialize()) {\r\n                     // 需要 afterInitialize 返回 IHooks.afterInitialize 的 selector\r\n                    if (key.hooks.afterInitialize(msg.sender, key, sqrtPriceX96, tick) != IHooks.afterInitialize.selector) {\r\n                        revert Hooks.InvalidHookResponse();\r\n                    }\r\n                }\r\n                // 触发 Initialize 事件\r\n                emit Initialize(id, key.currency0, key.currency1, key.fee, key.tickSpacing, key.hooks);\r\n            }\r\n            ```\r\n        - 总结\r\n            函数 `initialize` 用于创建流动池 pool，要注意对 hook 地址的检查。\r\n\r\n## 锁定用户\r\n- 公共函数（合约内外部都可以调用）\r\n    - lock\r\n        - 代码解析\r\n            ``` javascript\r\n            function lock(\r\n                bytes calldata data // 回调 data\r\n            ) external override returns (\r\n                bytes memory result // 返回回调结果\r\n            ) {\r\n                // 锁定用户\r\n                lockData.push(msg.sender);\r\n                // lockAcquired 回调\r\n                result = ILockCallback(msg.sender).lockAcquired(data);\r\n                if (lockData.length == 1) {\r\n                    // 如果非零账单数量需要等于 0\r\n                    if (lockData.nonzeroDeltaCount != 0) revert CurrencyNotSettled();\r\n                    // 删除 lockData\r\n                    delete lockData;\r\n                } else {\r\n                    // 删除最后一个 lockData\r\n                    lockData.pop();\r\n                }\r\n            }\r\n            ```\r\n        - 总结\r\n            函数 `lock` 用于锁定用户，是调用其他 `onlyByLocker` 函数的前提。\r\n\r\n## 添加/删除流动性\r\n- 公共函数（合约内外部都可以调用）\r\n    - modifyPosition\r\n        - 代码解析\r\n            ``` javascript\r\n           function modifyPosition(\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                IPoolManager.ModifyPositionParams memory params /* \r\n                struct ModifyPositionParams {\r\n                    int24 tickLower; // 最低 tick\r\n                    int24 tickUpper; // 最高 tick\r\n                    int256 liquidityDelta; // 流动性数量\r\n                } */\r\n            ) external override noDelegateCall onlyByLocker returns (\r\n                BalanceDelta delta // 分为 amount0 和 amount1\r\n            ) {\r\n                // 如果 hooks 地址的 shouldCallBeforeModifyPosition 检查通过\r\n                if (key.hooks.shouldCallBeforeModifyPosition()) {\r\n                    // 需要 beforeModifyPosition 返回 IHooks.beforeModifyPosition 的 selector\r\n                    if (key.hooks.beforeModifyPosition(msg.sender, key, params) != IHooks.beforeModifyPosition.selector) {\r\n                        revert Hooks.InvalidHookResponse();\r\n                    }\r\n                }\r\n                // 获取 poolId\r\n                PoolId id = key.toId();\r\n                Pool.FeeAmounts memory feeAmounts;\r\n                // 改变流动池\r\n                (delta, feeAmounts) = pools[id].modifyPosition(\r\n                    Pool.ModifyPositionParams({\r\n                        owner: msg.sender,\r\n                        tickLower: params.tickLower,\r\n                        tickUpper: params.tickUpper,\r\n                        liquidityDelta: params.liquidityDelta.toInt128(),\r\n                        tickSpacing: key.tickSpacing\r\n                    })\r\n                );\r\n                // 改变帐单\r\n                _accountPoolBalanceDelta(key, delta);\r\n                unchecked {\r\n                    // 如果 currency0 的协议手续费大于 0，就增加 currency0 的应计协议费用\r\n                    if (feeAmounts.feeForProtocol0 > 0) {\r\n                        protocolFeesAccrued[key.currency0] += feeAmounts.feeForProtocol0;\r\n                    }\r\n                    // 如果 currency1 的协议手续费大于 0，就增加 currency1 的应计协议费用\r\n                    if (feeAmounts.feeForProtocol1 > 0) {\r\n                        protocolFeesAccrued[key.currency1] += feeAmounts.feeForProtocol1;\r\n                    }\r\n                    // 如果 currency0 的 hook 手续费大于 0，就增加 currency0 的应计 hook 费用\r\n                    if (feeAmounts.feeForHook0 > 0) {\r\n                        hookFeesAccrued[address(key.hooks)][key.currency0] += feeAmounts.feeForHook0;\r\n                    }\r\n                    // 如果 currency1 的 hook 手续费大于 0，就增加 currency1 的应计 hook 费用\r\n                    if (feeAmounts.feeForHook1 > 0) {\r\n                        hookFeesAccrued[address(key.hooks)][key.currency1] += feeAmounts.feeForHook1;\r\n                    }\r\n                }\r\n                // 如果 hooks 地址的 shouldCallAfterModifyPosition 检查通过\r\n                if (key.hooks.shouldCallAfterModifyPosition()) {\r\n                    // 需要 afterModifyPosition 返回 IHooks.afterModifyPosition 的 selector\r\n                    if (key.hooks.afterModifyPosition(msg.sender, key, params, delta) != IHooks.afterModifyPosition.selector) {\r\n                        revert Hooks.InvalidHookResponse();\r\n                    }\r\n                }\r\n                // 触发 ModifyPosition 事件\r\n                emit ModifyPosition(id, msg.sender, params.tickLower, params.tickUpper, params.liquidityDelta);\r\n            }\r\n            ```\r\n        - 总结\r\n            函数 `modifyPosition` 用于添加/删除流动性。\r\n\r\n- 内部函数（仅合约内部可以调用）\r\n    - _accountPoolBalanceDelta\r\n        - 代码解析\r\n            ``` javascript\r\n            function _accountPoolBalanceDelta(\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                BalanceDelta delta // 分为 amount0 和 amount1\r\n            ) internal {\r\n                // 增加 amount0 数量的 currency0 帐单\r\n                _accountDelta(key.currency0, delta.amount0());\r\n                // 增加 amount1 数量的 currency1 帐单\r\n                _accountDelta(key.currency1, delta.amount1());\r\n            }\r\n            ```\r\n        - 总结\r\n            函数 `_accountPoolBalanceDelta` 用于同时改变 `currency0` 和 `currency1` 的账单。\r\n\r\n    - _accountDelta\r\n        - 代码解析\r\n            ``` javascript\r\n            function _accountDelta(\r\n                Currency currency, // currency 地址\r\n                int128 delta // 操作数量\r\n            ) internal {\r\n                // delta 不能为 0\r\n                if (delta == 0) return;\r\n                // 获取 lock 用户地址\r\n                address locker = lockData.getActiveLock();\r\n                // 获取 lock 用户的 currency 帐单数量\r\n                int256 current = currencyDelta[locker][currency];\r\n                // 完账数量\r\n                int256 next = current + delta;\r\n                unchecked {\r\n                    if (next == 0) {\r\n                        // 如果完账数量为 0，非零账单数量减一\r\n                        lockData.nonzeroDeltaCount--;\r\n                    } else if (current == 0) {\r\n                        // 如果完账数量不为 0，并且帐单数量为 0，非零账单数量加一\r\n                        lockData.nonzeroDeltaCount++;\r\n                    }\r\n                }\r\n                // 完账数量记上账单\r\n                currencyDelta[locker][currency] = next;\r\n            }\r\n            ```\r\n        - 总结\r\n            函数 `_accountDelta` 用于改变用户账单。\r\n\r\n## swap 交易\r\n- 公共函数（合约内外部都可以调用）\r\n    - swap\r\n        - 代码解析\r\n            ``` javascript\r\n            function swap(\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                IPoolManager.SwapParams memory params /* \r\n                struct SwapParams {\r\n                    bool zeroForOne; // swap 方向\r\n                    int256 amountSpecified; // 指定数量\r\n                    uint160 sqrtPriceLimitX96; // 价格限制\r\n                } */\r\n            ) external override noDelegateCall onlyByLocker returns (\r\n                BalanceDelta delta // \r\n            ) {\r\n                // 如果 hooks 地址的 shouldCallBeforeSwap 检查通过\r\n                if (key.hooks.shouldCallBeforeSwap()) {\r\n                    // 需要 beforeSwap 返回 IHooks.beforeSwap 的 selector\r\n                    if (key.hooks.beforeSwap(msg.sender, key, params) != IHooks.beforeSwap.selector) {\r\n                        revert Hooks.InvalidHookResponse();\r\n                    }\r\n                }\r\n                uint24 totalSwapFee;\r\n                if (key.fee.isDynamicFee()) {\r\n                    // 获取动态费用\r\n                    totalSwapFee = IDynamicFeeManager(address(key.hooks)).getFee(key);\r\n                    // 总费用不能大于 1000000\r\n                    if (totalSwapFee >= 1000000) revert FeeTooLarge();\r\n                } else {\r\n                    // 获取固定费用\r\n                    totalSwapFee = key.fee.getStaticFee();\r\n                }\r\n                uint256 feeForProtocol;\r\n                uint256 feeForHook;\r\n                Pool.SwapState memory state;\r\n                // 获取 poolId\r\n                PoolId id = key.toId();\r\n                // swap 交易\r\n                (delta, feeForProtocol, feeForHook, state) = pools[id].swap(\r\n                    Pool.SwapParams({\r\n                        fee: totalSwapFee,\r\n                        tickSpacing: key.tickSpacing,\r\n                        zeroForOne: params.zeroForOne,\r\n                        amountSpecified: params.amountSpecified,\r\n                        sqrtPriceLimitX96: params.sqrtPriceLimitX96\r\n                    })\r\n                );\r\n                // 改变帐单\r\n                _accountPoolBalanceDelta(key, delta);\r\n                unchecked {\r\n                    // 如果协议手续费大于 0，就增加应计协议费用\r\n                    if (feeForProtocol > 0) {\r\n                        protocolFeesAccrued[params.zeroForOne ? key.currency0 : key.currency1] += feeForProtocol;\r\n                    }\r\n                    // 如果 hook 手续费大于 0，就增加应计 hook 费用\r\n                    if (feeForHook > 0) {\r\n                        hookFeesAccrued[address(key.hooks)][params.zeroForOne ? key.currency0 : key.currency1] += feeForHook;\r\n                    }\r\n                }\r\n                // 如果 hooks 地址的 shouldCallAfterSwap 检查通过\r\n                if (key.hooks.shouldCallAfterSwap()) {\r\n                    // 需要 afterSwap 返回 IHooks.afterSwap 的 selector\r\n                    if (key.hooks.afterSwap(msg.sender, key, params, delta) != IHooks.afterSwap.selector) {\r\n                        revert Hooks.InvalidHookResponse();\r\n                    }\r\n                }\r\n                // 触发 Swap 事件\r\n                emit Swap(id,msg.sender,delta.amount0(),delta.amount1(),state.sqrtPriceX96,state.liquidity,state.tick,totalSwapFee\r\n                );\r\n            }\r\n            ```\r\n        - 总结\r\n            函数 `swap` 用于执行交易。\r\n\r\n## 捐赠\r\n- 公共函数（合约内外部都可以调用）\r\n    - donate\r\n        - 代码解析\r\n            ``` javascript\r\n            function donate(\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                uint256 amount0, // 地址数值较小 currency 的数量\r\n                uint256 amount1 // 地址数值较大 currency 的数量\r\n            ) external override noDelegateCall onlyByLocker returns (\r\n                BalanceDelta delta // 分为 amount0 和 amount1\r\n            ) {\r\n                // 如果 hooks 地址的 shouldCallBeforeDonate 检查通过\r\n                if (key.hooks.shouldCallBeforeDonate()) {\r\n                    // 需要 beforeDonate 返回 IHooks.beforeDonate 的 selector\r\n                    if (key.hooks.beforeDonate(msg.sender, key, amount0, amount1) != IHooks.beforeDonate.selector) {\r\n                        revert Hooks.InvalidHookResponse();\r\n                    }\r\n                }\r\n                // 向资金池捐赠 amount0 和 amount1\r\n                delta = _getPool(key).donate(amount0, amount1);\r\n                // 改变帐单\r\n                _accountPoolBalanceDelta(key, delta);\r\n                // 如果 hooks 地址的 shouldCallAfterDonate 检查通过\r\n                if (key.hooks.shouldCallAfterDonate()) {\r\n                    // 需要 afterDonate 返回 IHooks.afterDonate 的 selector\r\n                    if (key.hooks.afterDonate(msg.sender, key, amount0, amount1) != IHooks.afterDonate.selector) {\r\n                        revert Hooks.InvalidHookResponse();\r\n                    }\r\n                }\r\n            }\r\n            ```\r\n        - 总结\r\n            函数 `donate` 用于向流动池捐赠 `currency0` 和 `currency1`。\r\n\r\n## 获取 currency\r\n- 公共函数（合约内外部都可以调用）\r\n    - take\r\n        - 代码解析\r\n            ``` javascript\r\n            function take(\r\n                Currency currency, // currency 地址\r\n                address to, // to 地址\r\n                uint256 amount // 数量\r\n            ) external override noDelegateCall onlyByLocker {\r\n                // 增加 amount 数量的 currency 帐单\r\n                _accountDelta(currency, amount.toInt128());\r\n                // currency 储备数量减少 amount\r\n                reservesOf[currency] -= amount;\r\n                // 向 to 地址发送 amount 数量的 currency\r\n                currency.transfer(to, amount);\r\n            }\r\n            ```\r\n        - 总结\r\n            函数 `take` 可以直接向 v4 借 `currency`。\r\n\r\n## 铸造 ERC1155\r\n- 公共函数（合约内外部都可以调用）\r\n    - mint\r\n        - 代码解析\r\n            ``` javascript\r\n            function mint(\r\n                Currency currency, // currency 地址\r\n                address to, // to 地址\r\n                uint256 amount // 数量\r\n            ) external override noDelegateCall onlyByLocker {\r\n                // 增加 amount 数量的 currency 帐单\r\n                _accountDelta(currency, amount.toInt128());\r\n                // mint ERC1155\r\n                _mint(to, currency.toId(), amount, \"\");\r\n            }\r\n            ```\r\n        - 总结\r\n            函数 `mint` 可以直接从 v4 铸造 ERC1155。\r\n\r\n## 结清 currency\r\n- 公共函数（合约内外部都可以调用）\r\n    - settle\r\n        - 代码解析\r\n            ``` javascript\r\n            function settle(\r\n                Currency currency // currency 地址\r\n            ) external payable override noDelegateCall onlyByLocker returns (\r\n                uint256 paid // 回收数量\r\n            ) {\r\n                // 获取 currency 储备数量\r\n                uint256 reservesBefore = reservesOf[currency];\r\n                // 更新 currency 储备数量\r\n                reservesOf[currency] = currency.balanceOfSelf();\r\n                // 获取增加的 currency 储备数量\r\n                paid = reservesOf[currency] - reservesBefore;\r\n                // 减少 paid 数量的 currency 帐单\r\n                _accountDelta(currency, -(paid.toInt128()));\r\n            }\r\n            ```\r\n        - 总结\r\n            函数 `settle` 用于向 v4 结清 currency。\r\n\r\n## 结清 ERC1155\r\n- 公共函数（合约内外部都可以调用）\r\n    - onERC1155Received\r\n        - 代码解析\r\n            ``` javascript\r\n            function onERC1155Received(\r\n                address, address,\r\n                uint256 id, // tokenId\r\n                uint256 value, // 数量\r\n                bytes calldata\r\n            ) external returns (\r\n                bytes4 // IERC1155Receiver.onERC1155Received 的 selector\r\n            ) {\r\n                // 只允许合约回调\r\n                if (msg.sender != address(this)) revert NotPoolManagercurrency();\r\n                // 销毁并修改账单\r\n                _burnAndAccount(CurrencyLibrary.fromId(id), value);\r\n                // 返回 IERC1155Receiver.onERC1155Received 的 selector\r\n                return IERC1155Receiver.onERC1155Received.selector;\r\n            }\r\n            ```\r\n        - 总结\r\n            函数 `onERC1155Received` 用于向 v4 结清 ERC1155。\r\n\r\n    - onERC1155BatchReceived\r\n        - 代码解析\r\n            ``` javascript\r\n            function onERC1155BatchReceived(\r\n                address,\r\n                address,\r\n                uint256[] calldata ids, // tokenId 列表\r\n                uint256[] calldata values, // 数量列表\r\n                bytes calldata\r\n            ) external returns (\r\n                bytes4 // IERC1155Receiver.onERC1155Received 的 selector\r\n            ) {\r\n                // 只允许合约回调\r\n                if (msg.sender != address(this)) revert NotPoolManagercurrency();\r\n                unchecked {\r\n                    // 批量销毁并修改账单\r\n                    for (uint256 i; i < ids.length; i++) {\r\n                        _burnAndAccount(CurrencyLibrary.fromId(ids[i]), values[i]);\r\n                    }\r\n                }\r\n                // 返回 IERC1155Receiver.onERC1155Received 的 selector\r\n                return IERC1155Receiver.onERC1155BatchReceived.selector;\r\n            }\r\n            ```\r\n        - 总结\r\n            函数 `onERC1155BatchReceived` 用于向 v4 批量结清 ERC1155。\r\n\r\n- 内部函数（仅合约内部可以调用）\r\n    - _burnAndAccount\r\n        - 代码解析\r\n            ``` javascript\r\n            function _burnAndAccount(\r\n                Currency currency, // currency 地址\r\n                uint256 amount // currency 数量\r\n            ) internal {\r\n                // 销毁 amount 数量的 currency\r\n                _burn(address(this), currency.toId(), amount);\r\n                // 减少 amount 数量的 currency 帐单\r\n                _accountDelta(currency, -(amount.toInt128()));\r\n            }\r\n            ```\r\n        - 总结\r\n            函数 `_burnAndAccount` 用于燃烧 ERC1155 并改变账单。\r\n\r\n### 设置协议手续费\r\n- 公共函数（合约内外部都可以调用）\r\n    - setProtocolFees\r\n        - 代码解析\r\n            ``` javascript\r\n            function setProtocolFees(\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            ) external {\r\n                // 获取协议手续费\r\n                (uint8 newProtocolSwapFee, uint8 newProtocolWithdrawFee) = _fetchProtocolFees(key);\r\n                // 获取 poolId\r\n                PoolId id = key.toId();\r\n                // 设置协议手续费\r\n                pools[id].setProtocolFees(newProtocolSwapFee, newProtocolWithdrawFee);\r\n                // 触发协议手续费更新事件\r\n                emit ProtocolFeeUpdated(id, newProtocolSwapFee, newProtocolWithdrawFee);\r\n            }\r\n            ```\r\n        - 总结\r\n            函数 `setProtocolFees` 用于设置协议手续费。\r\n\r\n### 设置 hook 手续费\r\n- 公共函数（合约内外部都可以调用）\r\n    - setHookFees\r\n        - 代码解析\r\n            ``` javascript\r\n            function setHookFees(\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            ) external {\r\n                // 获取 hook 手续费\r\n                (uint8 newHookSwapFee, uint8 newHookWithdrawFee) = _fetchHookFees(key);\r\n                // 获取 poolId\r\n                PoolId id = key.toId();\r\n                // 设置 hook 手续费\r\n                pools[id].setHookFees(newHookSwapFee, newHookWithdrawFee);\r\n                // 触发 hook 手续费更新事件\r\n                emit HookFeeUpdated(id, newHookSwapFee, newHookWithdrawFee);\r\n            }\r\n            ```\r\n        - 总结\r\n            函数 `setHookFees` 用于设置 hook 手续费。\r\n           \r\n           \r\n ## 原文发布在 <https://github.com/33357/smartcontract-apps> 这是一个面向中文社区，分析市面上智能合约应用的架构与实现的仓库。欢迎关注开源知识项目！"},"author":{"user":"https://learnblockchain.cn/people/3877","address":"0x1f2479ee1b4aFE789e19D257D2D50810ac90fa59"},"history":null,"timestamp":1695113659,"version":1}