DopplerHookInitializer
0x4e3468951d49f2eea976ed0d6e75ffcb44a9a544
Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
25,533 bytes
ABI entries
72
13 read · 20 write
License
none
Contract information
- Address
- 0x4e3468951d49f2eea976ed0d6e75ffcb44a9a544
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.26+commit.8a97fa7a
- Optimization
- Enabled
- Creator
- 0xdD429645eB…639e0a342b
- Creation tx
- 0xd32e8ebbb5…fcfbe700b1
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (13)
airlock() → address
getAuthority(address) → address
getBeneficiaries(address) → tuple[]
getCumulatedFees0(bytes32) → uint256
getCumulatedFees1(bytes32) → uint256
getHookPermissions() → tuple
getLastCumulatedFees0(bytes32, address) → uint256
getLastCumulatedFees1(bytes32, address) → uint256
getPoolKey(bytes32) → address, address, uint24, int24, address
getShares(bytes32, address) → uint256
getState(address) → address, uint256, address, bytes, uint8, tuple, int24
isDopplerHookEnabled(address) → uint256
poolManager() → address
Events (11)
CollectCreateDelegateAuthorityGraduateLockModifyLiquidityReleaseSetDopplerHookSetDopplerHookStateSwapUpdateBeneficiary
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "airlock_",
"type": "address"
},
{
"internalType": "contract IPoolManager",
"name": "poolManager_",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "ArrayLengthsMismatch",
"type": "error"
},
{
"inputs": [
{
"internalType": "int24",
"name": "targetTick",
"type": "int24"
},
{
"internalType": "int24",
"name": "currentTick",
"type": "int24"
}
],
"name": "CannotMigrateInsufficientTick",
"type": "error"
},
{
"inputs": [],
"name": "CannotMigratePoolNoProvidedDopplerHook",
"type": "error"
},
{
"inputs": [],
"name": "DopplerHookNotEnabled",
"type": "error"
},
{
"inputs": [],
"name": "HookNotImplemented",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint8",
"name": "action",
"type": "uint8"
}
],
"name": "InvalidCallbackAction",
"type": "error"
},
{
"inputs": [],
"name": "InvalidNewBeneficiary",
"type": "error"
},
{
"inputs": [],
"name": "InvalidProtocolOwnerBeneficiary",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint96",
"name": "required",
"type": "uint96"
},
{
"internalType": "uint96",
"name": "provided",
"type": "uint96"
}
],
"name": "InvalidProtocolOwnerShares",
"type": "error"
},
{
"inputs": [],
"name": "InvalidShares",
"type": "error"
},
{
"inputs": [],
"name": "InvalidTotalShares",
"type": "error"
},
{
"inputs": [],
"name": "InvalidTotalShares",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint24",
"name": "maxFee",
"type": "uint24"
},
{
"internalType": "uint256",
"name": "fee",
"type": "uint256"
}
],
"name": "LPFeeTooHigh",
"type": "error"
},
{
"inputs": [],
"name": "NotPoolManager",
"type": "error"
},
{
"inputs": [],
"name": "OnlyInitializer",
"type": "error"
},
{
"inputs": [],
"name": "Reentrancy",
"type": "error"
},
{
"inputs": [],
"name": "SenderNotAirlock",
"type": "error"
},
{
"inputs": [],
"name": "SenderNotAirlockOwner",
"type": "error"
},
{
"inputs": [],
"name": "SenderNotAuthorized",
"type": "error"
},
{
"inputs": [
{
"internalType": "int24",
"name": "tick",
"type": "int24"
}
],
"name": "TickNotAligned",
"type": "error"
},
{
"inputs": [
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
}
],
"name": "TickRangeMisordered",
"type": "error"
},
{
"inputs": [],
"name": "UnorderedBeneficiaries",
"type": "error"
},
{
"inputs": [],
"name": "UnreachableFarTick",
"type": "error"
},
{
"inputs": [
{
"internalType": "enum PoolStatus",
"name": "expected",
"type": "uint8"
},
{
"internalType": "enum PoolStatus",
"name": "actual",
"type": "uint8"
}
],
"name": "WrongPoolStatus",
"type": "error"
},
{
"inputs": [],
"name": "ZeroPosition",
"type": "error"
},
{
"inputs": [],
"name": "ZeroShare",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "PoolId",
"name": "poolId",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "uint256",
"name": "fees0",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "fees1",
"type": "uint256"
}
],
"name": "Collect",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "poolOrHook",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "asset",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "numeraire",
"type": "address"
}
],
"name": "Create",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "user",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "authority",
"type": "address"
}
],
"name": "DelegateAuthority",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "asset",
"type": "address"
}
],
"name": "Graduate",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "pool",
"type": "address"
},
{
"components": [
{
"internalType": "address",
"name": "beneficiary",
"type": "address"
},
{
"internalType": "uint96",
"name": "shares",
"type": "uint96"
}
],
"indexed": false,
"internalType": "struct BeneficiaryData[]",
"name": "beneficiaries",
"type": "tuple[]"
}
],
"name": "Lock",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"indexed": false,
"internalType": "struct PoolKey",
"name": "key",
"type": "tuple"
},
{
"components": [
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"internalType": "int256",
"name": "liquidityDelta",
"type": "int256"
},
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
}
],
"indexed": false,
"internalType": "struct IPoolManager.ModifyLiquidityParams",
"name": "params",
"type": "tuple"
}
],
"name": "ModifyLiquidity",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "PoolId",
"name": "poolId",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "address",
"name": "beneficiary",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "fees0",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "fees1",
"type": "uint256"
}
],
"name": "Release",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "asset",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "dopplerHook",
"type": "address"
}
],
"name": "SetDopplerHook",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "dopplerHook",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "flag",
"type": "uint256"
}
],
"name": "SetDopplerHookState",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"indexed": true,
"internalType": "struct PoolKey",
"name": "poolKey",
"type": "tuple"
},
{
"indexed": true,
"internalType": "PoolId",
"name": "poolId",
"type": "bytes32"
},
{
"components": [
{
"internalType": "bool",
"name": "zeroForOne",
"type": "bool"
},
{
"internalType": "int256",
"name": "amountSpecified",
"type": "int256"
},
{
"internalType": "uint160",
"name": "sqrtPriceLimitX96",
"type": "uint160"
}
],
"indexed": false,
"internalType": "struct IPoolManager.SwapParams",
"name": "params",
"type": "tuple"
},
{
"indexed": false,
"internalType": "int128",
"name": "amount0",
"type": "int128"
},
{
"indexed": false,
"internalType": "int128",
"name": "amount1",
"type": "int128"
},
{
"indexed": false,
"internalType": "bytes",
"name": "hookData",
"type": "bytes"
}
],
"name": "Swap",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "PoolId",
"name": "poolId",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "address",
"name": "oldBeneficiary",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "newBeneficiary",
"type": "address"
}
],
"name": "UpdateBeneficiary",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "key",
"type": "tuple"
},
{
"components": [
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"internalType": "int256",
"name": "liquidityDelta",
"type": "int256"
},
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
}
],
"internalType": "struct IPoolManager.ModifyLiquidityParams",
"name": "params",
"type": "tuple"
},
{
"internalType": "BalanceDelta",
"name": "delta",
"type": "int256"
},
{
"internalType": "BalanceDelta",
"name": "feesAccrued",
"type": "int256"
},
{
"internalType": "bytes",
"name": "hookData",
"type": "bytes"
}
],
"name": "afterAddLiquidity",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
},
{
"internalType": "BalanceDelta",
"name": "",
"type": "int256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "key",
"type": "tuple"
},
{
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "hookData",
"type": "bytes"
}
],
"name": "afterDonate",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "key",
"type": "tuple"
},
{
"internalType": "uint160",
"name": "sqrtPriceX96",
"type": "uint160"
},
{
"internalType": "int24",
"name": "tick",
"type": "int24"
}
],
"name": "afterInitialize",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "key",
"type": "tuple"
},
{
"components": [
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"internalType": "int256",
"name": "liquidityDelta",
"type": "int256"
},
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
}
],
"internalType": "struct IPoolManager.ModifyLiquidityParams",
"name": "params",
"type": "tuple"
},
{
"internalType": "BalanceDelta",
"name": "delta",
"type": "int256"
},
{
"internalType": "BalanceDelta",
"name": "feesAccrued",
"type": "int256"
},
{
"internalType": "bytes",
"name": "hookData",
"type": "bytes"
}
],
"name": "afterRemoveLiquidity",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
},
{
"internalType": "BalanceDelta",
"name": "",
"type": "int256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "key",
"type": "tuple"
},
{
"components": [
{
"internalType": "bool",
"name": "zeroForOne",
"type": "bool"
},
{
"internalType": "int256",
"name": "amountSpecified",
"type": "int256"
},
{
"internalType": "uint160",
"name": "sqrtPriceLimitX96",
"type": "uint160"
}
],
"internalType": "struct IPoolManager.SwapParams",
"name": "params",
"type": "tuple"
},
{
"internalType": "BalanceDelta",
"name": "delta",
"type": "int256"
},
{
"internalType": "bytes",
"name": "hookData",
"type": "bytes"
}
],
"name": "afterSwap",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
},
{
"internalType": "int128",
"name": "",
"type": "int128"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "airlock",
"outputs": [
{
"internalType": "contract Airlock",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "key",
"type": "tuple"
},
{
"components": [
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"internalType": "int256",
"name": "liquidityDelta",
"type": "int256"
},
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
}
],
"internalType": "struct IPoolManager.ModifyLiquidityParams",
"name": "params",
"type": "tuple"
},
{
"internalType": "bytes",
"name": "hookData",
"type": "bytes"
}
],
"name": "beforeAddLiquidity",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "key",
"type": "tuple"
},
{
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "hookData",
"type": "bytes"
}
],
"name": "beforeDonate",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "key",
"type": "tuple"
},
{
"internalType": "uint160",
"name": "sqrtPriceX96",
"type": "uint160"
}
],
"name": "beforeInitialize",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "key",
"type": "tuple"
},
{
"components": [
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"internalType": "int256",
"name": "liquidityDelta",
"type": "int256"
},
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
}
],
"internalType": "struct IPoolManager.ModifyLiquidityParams",
"name": "params",
"type": "tuple"
},
{
"internalType": "bytes",
"name": "hookData",
"type": "bytes"
}
],
"name": "beforeRemoveLiquidity",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "key",
"type": "tuple"
},
{
"components": [
{
"internalType": "bool",
"name": "zeroForOne",
"type": "bool"
},
{
"internalType": "int256",
"name": "amountSpecified",
"type": "int256"
},
{
"internalType": "uint160",
"name": "sqrtPriceLimitX96",
"type": "uint160"
}
],
"internalType": "struct IPoolManager.SwapParams",
"name": "params",
"type": "tuple"
},
{
"internalType": "bytes",
"name": "hookData",
"type": "bytes"
}
],
"name": "beforeSwap",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
},
{
"internalType": "BeforeSwapDelta",
"name": "",
"type": "int256"
},
{
"internalType": "uint24",
"name": "",
"type": "uint24"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "PoolId",
"name": "poolId",
"type": "bytes32"
}
],
"name": "collectFees",
"outputs": [
{
"internalType": "uint128",
"name": "fees0",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "fees1",
"type": "uint128"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "delegatedAuthority",
"type": "address"
}
],
"name": "delegateAuthority",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "asset",
"type": "address"
}
],
"name": "exitLiquidity",
"outputs": [
{
"internalType": "uint160",
"name": "sqrtPriceX96",
"type": "uint160"
},
{
"internalType": "address",
"name": "token0",
"type": "address"
},
{
"internalType": "uint128",
"name": "fees0",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "balance0",
"type": "uint128"
},
{
"internalType": "address",
"name": "token1",
"type": "address"
},
{
"internalType": "uint128",
"name": "fees1",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "balance1",
"type": "uint128"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "user",
"type": "address"
}
],
"name": "getAuthority",
"outputs": [
{
"internalType": "address",
"name": "authority",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "asset",
"type": "address"
}
],
"name": "getBeneficiaries",
"outputs": [
{
"components": [
{
"internalType": "address",
"name": "beneficiary",
"type": "address"
},
{
"internalType": "uint96",
"name": "shares",
"type": "uint96"
}
],
"internalType": "struct BeneficiaryData[]",
"name": "",
"type": "tuple[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "PoolId",
"name": "poolId",
"type": "bytes32"
}
],
"name": "getCumulatedFees0",
"outputs": [
{
"internalType": "uint256",
"name": "cumulatedFees0",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "PoolId",
"name": "poolId",
"type": "bytes32"
}
],
"name": "getCumulatedFees1",
"outputs": [
{
"internalType": "uint256",
"name": "cumulatedFees1",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getHookPermissions",
"outputs": [
{
"components": [
{
"internalType": "bool",
"name": "beforeInitialize",
"type": "bool"
},
{
"internalType": "bool",
"name": "afterInitialize",
"type": "bool"
},
{
"internalType": "bool",
"name": "beforeAddLiquidity",
"type": "bool"
},
{
"internalType": "bool",
"name": "afterAddLiquidity",
"type": "bool"
},
{
"internalType": "bool",
"name": "beforeRemoveLiquidity",
"type": "bool"
},
{
"internalType": "bool",
"name": "afterRemoveLiquidity",
"type": "bool"
},
{
"internalType": "bool",
"name": "beforeSwap",
"type": "bool"
},
{
"internalType": "bool",
"name": "afterSwap",
"type": "bool"
},
{
"internalType": "bool",
"name": "beforeDonate",
"type": "bool"
},
{
"internalType": "bool",
"name": "afterDonate",
"type": "bool"
},
{
"internalType": "bool",
"name": "beforeSwapReturnDelta",
"type": "bool"
},
{
"internalType": "bool",
"name": "afterSwapReturnDelta",
"type": "bool"
},
{
"internalType": "bool",
"name": "afterAddLiquidityReturnDelta",
"type": "bool"
},
{
"internalType": "bool",
"name": "afterRemoveLiquidityReturnDelta",
"type": "bool"
}
],
"internalType": "struct Hooks.Permissions",
"name": "",
"type": "tuple"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
"internalType": "PoolId",
"name": "poolId",
"type": "bytes32"
},
{
"internalType": "address",
"name": "beneficiary",
"type": "address"
}
],
"name": "getLastCumulatedFees0",
"outputs": [
{
"internalType": "uint256",
"name": "lastCumulatedFees0",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "PoolId",
"name": "poolId",
"type": "bytes32"
},
{
"internalType": "address",
"name": "beneficiary",
"type": "address"
}
],
"name": "getLastCumulatedFees1",
"outputs": [
{
"internalType": "uint256",
"name": "lastCumulatedFees1",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "PoolId",
"name": "poolId",
"type": "bytes32"
}
],
"name": "getPoolKey",
"outputs": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "PoolId",
"name": "poolId",
"type": "bytes32"
},
{
"internalType": "address",
"name": "beneficiary",
"type": "address"
}
],
"name": "getShares",
"outputs": [
{
"internalType": "uint256",
"name": "shares",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "asset",
"type": "address"
}
],
"name": "getState",
"outputs": [
{
"internalType": "address",
"name": "numeraire",
"type": "address"
},
{
"internalType": "uint256",
"name": "totalTokensOnBondingCurve",
"type": "uint256"
},
{
"internalType": "address",
"name": "dopplerHook",
"type": "address"
},
{
"internalType": "bytes",
"name": "graduationDopplerHookCalldata",
"type": "bytes"
},
{
"internalType": "enum PoolStatus",
"name": "status",
"type": "uint8"
},
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "poolKey",
"type": "tuple"
},
{
"internalType": "int24",
"name": "farTick",
"type": "int24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "asset",
"type": "address"
}
],
"name": "graduate",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "asset",
"type": "address"
},
{
"internalType": "address",
"name": "numeraire",
"type": "address"
},
{
"internalType": "uint256",
"name": "totalTokensOnBondingCurve",
"type": "uint256"
},
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "initialize",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "dopplerHook",
"type": "address"
}
],
"name": "isDopplerHookEnabled",
"outputs": [
{
"internalType": "uint256",
"name": "flags",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "poolManager",
"outputs": [
{
"internalType": "contract IPoolManager",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "asset",
"type": "address"
},
{
"internalType": "address",
"name": "dopplerHook",
"type": "address"
},
{
"internalType": "bytes",
"name": "onInitializationCalldata",
"type": "bytes"
},
{
"internalType": "bytes",
"name": "onGraduationCalldata",
"type": "bytes"
}
],
"name": "setDopplerHook",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address[]",
"name": "dopplerHooks",
"type": "address[]"
},
{
"internalType": "uint256[]",
"name": "flags",
"type": "uint256[]"
}
],
"name": "setDopplerHookState",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "unlockCallback",
"outputs": [
{
"internalType": "bytes",
"name": "",
"type": "bytes"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "PoolId",
"name": "poolId",
"type": "bytes32"
},
{
"internalType": "address",
"name": "newBeneficiary",
"type": "address"
}
],
"name": "updateBeneficiary",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "asset",
"type": "address"
},
{
"internalType": "uint24",
"name": "lpFee",
"type": "uint24"
}
],
"name": "updateDynamicLPFee",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]Source code
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.26;
import { SafeTransferLib } from "@solady/utils/SafeTransferLib.sol";
import { IHooks } from "@v4-core/interfaces/IHooks.sol";
import { IPoolManager } from "@v4-core/interfaces/IPoolManager.sol";
import { Hooks } from "@v4-core/libraries/Hooks.sol";
import { LPFeeLibrary } from "@v4-core/libraries/LPFeeLibrary.sol";
import { StateLibrary } from "@v4-core/libraries/StateLibrary.sol";
import { TickMath } from "@v4-core/libraries/TickMath.sol";
import { BalanceDelta, BalanceDeltaLibrary } from "@v4-core/types/BalanceDelta.sol";
import { Currency, CurrencyLibrary } from "@v4-core/types/Currency.sol";
import { PoolId } from "@v4-core/types/PoolId.sol";
import { PoolKey } from "@v4-core/types/PoolKey.sol";
import { ImmutableState } from "@v4-periphery/base/ImmutableState.sol";
import { ON_GRADUATION_FLAG, ON_INITIALIZATION_FLAG, ON_SWAP_FLAG } from "src/base/BaseDopplerHookInitializer.sol";
import { BaseHook } from "src/base/BaseHook.sol";
import { FeesManager } from "src/base/FeesManager.sol";
import { ImmutableAirlock, SenderNotAirlock } from "src/base/ImmutableAirlock.sol";
import { MiniV4Manager } from "src/base/MiniV4Manager.sol";
import { IDopplerHookInitializer } from "src/interfaces/IDopplerHookInitializer.sol";
import { IPoolInitializer } from "src/interfaces/IPoolInitializer.sol";
import { Curve, Multicurve } from "src/libraries/Multicurve.sol";
import { BeneficiaryData, MIN_PROTOCOL_OWNER_SHARES } from "src/types/BeneficiaryData.sol";
import { Position } from "src/types/Position.sol";
/**
* @notice Emitted when a new pool is locked
* @param pool Address of the Uniswap V4 pool key
* @param beneficiaries Array of beneficiaries with their shares
*/
event Lock(address indexed pool, BeneficiaryData[] beneficiaries);
/**
* @notice Emitted when the state of a Doppler Hook is set
* @param dopplerHook Address of the Doppler Hook
* @param flag Flag of the Doppler Hook (see flags in BaseDopplerHookInitializer.sol)
*/
event SetDopplerHookState(address indexed dopplerHook, uint256 indexed flag);
/// @notice Emitted when a dopplerHook is linked to a pool
event SetDopplerHook(address indexed asset, address indexed dopplerHook);
/// @notice Emitted when a pool graduates
event Graduate(address indexed asset);
/**
* @notice Emitted when liquidity is modified
* @param key Key of the related pool
* @param params Parameters of the liquidity modification
*/
event ModifyLiquidity(PoolKey key, IPoolManager.ModifyLiquidityParams params);
/**
* @notice Emitted when a Swap occurs
* @param sender Address calling the PoolManager
* @param poolKey Key of the related pool
* @param poolId Id of the related pool
* @param params Parameters of the swap
* @param amount0 Balance denominated in token0
* @param amount1 Balance denominated in token1
* @param hookData Data passed to the hook
*/
event Swap(
address indexed sender,
PoolKey indexed poolKey,
PoolId indexed poolId,
IPoolManager.SwapParams params,
int128 amount0,
int128 amount1,
bytes hookData
);
/**
* @notice Emitted when a user delegates their pool authority to another address
* @param user Address of the user delegating their authority
* @param authority Address of the delegated authority
*/
event DelegateAuthority(address indexed user, address indexed authority);
/// @notice Thrown when the caller is not the Uniswap V4 Multicurve Initializer
error OnlyInitializer();
/**
* @notice Thrown when the pool is not in the expected status
* @param expected Expected pool status
* @param actual Actual pool status
*/
error WrongPoolStatus(PoolStatus expected, PoolStatus actual);
/// @notice Thrown when the current tick is not sufficient to migrate
error CannotMigrateInsufficientTick(int24 targetTick, int24 currentTick);
/// @notice Thrown when the hook is not provided but migration is attempted
error CannotMigratePoolNoProvidedDopplerHook();
/// @notice Thrown when an unauthorized sender calls `setDopplerHookState()`
error SenderNotAirlockOwner();
/// @notice Thrown when an unauthorized sender tries to associate a Doppler Hook to a pool
error SenderNotAuthorized();
/// @notice Thrown when the given Doppler Hook is not enabled
error DopplerHookNotEnabled();
/// @notice Thrown when the lengths of two arrays do not match
error ArrayLengthsMismatch();
/// @notice Thrown when the far tick is unreachable
error UnreachableFarTick();
/// @notice Thrown when the provided LP fee is above the maximum allowed
error LPFeeTooHigh(uint24 maxFee, uint256 fee);
/**
* @notice Data used to initialize the Uniswap V4 pool
* @param fee Fee of the Uniswap V4 pool (capped at 1_000_000), use a dynamic fee if a Doppler Hook is provided
* @param farTick Farthest tick that must be reached to allow exiting liquidity, will be flipped if necessary
* @param tickSpacing Tick spacing for the Uniswap V4 pool
* @param curves Array of curves to distribute liquidity across
* @param beneficiaries Array of beneficiaries with their shares, will lock the pool if not empty
* @param dopplerHook Address of the associated Doppler Hook
* @param onInitializationDopplerHookCalldata Calldata passed to the Doppler Hook on initialization
* @param graduationDopplerHookCalldata Calldata passed to the Doppler Hook on graduation
*/
struct InitData {
uint24 fee;
int24 tickSpacing;
int24 farTick;
Curve[] curves;
BeneficiaryData[] beneficiaries;
address dopplerHook;
bytes onInitializationDopplerHookCalldata;
bytes graduationDopplerHookCalldata;
}
/// @notice Possible status of a pool, note a locked pool cannot be exited
enum PoolStatus {
Uninitialized,
Initialized,
Locked,
Graduated,
Exited
}
/**
* @notice State of a pool
* @param numeraire Address of the numeraire currency
* @param beneficiaries Array of beneficiaries with their shares
* @param adjustedCurves Array of adjusted curves used for liquidity distribution
* @param totalTokensOnBondingCurve Total amount of tokens allocated to the bonding curve
* @param dopplerHook Address of the Doppler hook
* @param graduationDopplerHookCalldata Calldata passed to the Doppler Hook on graduation
* @param status Current status of the pool
* @param poolKey Key of the Uniswap V4 pool
* @param farTick Farthest tick that must be reached to allow exiting liquidity
*/
struct PoolState {
address numeraire;
BeneficiaryData[] beneficiaries;
Curve[] adjustedCurves;
uint256 totalTokensOnBondingCurve;
address dopplerHook;
bytes graduationDopplerHookCalldata;
PoolStatus status;
PoolKey poolKey;
int24 farTick;
}
/// @dev Maximum LP fee allowed (1_000_000 = 100%)
uint24 constant MAX_LP_FEE = 100_000;
/**
* @title Doppler Hook Uniswap V4 Multicurve Initializer
* @author Whetstone Research
* @custom:security-contact security@whetstone.cc
* @notice Initializes a Uniswap V4 pool and distributes liquidity across multiple positions, as described
* in the Doppler Multicurve whitepaper (https://www.doppler.lol/multicurve.pdf), with optional support for
* Doppler Hooks for dynamic fee adjustment and custom logic on initialization, swaps and pool graduation
*/
contract DopplerHookInitializer is ImmutableAirlock, BaseHook, MiniV4Manager, FeesManager, IPoolInitializer {
using StateLibrary for IPoolManager;
using CurrencyLibrary for Currency;
using BalanceDeltaLibrary for BalanceDelta;
/// @notice Returns the state of a pool
mapping(address asset => PoolState state) public getState;
/// @notice Maps a Uniswap V4 poolId to its associated asset
mapping(PoolId poolId => address asset) internal getAsset;
/// @notice Returns a non-zero value if a Doppler hook is enabled
mapping(address dopplerHook => uint256 flags) public isDopplerHookEnabled;
/// @notice Returns the delegated authority for a user
mapping(address user => address authority) public getAuthority;
/**
* @dev Since both the `MiniV4Manager` and the `BaseHook` contracts are inheriting from the `ImmutableState` base
* contract we modified these contracts to get rid of their constructors, allowing us to call the base contract here
* and avoid the `Base constructor arguments given twice` error due to both parents inheriting from the same child
* @param airlock_ Address of the Airlock contract
* @param poolManager_ Address of the Uniswap V4 pool manager
*/
constructor(address airlock_, IPoolManager poolManager_) ImmutableAirlock(airlock_) ImmutableState(poolManager_) { }
/// @inheritdoc IPoolInitializer
function initialize(
address asset,
address numeraire,
uint256 totalTokensOnBondingCurve,
bytes32,
bytes calldata data
) external override returns (address) {
require(msg.sender == address(airlock), SenderNotAirlock());
require(
getState[asset].status == PoolStatus.Uninitialized,
WrongPoolStatus(PoolStatus.Uninitialized, getState[asset].status)
);
InitData memory initData = abi.decode(data, (InitData));
(PoolKey memory poolKey, Position[] memory positions) =
_initialize(asset, numeraire, totalTokensOnBondingCurve, initData);
PoolId poolId = poolKey.toId();
getAsset[poolId] = asset;
(
address dopplerHook,
uint24 fee,
BeneficiaryData[] memory beneficiaries,
bytes memory onInitializationDopplerHookCalldata
) = (initData.dopplerHook, initData.fee, initData.beneficiaries, initData.onInitializationDopplerHookCalldata);
uint256 dopplerHookFlag = isDopplerHookEnabled[initData.dopplerHook];
if (dopplerHook != address(0)) {
require(dopplerHookFlag > 0, DopplerHookNotEnabled());
poolManager.updateDynamicLPFee(poolKey, fee);
}
SafeTransferLib.safeTransferFrom(asset, address(airlock), address(this), totalTokensOnBondingCurve);
_mint(poolKey, positions);
emit Create(address(poolManager), asset, numeraire);
if (beneficiaries.length != 0) {
_storeBeneficiaries(poolKey, beneficiaries, airlock.owner(), MIN_PROTOCOL_OWNER_SHARES);
emit Lock(asset, beneficiaries);
}
// If any dust asset tokens are left in this contract after providing liquidity, we send them
// back to the Airlock so they'll be transferred to the associated governance or burnt
if (Currency.wrap(asset).balanceOfSelf() > 0) {
Currency.wrap(asset).transfer(address(airlock), Currency.wrap(asset).balanceOfSelf());
}
if (dopplerHookFlag & ON_INITIALIZATION_FLAG != 0) {
IDopplerHookInitializer(dopplerHook).onInitialization(asset, poolKey, onInitializationDopplerHookCalldata);
}
// Uniswap V4 pools don't have addresses, so we are returning the asset address
// instead to retrieve the associated state later during the `exitLiquidity` call
return asset;
}
/// @dev Internal function to avoid stack too deep errors
function _initialize(
address asset,
address numeraire,
uint256 totalTokensOnBondingCurve,
InitData memory initData
) private returns (PoolKey memory poolKey, Position[] memory positions) {
poolKey = PoolKey({
currency0: asset < numeraire ? Currency.wrap(asset) : Currency.wrap(numeraire),
currency1: asset < numeraire ? Currency.wrap(numeraire) : Currency.wrap(asset),
hooks: IHooks(address(this)),
fee: initData.dopplerHook != address(0) ? LPFeeLibrary.DYNAMIC_FEE_FLAG : initData.fee,
tickSpacing: initData.tickSpacing
});
bool isToken0 = asset == Currency.unwrap(poolKey.currency0);
(Curve[] memory adjustedCurves, int24 lowerTickBoundary, int24 upperTickBoundary) =
Multicurve.adjustCurves(initData.curves, 0, initData.tickSpacing, isToken0);
int24 startTick;
if (isToken0) {
startTick = lowerTickBoundary;
require(initData.farTick >= startTick && initData.farTick < upperTickBoundary, UnreachableFarTick());
} else {
startTick = upperTickBoundary;
initData.farTick = -initData.farTick;
require(initData.farTick <= startTick && initData.farTick >= lowerTickBoundary, UnreachableFarTick());
}
uint160 sqrtPriceX96 = TickMath.getSqrtPriceAtTick(startTick);
poolManager.initialize(poolKey, sqrtPriceX96);
positions =
Multicurve.calculatePositions(adjustedCurves, initData.tickSpacing, totalTokensOnBondingCurve, 0, isToken0);
PoolState memory state = PoolState({
numeraire: numeraire,
beneficiaries: initData.beneficiaries,
adjustedCurves: adjustedCurves,
totalTokensOnBondingCurve: totalTokensOnBondingCurve,
dopplerHook: initData.dopplerHook,
graduationDopplerHookCalldata: initData.graduationDopplerHookCalldata,
status: initData.beneficiaries.length != 0 ? PoolStatus.Locked : PoolStatus.Initialized,
poolKey: poolKey,
farTick: initData.farTick
});
getState[asset] = state;
}
/// @inheritdoc IPoolInitializer
function exitLiquidity(address asset)
external
returns (
uint160 sqrtPriceX96,
address token0,
uint128 fees0,
uint128 balance0,
address token1,
uint128 fees1,
uint128 balance1
)
{
require(msg.sender == address(airlock), SenderNotAirlock());
PoolState memory state = getState[asset];
require(state.status == PoolStatus.Initialized, WrongPoolStatus(PoolStatus.Initialized, state.status));
getState[asset].status = PoolStatus.Exited;
token0 = Currency.unwrap(state.poolKey.currency0);
token1 = Currency.unwrap(state.poolKey.currency1);
_canGraduateOrMigrate(state.poolKey.toId(), asset == token0, state.farTick);
sqrtPriceX96 = TickMath.getSqrtPriceAtTick(state.farTick);
Position[] memory positions = Multicurve.calculatePositions(
state.adjustedCurves, state.poolKey.tickSpacing, state.totalTokensOnBondingCurve, 0, asset == token0
);
(BalanceDelta balanceDelta, BalanceDelta feesAccrued) = _burn(state.poolKey, positions);
balance0 = uint128(balanceDelta.amount0());
balance1 = uint128(balanceDelta.amount1());
fees0 = uint128(feesAccrued.amount0());
fees1 = uint128(feesAccrued.amount1());
state.poolKey.currency0.transfer(msg.sender, balance0);
state.poolKey.currency1.transfer(msg.sender, balance1);
}
/**
* @notice Delegates `msg.sender`'s pool authority to another address
* @param delegatedAuthority Address to delgate to
*/
function delegateAuthority(address delegatedAuthority) external {
emit DelegateAuthority(msg.sender, delegatedAuthority);
getAuthority[msg.sender] = delegatedAuthority;
}
/**
* @notice Associates a Doppler hook with the pool of a given asset
* @param asset Address to of the targeted asset
* @param dopplerHook Address of the Doppler hook being associated
* @param onInitializationCalldata Calldata passed to the Doppler Hook on initialization
* @param onGraduationCalldata Calldata passed to the Doppler Hook on graduation
*/
function setDopplerHook(
address asset,
address dopplerHook,
bytes calldata onInitializationCalldata,
bytes calldata onGraduationCalldata
) external {
PoolState memory state = getState[asset];
require(state.status == PoolStatus.Locked, WrongPoolStatus(PoolStatus.Locked, state.status));
(, address timelock,,,,,,,,) = airlock.getAssetData(asset);
address authority = getAuthority[timelock];
require(msg.sender == authority || msg.sender == timelock, SenderNotAuthorized());
uint256 dopplerHookFlag = isDopplerHookEnabled[dopplerHook];
if (dopplerHook != address(0)) {
require(dopplerHookFlag > 0, DopplerHookNotEnabled());
}
getState[asset].dopplerHook = dopplerHook;
getState[asset].graduationDopplerHookCalldata = onGraduationCalldata;
emit SetDopplerHook(asset, dopplerHook);
if (dopplerHookFlag & ON_INITIALIZATION_FLAG != 0) {
IDopplerHookInitializer(dopplerHook).onInitialization(asset, state.poolKey, onInitializationCalldata);
}
}
/**
* @notice Graduates a pool if the conditions are met, this is a one-way operation
* @param asset Address of the asset to graduate
*/
function graduate(address asset) external {
PoolState memory state = getState[asset];
require(state.status == PoolStatus.Locked, WrongPoolStatus(PoolStatus.Locked, state.status));
address dopplerHook = state.dopplerHook;
uint256 flags = isDopplerHookEnabled[dopplerHook];
if (dopplerHook == address(0) || flags & ON_GRADUATION_FLAG == 0) {
revert CannotMigratePoolNoProvidedDopplerHook();
}
_canGraduateOrMigrate(state.poolKey.toId(), asset == Currency.unwrap(state.poolKey.currency0), state.farTick);
getState[asset].status = PoolStatus.Graduated;
emit Graduate(asset);
IDopplerHookInitializer(dopplerHook).onGraduation(asset, state.poolKey, state.graduationDopplerHookCalldata);
}
/**
* @notice Updates the LP fee for a given asset's pool
* @param asset Address of the asset used for the Uniswap V4 pool
* @param lpFee New dynamic LP fee to set
*/
function updateDynamicLPFee(address asset, uint24 lpFee) external {
PoolState memory state = getState[asset];
require(state.status == PoolStatus.Locked, WrongPoolStatus(PoolStatus.Locked, state.status));
require(msg.sender == state.dopplerHook, SenderNotAuthorized());
require(lpFee <= MAX_LP_FEE, LPFeeTooHigh(MAX_LP_FEE, lpFee));
poolManager.updateDynamicLPFee(state.poolKey, lpFee);
}
/**
* @notice Sets the state of a given Doppler hooks array
* @param dopplerHooks Array of Doppler hook addresses
* @param flags Array of flags to set (see flags in BaseDopplerHookInitializer.sol)
*/
function setDopplerHookState(address[] calldata dopplerHooks, uint256[] calldata flags) external {
require(msg.sender == airlock.owner(), SenderNotAirlockOwner());
uint256 length = dopplerHooks.length;
if (length != flags.length) {
revert ArrayLengthsMismatch();
}
for (uint256 i; i != length; i++) {
isDopplerHookEnabled[dopplerHooks[i]] = flags[i];
emit SetDopplerHookState(dopplerHooks[i], flags[i]);
}
}
/**
* @notice Returns the positions currently held in the Uniswap V4 pool for the given `asset`
* @param asset Address of the asset used for the Uniswap V4 pool
* @return Array of positions currently held in the Uniswap V4 pool
*/
function getPositions(address asset) internal view returns (Position[] memory) {
PoolState memory state = getState[asset];
address token0 = Currency.unwrap(state.poolKey.currency0);
Position[] memory positions = Multicurve.calculatePositions(
state.adjustedCurves, state.poolKey.tickSpacing, state.totalTokensOnBondingCurve, 0, asset == token0
);
return positions;
}
/**
* @notice Returns the beneficiaries and their shares for the given `asset`
* @param asset Address of the asset used for the Uniswap V4 pool
* @return Array of beneficiaries with their shares
*/
function getBeneficiaries(address asset) external view returns (BeneficiaryData[] memory) {
return getState[asset].beneficiaries;
}
/// @inheritdoc FeesManager
function _collectFees(PoolId poolId) internal override returns (BalanceDelta fees) {
address asset = getAsset[poolId];
PoolState memory state = getState[asset];
require(state.status == PoolStatus.Locked, WrongPoolStatus(PoolStatus.Locked, state.status));
fees = _collect(state.poolKey, getPositions(asset));
}
/**
* @notice Returns true if a pool can be graduated or migrated
* @param poolId PoolId of the Uniswap V4 pool
* @param isToken0 True if the asset is token0 in the pool
* @param farTick The farthest tick that must be reached to allow exiting liquidity
*/
function _canGraduateOrMigrate(PoolId poolId, bool isToken0, int24 farTick) internal view {
(, int24 tick,,) = poolManager.getSlot0(poolId);
require(isToken0 ? tick >= farTick : tick <= farTick, CannotMigrateInsufficientTick(farTick, tick));
}
/// @inheritdoc BaseHook
function _beforeInitialize(address sender, PoolKey calldata, uint160) internal view override returns (bytes4) {
require(sender == address(this), OnlyInitializer());
return BaseHook.beforeInitialize.selector;
}
/// @inheritdoc BaseHook
function _afterAddLiquidity(
address,
PoolKey calldata key,
IPoolManager.ModifyLiquidityParams calldata params,
BalanceDelta,
BalanceDelta,
bytes calldata
) internal override returns (bytes4, BalanceDelta) {
emit ModifyLiquidity(key, params);
return (BaseHook.afterAddLiquidity.selector, BalanceDeltaLibrary.ZERO_DELTA);
}
/// @inheritdoc BaseHook
function _afterRemoveLiquidity(
address,
PoolKey calldata key,
IPoolManager.ModifyLiquidityParams calldata params,
BalanceDelta,
BalanceDelta,
bytes calldata
) internal override returns (bytes4, BalanceDelta) {
emit ModifyLiquidity(key, params);
return (BaseHook.afterRemoveLiquidity.selector, BalanceDeltaLibrary.ZERO_DELTA);
}
/// @inheritdoc BaseHook
function _afterSwap(
address sender,
PoolKey calldata key,
IPoolManager.SwapParams calldata params,
BalanceDelta balanceDelta,
bytes calldata data
) internal override returns (bytes4, int128) {
address asset = getAsset[key.toId()];
PoolState memory state = getState[asset];
address dopplerHook = state.dopplerHook;
int128 delta;
if (dopplerHook != address(0) && isDopplerHookEnabled[dopplerHook] & ON_SWAP_FLAG != 0) {
Currency feeCurrency;
(feeCurrency, delta) = IDopplerHookInitializer(dopplerHook).onSwap(sender, key, params, balanceDelta, data);
if (delta > 0) {
poolManager.take(feeCurrency, address(this), uint128(delta));
poolManager.sync(feeCurrency);
if (feeCurrency.isAddressZero()) {
poolManager.settleFor{ value: uint128(delta) }(dopplerHook);
} else {
feeCurrency.transfer(address(poolManager), uint128(delta));
poolManager.settleFor(dopplerHook);
}
}
}
emit Swap(sender, key, key.toId(), params, balanceDelta.amount0(), balanceDelta.amount1(), data);
return (BaseHook.afterSwap.selector, delta);
}
/// @inheritdoc BaseHook
function getHookPermissions() public pure override returns (Hooks.Permissions memory) {
return Hooks.Permissions({
beforeInitialize: true,
afterInitialize: false,
beforeAddLiquidity: false,
beforeRemoveLiquidity: false,
afterAddLiquidity: true,
afterRemoveLiquidity: true,
beforeSwap: false,
afterSwap: true,
beforeDonate: false,
afterDonate: false,
beforeSwapReturnDelta: false,
afterSwapReturnDelta: true,
afterAddLiquidityReturnDelta: false,
afterRemoveLiquidityReturnDelta: false
});
}
}
Chain explorer4227msChain node87ms