diff --git a/.gitignore b/.gitignore
index 05a805c264..847bb491de 100644
--- a/.gitignore
+++ b/.gitignore
@@ -65,6 +65,8 @@ contracts/.localKeyValueStorage.mainnet
contracts/.localKeyValueStorage.holesky
contracts/scripts/defender-actions/dist/
+contracts/lib/defender-actions/dist/
+
todo.txt
brownie/env-brownie/
diff --git a/brownie/abi/maverick_v2_pool.json b/brownie/abi/maverick_v2_pool.json
new file mode 100644
index 0000000000..c252d5b401
--- /dev/null
+++ b/brownie/abi/maverick_v2_pool.json
@@ -0,0 +1,1168 @@
+[
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "binIdsLength",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "amountsLength",
+ "type": "uint256"
+ }
+ ],
+ "name": "PoolBinIdsAmountsLengthMismatch",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "int32",
+ "name": "startingTick",
+ "type": "int32"
+ }
+ ],
+ "name": "PoolCurrentTickBeyondSwapLimit",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "PoolFunctionNotImplemented",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "deltaLpAmount",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "accountBalance",
+ "type": "uint256"
+ }
+ ],
+ "name": "PoolInsufficientBalance",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "PoolInvalidFee",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "kinds",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "kind",
+ "type": "uint256"
+ }
+ ],
+ "name": "PoolKindNotSupported",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "PoolLocked",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "PoolMigrateBinFirst",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "PoolMinimumLiquidityNotMet",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "PoolNoProtocolFeeReceiverSet",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ }
+ ],
+ "name": "PoolReservesExceedMaximum",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "sender_",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "accessor",
+ "type": "address"
+ }
+ ],
+ "name": "PoolSenderNotAccessor",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "sender_",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "accessor",
+ "type": "address"
+ }
+ ],
+ "name": "PoolSenderNotFactory",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "tick",
+ "type": "uint256"
+ }
+ ],
+ "name": "PoolTickMaxExceeded",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "ticksLength",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "amountsLength",
+ "type": "uint256"
+ }
+ ],
+ "name": "PoolTicksAmountsLengthMismatch",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "index",
+ "type": "uint256"
+ },
+ {
+ "internalType": "int256",
+ "name": "previousTick",
+ "type": "int256"
+ },
+ {
+ "internalType": "int256",
+ "name": "tick",
+ "type": "int256"
+ }
+ ],
+ "name": "PoolTicksNotSorted",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "internalReserve",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "tokenBalance",
+ "type": "uint256"
+ },
+ {
+ "internalType": "contract IERC20",
+ "name": "token",
+ "type": "address"
+ }
+ ],
+ "name": "PoolTokenNotSolvent",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "bits",
+ "type": "uint256"
+ }
+ ],
+ "name": "PoolValueExceedsBits",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "PoolZeroLiquidityAdded",
+ "type": "error"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "sender",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "recipient",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "subaccount",
+ "type": "uint256"
+ },
+ {
+ "components": [
+ {
+ "internalType": "uint8",
+ "name": "kind",
+ "type": "uint8"
+ },
+ {
+ "internalType": "int32[]",
+ "name": "ticks",
+ "type": "int32[]"
+ },
+ {
+ "internalType": "uint128[]",
+ "name": "amounts",
+ "type": "uint128[]"
+ }
+ ],
+ "indexed": false,
+ "internalType": "struct IMaverickV2Pool.AddLiquidityParams",
+ "name": "params",
+ "type": "tuple"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "tokenAAmount",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "tokenBAmount",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint32[]",
+ "name": "binIds",
+ "type": "uint32[]"
+ }
+ ],
+ "name": "PoolAddLiquidity",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "sender",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "recipient",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "amountA",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "amountB",
+ "type": "uint256"
+ }
+ ],
+ "name": "PoolFlashLoan",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "sender",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint32",
+ "name": "binId",
+ "type": "uint32"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint32",
+ "name": "maxRecursion",
+ "type": "uint32"
+ }
+ ],
+ "name": "PoolMigrateBinsUpStack",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "contract IERC20",
+ "name": "token",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "protocolFee",
+ "type": "uint256"
+ }
+ ],
+ "name": "PoolProtocolFeeCollected",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "sender",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "recipient",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "subaccount",
+ "type": "uint256"
+ },
+ {
+ "components": [
+ {
+ "internalType": "uint32[]",
+ "name": "binIds",
+ "type": "uint32[]"
+ },
+ {
+ "internalType": "uint128[]",
+ "name": "amounts",
+ "type": "uint128[]"
+ }
+ ],
+ "indexed": false,
+ "internalType": "struct IMaverickV2Pool.RemoveLiquidityParams",
+ "name": "params",
+ "type": "tuple"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "tokenAOut",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "tokenBOut",
+ "type": "uint256"
+ }
+ ],
+ "name": "PoolRemoveLiquidity",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "sqrtPrice",
+ "type": "uint256"
+ }
+ ],
+ "name": "PoolSqrtPrice",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "sender",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "recipient",
+ "type": "address"
+ },
+ {
+ "components": [
+ {
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ },
+ {
+ "internalType": "bool",
+ "name": "tokenAIn",
+ "type": "bool"
+ },
+ {
+ "internalType": "bool",
+ "name": "exactOutput",
+ "type": "bool"
+ },
+ {
+ "internalType": "int32",
+ "name": "tickLimit",
+ "type": "int32"
+ }
+ ],
+ "indexed": false,
+ "internalType": "struct IMaverickV2Pool.SwapParams",
+ "name": "params",
+ "type": "tuple"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "amountIn",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "amountOut",
+ "type": "uint256"
+ }
+ ],
+ "name": "PoolSwap",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "int32",
+ "name": "tick",
+ "type": "int32"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint8",
+ "name": "kind",
+ "type": "uint8"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint32",
+ "name": "binId",
+ "type": "uint32"
+ }
+ ],
+ "name": "PoolTickBinUpdate",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "int32",
+ "name": "tick",
+ "type": "int32"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "reserveA",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "reserveB",
+ "type": "uint256"
+ }
+ ],
+ "name": "PoolTickState",
+ "type": "event"
+ },
+ {
+ "inputs": [],
+ "name": "accessor",
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "recipient",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "subaccount",
+ "type": "uint256"
+ },
+ {
+ "components": [
+ {
+ "internalType": "uint8",
+ "name": "kind",
+ "type": "uint8"
+ },
+ {
+ "internalType": "int32[]",
+ "name": "ticks",
+ "type": "int32[]"
+ },
+ {
+ "internalType": "uint128[]",
+ "name": "amounts",
+ "type": "uint128[]"
+ }
+ ],
+ "internalType": "struct IMaverickV2Pool.AddLiquidityParams",
+ "name": "params",
+ "type": "tuple"
+ },
+ {
+ "internalType": "bytes",
+ "name": "data",
+ "type": "bytes"
+ }
+ ],
+ "name": "addLiquidity",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "tokenAAmount",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "tokenBAmount",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint32[]",
+ "name": "binIds",
+ "type": "uint32[]"
+ }
+ ],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "user",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "subaccount",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint32",
+ "name": "binId",
+ "type": "uint32"
+ }
+ ],
+ "name": "balanceOf",
+ "outputs": [
+ {
+ "internalType": "uint128",
+ "name": "lpToken",
+ "type": "uint128"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "int32",
+ "name": "tick",
+ "type": "int32"
+ },
+ {
+ "internalType": "uint256",
+ "name": "kind",
+ "type": "uint256"
+ }
+ ],
+ "name": "binIdByTickKind",
+ "outputs": [
+ {
+ "internalType": "uint32",
+ "name": "",
+ "type": "uint32"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bool",
+ "name": "isTokenA",
+ "type": "bool"
+ }
+ ],
+ "name": "distributeFees",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "protocolFee",
+ "type": "uint256"
+ },
+ {
+ "internalType": "contract IERC20",
+ "name": "token",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "factory",
+ "outputs": [
+ {
+ "internalType": "contract IMaverickV2Factory",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bool",
+ "name": "tokenAIn",
+ "type": "bool"
+ }
+ ],
+ "name": "fee",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "recipient",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "amountA",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "amountB",
+ "type": "uint256"
+ },
+ {
+ "internalType": "bytes",
+ "name": "data",
+ "type": "bytes"
+ }
+ ],
+ "name": "flashLoan",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint32",
+ "name": "binId",
+ "type": "uint32"
+ }
+ ],
+ "name": "getBin",
+ "outputs": [
+ {
+ "components": [
+ {
+ "internalType": "uint128",
+ "name": "mergeBinBalance",
+ "type": "uint128"
+ },
+ {
+ "internalType": "uint128",
+ "name": "tickBalance",
+ "type": "uint128"
+ },
+ {
+ "internalType": "uint128",
+ "name": "totalSupply",
+ "type": "uint128"
+ },
+ {
+ "internalType": "uint8",
+ "name": "kind",
+ "type": "uint8"
+ },
+ {
+ "internalType": "int32",
+ "name": "tick",
+ "type": "int32"
+ },
+ {
+ "internalType": "uint32",
+ "name": "mergeId",
+ "type": "uint32"
+ }
+ ],
+ "internalType": "struct IMaverickV2Pool.BinState",
+ "name": "bin",
+ "type": "tuple"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "getCurrentTwa",
+ "outputs": [
+ {
+ "internalType": "int256",
+ "name": "",
+ "type": "int256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "getState",
+ "outputs": [
+ {
+ "components": [
+ {
+ "internalType": "uint128",
+ "name": "reserveA",
+ "type": "uint128"
+ },
+ {
+ "internalType": "uint128",
+ "name": "reserveB",
+ "type": "uint128"
+ },
+ {
+ "internalType": "int64",
+ "name": "lastTwaD8",
+ "type": "int64"
+ },
+ {
+ "internalType": "int64",
+ "name": "lastLogPriceD8",
+ "type": "int64"
+ },
+ {
+ "internalType": "uint40",
+ "name": "lastTimestamp",
+ "type": "uint40"
+ },
+ {
+ "internalType": "int32",
+ "name": "activeTick",
+ "type": "int32"
+ },
+ {
+ "internalType": "bool",
+ "name": "isLocked",
+ "type": "bool"
+ },
+ {
+ "internalType": "uint32",
+ "name": "binCounter",
+ "type": "uint32"
+ },
+ {
+ "internalType": "uint8",
+ "name": "protocolFeeRatioD3",
+ "type": "uint8"
+ }
+ ],
+ "internalType": "struct IMaverickV2Pool.State",
+ "name": "",
+ "type": "tuple"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "int32",
+ "name": "tick",
+ "type": "int32"
+ }
+ ],
+ "name": "getTick",
+ "outputs": [
+ {
+ "components": [
+ {
+ "internalType": "uint128",
+ "name": "reserveA",
+ "type": "uint128"
+ },
+ {
+ "internalType": "uint128",
+ "name": "reserveB",
+ "type": "uint128"
+ },
+ {
+ "internalType": "uint128",
+ "name": "totalSupply",
+ "type": "uint128"
+ },
+ {
+ "internalType": "uint32[4]",
+ "name": "binIdsByTick",
+ "type": "uint32[4]"
+ }
+ ],
+ "internalType": "struct IMaverickV2Pool.TickState",
+ "name": "tickState",
+ "type": "tuple"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "kinds",
+ "outputs": [
+ {
+ "internalType": "uint8",
+ "name": "_kinds",
+ "type": "uint8"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "lendingFeeRateD18",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "lookback",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint32",
+ "name": "binId",
+ "type": "uint32"
+ },
+ {
+ "internalType": "uint32",
+ "name": "maxRecursion",
+ "type": "uint32"
+ }
+ ],
+ "name": "migrateBinUpStack",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "protocolFeeA",
+ "outputs": [
+ {
+ "internalType": "uint128",
+ "name": "",
+ "type": "uint128"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "protocolFeeB",
+ "outputs": [
+ {
+ "internalType": "uint128",
+ "name": "",
+ "type": "uint128"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "recipient",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "subaccount",
+ "type": "uint256"
+ },
+ {
+ "components": [
+ {
+ "internalType": "uint32[]",
+ "name": "binIds",
+ "type": "uint32[]"
+ },
+ {
+ "internalType": "uint128[]",
+ "name": "amounts",
+ "type": "uint128[]"
+ }
+ ],
+ "internalType": "struct IMaverickV2Pool.RemoveLiquidityParams",
+ "name": "params",
+ "type": "tuple"
+ }
+ ],
+ "name": "removeLiquidity",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "tokenAOut",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "tokenBOut",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "recipient",
+ "type": "address"
+ },
+ {
+ "components": [
+ {
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ },
+ {
+ "internalType": "bool",
+ "name": "tokenAIn",
+ "type": "bool"
+ },
+ {
+ "internalType": "bool",
+ "name": "exactOutput",
+ "type": "bool"
+ },
+ {
+ "internalType": "int32",
+ "name": "tickLimit",
+ "type": "int32"
+ }
+ ],
+ "internalType": "struct IMaverickV2Pool.SwapParams",
+ "name": "params",
+ "type": "tuple"
+ },
+ {
+ "internalType": "bytes",
+ "name": "data",
+ "type": "bytes"
+ }
+ ],
+ "name": "swap",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "amountIn",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "amountOut",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "tickSpacing",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "tokenA",
+ "outputs": [
+ {
+ "internalType": "contract IERC20",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "tokenAScale",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "tokenB",
+ "outputs": [
+ {
+ "internalType": "contract IERC20",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "tokenBScale",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ }
+ ]
\ No newline at end of file
diff --git a/brownie/abi/rooster_amo_strat.json b/brownie/abi/rooster_amo_strat.json
new file mode 100644
index 0000000000..90696d3170
--- /dev/null
+++ b/brownie/abi/rooster_amo_strat.json
@@ -0,0 +1,1179 @@
+[
+ {
+ "inputs": [
+ {
+ "components": [
+ {
+ "internalType": "address",
+ "name": "platformAddress",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "vaultAddress",
+ "type": "address"
+ }
+ ],
+ "internalType": "struct InitializableAbstractStrategy.BaseStrategyConfig",
+ "name": "_stratConfig",
+ "type": "tuple"
+ },
+ {
+ "internalType": "address",
+ "name": "_wethAddress",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "_oethpAddress",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "_liquidityManager",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "_poolLens",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "_maverickPosition",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "_maverickQuoter",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "_mPool",
+ "type": "address"
+ },
+ {
+ "internalType": "bool",
+ "name": "_upperTickAtParity",
+ "type": "bool"
+ },
+ {
+ "internalType": "address",
+ "name": "_votingDistributor",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "_poolDistributor",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "nonpayable",
+ "type": "constructor"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "tokenOffered",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "tokenRequired",
+ "type": "uint256"
+ },
+ {
+ "internalType": "address",
+ "name": "token",
+ "type": "address"
+ }
+ ],
+ "name": "InsufficientTokenBalance",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "wethBalance",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "requiredWeth",
+ "type": "uint256"
+ }
+ ],
+ "name": "NotEnoughWethForSwap",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "wethBalance",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "requiredWeth",
+ "type": "uint256"
+ }
+ ],
+ "name": "NotEnoughWethLiquidity",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "OutsideExpectedTickRange",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "currentPoolWethShare",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "allowedWethShareStart",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "allowedWethShareEnd",
+ "type": "uint256"
+ }
+ ],
+ "name": "PoolRebalanceOutOfBounds",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "tokenReceived",
+ "type": "uint256"
+ }
+ ],
+ "name": "SlippageCheck",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "int256",
+ "name": "tick",
+ "type": "int256"
+ }
+ ],
+ "name": "TickMaxExceeded",
+ "type": "error"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "_asset",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "_pToken",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "_amount",
+ "type": "uint256"
+ }
+ ],
+ "name": "Deposit",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "previousGovernor",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "newGovernor",
+ "type": "address"
+ }
+ ],
+ "name": "GovernorshipTransferred",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "_oldHarvesterAddress",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "_newHarvesterAddress",
+ "type": "address"
+ }
+ ],
+ "name": "HarvesterAddressesUpdated",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "wethAmountDesired",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "oethbAmountDesired",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "wethAmountSupplied",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "oethbAmountSupplied",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "tokenId",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "underlyingAssets",
+ "type": "uint256"
+ }
+ ],
+ "name": "LiquidityAdded",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "withdrawLiquidityShare",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "removedWETHAmount",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "removedOETHbAmount",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "underlyingAssets",
+ "type": "uint256"
+ }
+ ],
+ "name": "LiquidityRemoved",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "_asset",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "_pToken",
+ "type": "address"
+ }
+ ],
+ "name": "PTokenAdded",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "_asset",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "_pToken",
+ "type": "address"
+ }
+ ],
+ "name": "PTokenRemoved",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "previousGovernor",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "newGovernor",
+ "type": "address"
+ }
+ ],
+ "name": "PendingGovernorshipTransfer",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "currentPoolWethShare",
+ "type": "uint256"
+ }
+ ],
+ "name": "PoolRebalanced",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "allowedWethShareStart",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "allowedWethShareEnd",
+ "type": "uint256"
+ }
+ ],
+ "name": "PoolWethShareIntervalUpdated",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "address[]",
+ "name": "_oldAddresses",
+ "type": "address[]"
+ },
+ {
+ "indexed": false,
+ "internalType": "address[]",
+ "name": "_newAddresses",
+ "type": "address[]"
+ }
+ ],
+ "name": "RewardTokenAddressesUpdated",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "recipient",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "rewardToken",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ }
+ ],
+ "name": "RewardTokenCollected",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "underlyingAssets",
+ "type": "uint256"
+ }
+ ],
+ "name": "UnderlyingAssetsUpdated",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "_asset",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "_pToken",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "_amount",
+ "type": "uint256"
+ }
+ ],
+ "name": "Withdrawal",
+ "type": "event"
+ },
+ {
+ "inputs": [],
+ "name": "OETHp",
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "SOLVENCY_THRESHOLD",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "WETH",
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "allowedWethShareEnd",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "allowedWethShareStart",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "name": "assetToPToken",
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_asset",
+ "type": "address"
+ }
+ ],
+ "name": "checkBalance",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "claimGovernance",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "collectRewardTokens",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_asset",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_amount",
+ "type": "uint256"
+ }
+ ],
+ "name": "deposit",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "depositAll",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "getCurrentTradingTick",
+ "outputs": [
+ {
+ "internalType": "int32",
+ "name": "_currentTick",
+ "type": "int32"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "getPoolSqrtPrice",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "getPositionPrincipal",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "_amountWeth",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_amountOethp",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "getRewardTokenAddresses",
+ "outputs": [
+ {
+ "internalType": "address[]",
+ "name": "",
+ "type": "address[]"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "getWETHShare",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "governor",
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "harvesterAddress",
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "initialize",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "isGovernor",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "liquidityManager",
+ "outputs": [
+ {
+ "internalType": "contract IMaverickV2LiquidityManager",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "mPool",
+ "outputs": [
+ {
+ "internalType": "contract IMaverickV2Pool",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "maverickPosition",
+ "outputs": [
+ {
+ "internalType": "contract IMaverickV2Position",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "mintInitialPosition",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "platformAddress",
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "poolDistributor",
+ "outputs": [
+ {
+ "internalType": "contract IPoolDistributor",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "poolLens",
+ "outputs": [
+ {
+ "internalType": "contract IMaverickV2PoolLens",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "quoter",
+ "outputs": [
+ {
+ "internalType": "contract IMaverickV2Quoter",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "_amountToSwap",
+ "type": "uint256"
+ },
+ {
+ "internalType": "bool",
+ "name": "_swapWeth",
+ "type": "bool"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_minTokenReceived",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_liquidityToRemovePct",
+ "type": "uint256"
+ }
+ ],
+ "name": "rebalance",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "name": "removePToken",
+ "outputs": [],
+ "stateMutability": "pure",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "name": "rewardTokenAddresses",
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "safeApproveAllTokens",
+ "outputs": [],
+ "stateMutability": "pure",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "_allowedWethShareStart",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_allowedWethShareEnd",
+ "type": "uint256"
+ }
+ ],
+ "name": "setAllowedPoolWethShareInterval",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_harvesterAddress",
+ "type": "address"
+ }
+ ],
+ "name": "setHarvesterAddress",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "name": "setPTokenAddress",
+ "outputs": [],
+ "stateMutability": "pure",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address[]",
+ "name": "_rewardTokenAddresses",
+ "type": "address[]"
+ }
+ ],
+ "name": "setRewardTokenAddresses",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "sqrtPriceAtParity",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "sqrtPriceTickHigher",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "sqrtPriceTickLower",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_asset",
+ "type": "address"
+ }
+ ],
+ "name": "supportsAsset",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "tickDominance",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "_tickDominance",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "tickNumber",
+ "outputs": [
+ {
+ "internalType": "int32",
+ "name": "",
+ "type": "int32"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "tickSpacing",
+ "outputs": [
+ {
+ "internalType": "int24",
+ "name": "",
+ "type": "int24"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "tokenId",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_newGovernor",
+ "type": "address"
+ }
+ ],
+ "name": "transferGovernance",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_asset",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_amount",
+ "type": "uint256"
+ }
+ ],
+ "name": "transferToken",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "underlyingAssets",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "vaultAddress",
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "votingDistributor",
+ "outputs": [
+ {
+ "internalType": "contract IVotingDistributor",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_recipient",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "_asset",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_amount",
+ "type": "uint256"
+ }
+ ],
+ "name": "withdraw",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "withdrawAll",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ }
+ ]
\ No newline at end of file
diff --git a/brownie/abi/rooster_maverick_pool.json b/brownie/abi/rooster_maverick_pool.json
new file mode 100644
index 0000000000..c252d5b401
--- /dev/null
+++ b/brownie/abi/rooster_maverick_pool.json
@@ -0,0 +1,1168 @@
+[
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "binIdsLength",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "amountsLength",
+ "type": "uint256"
+ }
+ ],
+ "name": "PoolBinIdsAmountsLengthMismatch",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "int32",
+ "name": "startingTick",
+ "type": "int32"
+ }
+ ],
+ "name": "PoolCurrentTickBeyondSwapLimit",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "PoolFunctionNotImplemented",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "deltaLpAmount",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "accountBalance",
+ "type": "uint256"
+ }
+ ],
+ "name": "PoolInsufficientBalance",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "PoolInvalidFee",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "kinds",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "kind",
+ "type": "uint256"
+ }
+ ],
+ "name": "PoolKindNotSupported",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "PoolLocked",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "PoolMigrateBinFirst",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "PoolMinimumLiquidityNotMet",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "PoolNoProtocolFeeReceiverSet",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ }
+ ],
+ "name": "PoolReservesExceedMaximum",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "sender_",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "accessor",
+ "type": "address"
+ }
+ ],
+ "name": "PoolSenderNotAccessor",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "sender_",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "accessor",
+ "type": "address"
+ }
+ ],
+ "name": "PoolSenderNotFactory",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "tick",
+ "type": "uint256"
+ }
+ ],
+ "name": "PoolTickMaxExceeded",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "ticksLength",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "amountsLength",
+ "type": "uint256"
+ }
+ ],
+ "name": "PoolTicksAmountsLengthMismatch",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "index",
+ "type": "uint256"
+ },
+ {
+ "internalType": "int256",
+ "name": "previousTick",
+ "type": "int256"
+ },
+ {
+ "internalType": "int256",
+ "name": "tick",
+ "type": "int256"
+ }
+ ],
+ "name": "PoolTicksNotSorted",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "internalReserve",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "tokenBalance",
+ "type": "uint256"
+ },
+ {
+ "internalType": "contract IERC20",
+ "name": "token",
+ "type": "address"
+ }
+ ],
+ "name": "PoolTokenNotSolvent",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "bits",
+ "type": "uint256"
+ }
+ ],
+ "name": "PoolValueExceedsBits",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "PoolZeroLiquidityAdded",
+ "type": "error"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "sender",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "recipient",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "subaccount",
+ "type": "uint256"
+ },
+ {
+ "components": [
+ {
+ "internalType": "uint8",
+ "name": "kind",
+ "type": "uint8"
+ },
+ {
+ "internalType": "int32[]",
+ "name": "ticks",
+ "type": "int32[]"
+ },
+ {
+ "internalType": "uint128[]",
+ "name": "amounts",
+ "type": "uint128[]"
+ }
+ ],
+ "indexed": false,
+ "internalType": "struct IMaverickV2Pool.AddLiquidityParams",
+ "name": "params",
+ "type": "tuple"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "tokenAAmount",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "tokenBAmount",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint32[]",
+ "name": "binIds",
+ "type": "uint32[]"
+ }
+ ],
+ "name": "PoolAddLiquidity",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "sender",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "recipient",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "amountA",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "amountB",
+ "type": "uint256"
+ }
+ ],
+ "name": "PoolFlashLoan",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "sender",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint32",
+ "name": "binId",
+ "type": "uint32"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint32",
+ "name": "maxRecursion",
+ "type": "uint32"
+ }
+ ],
+ "name": "PoolMigrateBinsUpStack",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "contract IERC20",
+ "name": "token",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "protocolFee",
+ "type": "uint256"
+ }
+ ],
+ "name": "PoolProtocolFeeCollected",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "sender",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "recipient",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "subaccount",
+ "type": "uint256"
+ },
+ {
+ "components": [
+ {
+ "internalType": "uint32[]",
+ "name": "binIds",
+ "type": "uint32[]"
+ },
+ {
+ "internalType": "uint128[]",
+ "name": "amounts",
+ "type": "uint128[]"
+ }
+ ],
+ "indexed": false,
+ "internalType": "struct IMaverickV2Pool.RemoveLiquidityParams",
+ "name": "params",
+ "type": "tuple"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "tokenAOut",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "tokenBOut",
+ "type": "uint256"
+ }
+ ],
+ "name": "PoolRemoveLiquidity",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "sqrtPrice",
+ "type": "uint256"
+ }
+ ],
+ "name": "PoolSqrtPrice",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "sender",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "recipient",
+ "type": "address"
+ },
+ {
+ "components": [
+ {
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ },
+ {
+ "internalType": "bool",
+ "name": "tokenAIn",
+ "type": "bool"
+ },
+ {
+ "internalType": "bool",
+ "name": "exactOutput",
+ "type": "bool"
+ },
+ {
+ "internalType": "int32",
+ "name": "tickLimit",
+ "type": "int32"
+ }
+ ],
+ "indexed": false,
+ "internalType": "struct IMaverickV2Pool.SwapParams",
+ "name": "params",
+ "type": "tuple"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "amountIn",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "amountOut",
+ "type": "uint256"
+ }
+ ],
+ "name": "PoolSwap",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "int32",
+ "name": "tick",
+ "type": "int32"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint8",
+ "name": "kind",
+ "type": "uint8"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint32",
+ "name": "binId",
+ "type": "uint32"
+ }
+ ],
+ "name": "PoolTickBinUpdate",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "int32",
+ "name": "tick",
+ "type": "int32"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "reserveA",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "reserveB",
+ "type": "uint256"
+ }
+ ],
+ "name": "PoolTickState",
+ "type": "event"
+ },
+ {
+ "inputs": [],
+ "name": "accessor",
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "recipient",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "subaccount",
+ "type": "uint256"
+ },
+ {
+ "components": [
+ {
+ "internalType": "uint8",
+ "name": "kind",
+ "type": "uint8"
+ },
+ {
+ "internalType": "int32[]",
+ "name": "ticks",
+ "type": "int32[]"
+ },
+ {
+ "internalType": "uint128[]",
+ "name": "amounts",
+ "type": "uint128[]"
+ }
+ ],
+ "internalType": "struct IMaverickV2Pool.AddLiquidityParams",
+ "name": "params",
+ "type": "tuple"
+ },
+ {
+ "internalType": "bytes",
+ "name": "data",
+ "type": "bytes"
+ }
+ ],
+ "name": "addLiquidity",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "tokenAAmount",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "tokenBAmount",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint32[]",
+ "name": "binIds",
+ "type": "uint32[]"
+ }
+ ],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "user",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "subaccount",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint32",
+ "name": "binId",
+ "type": "uint32"
+ }
+ ],
+ "name": "balanceOf",
+ "outputs": [
+ {
+ "internalType": "uint128",
+ "name": "lpToken",
+ "type": "uint128"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "int32",
+ "name": "tick",
+ "type": "int32"
+ },
+ {
+ "internalType": "uint256",
+ "name": "kind",
+ "type": "uint256"
+ }
+ ],
+ "name": "binIdByTickKind",
+ "outputs": [
+ {
+ "internalType": "uint32",
+ "name": "",
+ "type": "uint32"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bool",
+ "name": "isTokenA",
+ "type": "bool"
+ }
+ ],
+ "name": "distributeFees",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "protocolFee",
+ "type": "uint256"
+ },
+ {
+ "internalType": "contract IERC20",
+ "name": "token",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "factory",
+ "outputs": [
+ {
+ "internalType": "contract IMaverickV2Factory",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bool",
+ "name": "tokenAIn",
+ "type": "bool"
+ }
+ ],
+ "name": "fee",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "recipient",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "amountA",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "amountB",
+ "type": "uint256"
+ },
+ {
+ "internalType": "bytes",
+ "name": "data",
+ "type": "bytes"
+ }
+ ],
+ "name": "flashLoan",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint32",
+ "name": "binId",
+ "type": "uint32"
+ }
+ ],
+ "name": "getBin",
+ "outputs": [
+ {
+ "components": [
+ {
+ "internalType": "uint128",
+ "name": "mergeBinBalance",
+ "type": "uint128"
+ },
+ {
+ "internalType": "uint128",
+ "name": "tickBalance",
+ "type": "uint128"
+ },
+ {
+ "internalType": "uint128",
+ "name": "totalSupply",
+ "type": "uint128"
+ },
+ {
+ "internalType": "uint8",
+ "name": "kind",
+ "type": "uint8"
+ },
+ {
+ "internalType": "int32",
+ "name": "tick",
+ "type": "int32"
+ },
+ {
+ "internalType": "uint32",
+ "name": "mergeId",
+ "type": "uint32"
+ }
+ ],
+ "internalType": "struct IMaverickV2Pool.BinState",
+ "name": "bin",
+ "type": "tuple"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "getCurrentTwa",
+ "outputs": [
+ {
+ "internalType": "int256",
+ "name": "",
+ "type": "int256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "getState",
+ "outputs": [
+ {
+ "components": [
+ {
+ "internalType": "uint128",
+ "name": "reserveA",
+ "type": "uint128"
+ },
+ {
+ "internalType": "uint128",
+ "name": "reserveB",
+ "type": "uint128"
+ },
+ {
+ "internalType": "int64",
+ "name": "lastTwaD8",
+ "type": "int64"
+ },
+ {
+ "internalType": "int64",
+ "name": "lastLogPriceD8",
+ "type": "int64"
+ },
+ {
+ "internalType": "uint40",
+ "name": "lastTimestamp",
+ "type": "uint40"
+ },
+ {
+ "internalType": "int32",
+ "name": "activeTick",
+ "type": "int32"
+ },
+ {
+ "internalType": "bool",
+ "name": "isLocked",
+ "type": "bool"
+ },
+ {
+ "internalType": "uint32",
+ "name": "binCounter",
+ "type": "uint32"
+ },
+ {
+ "internalType": "uint8",
+ "name": "protocolFeeRatioD3",
+ "type": "uint8"
+ }
+ ],
+ "internalType": "struct IMaverickV2Pool.State",
+ "name": "",
+ "type": "tuple"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "int32",
+ "name": "tick",
+ "type": "int32"
+ }
+ ],
+ "name": "getTick",
+ "outputs": [
+ {
+ "components": [
+ {
+ "internalType": "uint128",
+ "name": "reserveA",
+ "type": "uint128"
+ },
+ {
+ "internalType": "uint128",
+ "name": "reserveB",
+ "type": "uint128"
+ },
+ {
+ "internalType": "uint128",
+ "name": "totalSupply",
+ "type": "uint128"
+ },
+ {
+ "internalType": "uint32[4]",
+ "name": "binIdsByTick",
+ "type": "uint32[4]"
+ }
+ ],
+ "internalType": "struct IMaverickV2Pool.TickState",
+ "name": "tickState",
+ "type": "tuple"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "kinds",
+ "outputs": [
+ {
+ "internalType": "uint8",
+ "name": "_kinds",
+ "type": "uint8"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "lendingFeeRateD18",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "lookback",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint32",
+ "name": "binId",
+ "type": "uint32"
+ },
+ {
+ "internalType": "uint32",
+ "name": "maxRecursion",
+ "type": "uint32"
+ }
+ ],
+ "name": "migrateBinUpStack",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "protocolFeeA",
+ "outputs": [
+ {
+ "internalType": "uint128",
+ "name": "",
+ "type": "uint128"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "protocolFeeB",
+ "outputs": [
+ {
+ "internalType": "uint128",
+ "name": "",
+ "type": "uint128"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "recipient",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "subaccount",
+ "type": "uint256"
+ },
+ {
+ "components": [
+ {
+ "internalType": "uint32[]",
+ "name": "binIds",
+ "type": "uint32[]"
+ },
+ {
+ "internalType": "uint128[]",
+ "name": "amounts",
+ "type": "uint128[]"
+ }
+ ],
+ "internalType": "struct IMaverickV2Pool.RemoveLiquidityParams",
+ "name": "params",
+ "type": "tuple"
+ }
+ ],
+ "name": "removeLiquidity",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "tokenAOut",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "tokenBOut",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "recipient",
+ "type": "address"
+ },
+ {
+ "components": [
+ {
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ },
+ {
+ "internalType": "bool",
+ "name": "tokenAIn",
+ "type": "bool"
+ },
+ {
+ "internalType": "bool",
+ "name": "exactOutput",
+ "type": "bool"
+ },
+ {
+ "internalType": "int32",
+ "name": "tickLimit",
+ "type": "int32"
+ }
+ ],
+ "internalType": "struct IMaverickV2Pool.SwapParams",
+ "name": "params",
+ "type": "tuple"
+ },
+ {
+ "internalType": "bytes",
+ "name": "data",
+ "type": "bytes"
+ }
+ ],
+ "name": "swap",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "amountIn",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "amountOut",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "tickSpacing",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "tokenA",
+ "outputs": [
+ {
+ "internalType": "contract IERC20",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "tokenAScale",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "tokenB",
+ "outputs": [
+ {
+ "internalType": "contract IERC20",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "tokenBScale",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ }
+ ]
\ No newline at end of file
diff --git a/brownie/scripts/strategy_report_amo.py b/brownie/scripts/strategy_report_amo.py
index 7434adc67c..2ba6e79f63 100644
--- a/brownie/scripts/strategy_report_amo.py
+++ b/brownie/scripts/strategy_report_amo.py
@@ -7,7 +7,8 @@
# NOTICE: un-comment the below import depending on the chain in which the test is ran
# from world import *
# from world_base import *
-from world_sonic import *
+# from world_sonic import *
+from world_plume import *
WSTETH_WHALE = "0x176f3dab24a159341c0509bb36b833e7fdd0a132"
@@ -22,6 +23,11 @@
# the WOS contract
SONIC_OS_WHALE = "0x9F0dF7799f6FDAd409300080cfF680f5A23df4b1"
+# Plume
+# Account holds 440 tokens on block number 3507372
+PLUME_WETH_FISH = "0x4FF1b9D9ba8558F5EAfCec096318eA0d8b541971"
+
+
MASTER_SIZE = 60
NUM_DEPOSIT_TESTS_EACH = MASTER_SIZE
NUM_BALANCE_TESTS_EACH = MASTER_SIZE
@@ -54,6 +60,9 @@ def pool_balances(self):
balances = self._balancer_vault.getPoolTokens(self._pool_pid)[0:2]
return dict(zip(balances[0], balances[1]))
+ def print_debug_data(self):
+ pass
+
def tilt_pool(self, size):
print("Tilt pool", size)
amount = abs(size) * self.base_size * int(1e18)
@@ -107,6 +116,9 @@ def pool_balances(self):
balances = self._balancer_vault.getPoolTokens(self._pool_pid)
return dict(zip(balances[0][1:3], balances[1][1:3]))
+ def print_debug_data(self):
+ pass
+
def tilt_pool(self, size):
amount = abs(size) * self.base_size * int(1e18)
print("Tilt pool", size, amount)
@@ -151,7 +163,6 @@ def __init__(self):
self.amo_base = weth
def setup(self):
- print(weth.address)
weth.approve(self.vault_core, 1e70, {"from": BASE_WETH_WHALE})
weth.approve(self.pool, 1e70, {"from": BASE_WETH_WHALE})
oethb.approve(self.pool, 1e70, {"from": BASE_WETH_WHALE})
@@ -164,6 +175,9 @@ def pool_balances(self):
oethb.address: self.pool.balances(1),
}
+ def print_debug_data(self):
+ pass
+
def tilt_pool(self, size):
vault_core.mint(weth, 1000*10**18, 0, {"from": BASE_WETH_WHALE})
amount = abs(size) * self.base_size * int(1e18)
@@ -207,6 +221,9 @@ def setup(self):
ws.approve(self.pool, 1e70, {"from": SONIC_WS_WHALE})
os.approve(self.pool, 1e70, {"from": SONIC_WS_WHALE})
+ deposit_amount = self.base_size * 0.1 * int(1e18)
+ self.amo_base.transfer(vault_admin, deposit_amount, {"from": SONIC_WS_WHALE})
+
def pool_balances(self):
print("⚱︎ pool_balances")
@@ -216,6 +233,9 @@ def pool_balances(self):
"os": self.pool.reserve1(),
}
+ def print_debug_data(self):
+ pass
+
def tilt_pool(self, size):
# 10m OS
vault_core.mint(ws, 10 * 10**24, 0, {"from": SONIC_WS_WHALE})
@@ -243,6 +263,121 @@ def pool_create_mix(self, tilt=0.5, size=1):
}
return mix
+class RoosterWETHOethp:
+ def __init__(self):
+ # TODO: the strat address will change once the pool is live
+ strat_address = "0xEA24e9Bac006DE9635Ac7fA4D767fFb64FB5645c"
+ pool_address = "0x3F86B564A9B530207876d2752948268b9Bf04F71"
+ self.strat = load_contract("rooster_amo_strat", strat_address)
+ self.pool = load_contract("rooster_maverick_pool", pool_address)
+ self.name = "Rooster Pool AMO"
+ self.vault_core = vault_core
+ self.vault_admin = vault_admin
+ self.otoken = oethp
+ # for check balance tests 200 base
+ #self.base_size = int(200)
+ # for deposit tests use 50 base
+ self.base_size = int(50)
+ self.STRATEGIST = vault_core.strategistAddr()
+ self.amo_base = weth
+ self.deposit_debug_data = []
+
+ def setup(self):
+ weth.approve(self.vault_core, 1e70, {"from": PLUME_WETH_FISH})
+ oethAmountNeeded = 200 * 10**18
+
+ if oethp.balanceOf(PLUME_WETH_FISH) < oethAmountNeeded:
+ vault_core.mint(weth, 200 * 10**18, 0, {"from": PLUME_WETH_FISH})
+
+ def pool_balances(self):
+ print("⚱︎ pool_balances")
+
+ return {
+ "weth": self.pool.getState()[0], # reserveA
+ "oethp": self.pool.getState()[0] # reserveB,
+ }
+
+ def print_debug_data(self):
+ for data in self.deposit_debug_data:
+ print(data)
+
+ def tilt_pool(self, size):
+ amount = abs(size) * self.base_size * int(1e18)
+ print("Tilt pool", size, amount)
+ if size > -0.00001 and size < 0.00001:
+ print("skip tilt")
+ pass
+
+ self.swap_pool(amount, size > 0)
+
+ def swap_pool(self, amount, is_weth):
+ if is_weth:
+ weth.transfer(self.pool.address, amount, {"from": PLUME_WETH_FISH})
+ self.pool.swap(
+ PLUME_WETH_FISH,
+ [
+ amount, #amount
+ True, #tokenAIn
+ False, #exactOutput
+ 1000 #tickLimit
+ ],
+ b'',
+ {"from": PLUME_WETH_FISH}
+ );
+ else:
+ oethp.transfer(self.pool.address, amount, {"from": PLUME_WETH_FISH})
+ self.pool.swap(
+ PLUME_WETH_FISH,
+ [
+ amount, #amount
+ False, #tokenAIn
+ False, #exactOutput
+ -1000 #tickLimit
+ ],
+ b'',
+ {"from": PLUME_WETH_FISH}
+ );
+
+ def estimate_swap_amount_to_reach_weth_ratio(self, wethRatio):
+ tickInfo = self.pool.getTick(-1);
+ wethReserve = tickInfo[0]
+ oethpReserve = tickInfo[1]
+ total = wethReserve + oethpReserve
+
+ if wethReserve == 0 or oethpReserve == 0:
+ print("wethReserve: ", wethReserve, " oethpReserve:", oethpReserve)
+ raise Exception("Not in the -1 trading tick. Perform a swap to move the trading in the pool to -1 tick")
+
+
+ currentWethRatio = wethReserve * 1e18 / total
+ if wethRatio > currentWethRatio:
+ diff = wethRatio - currentWethRatio
+ swapWeth = True
+ else:
+ diff = currentWethRatio - wethRatio
+ swapWeth = False
+
+ return (diff * total / 1e18, swapWeth)
+
+
+ def write_debug_data(self, size):
+ self.deposit_debug_data.append(
+ (
+ round(size, 2),
+ self.strat.getCurrentTradingTick(),
+ round(self.strat.checkBalance(self.amo_base) / 1e18, 2),
+ round(self.strat.getWETHShare() / 1e16, 2)
+ )
+ )
+
+ def pool_create_mix(self, tilt=0.5, size=1):
+ print("⚱︎ pool_create_mix")
+ mix = {
+ weth.address: 2 + int(size * self.base_size * (int(1e18) - (int(1e18) * tilt))),
+ oethp.address: 2 + int(size * self.base_size * int(1e18) * tilt),
+ }
+ return mix
+
# --------------
@@ -256,6 +391,8 @@ def _getHarness(name):
return CurveSuperOETHbWETH()
elif name == "SwapxOsWS":
return SwapxOsWS()
+ elif name == "RoosterWETHOethp":
+ return RoosterWETHOethp()
return
@@ -301,6 +438,8 @@ def run_simulations_amo(strategy_name):
workspace = _getWorkspace(strategy_name)
harness = _getHarness(strategy_name)
+ harness_rooster = strategy_name == "RoosterWETHOethp"
+
# Run setup
harness.setup()
try:
@@ -310,58 +449,61 @@ def run_simulations_amo(strategy_name):
pass
- # Test CheckBalance
- print("Test Check Balance")
- check_balance_stats = []
+ # # Test CheckBalance
+ # print("Test Check Balance")
+ # check_balance_stats = []
- for tilt in np.linspace(-1, 1, 41):
- with TemporaryFork():
- stat = {}
- stat["action"] = "checkBalance"
- stat["action_mix"] = tilt
+ # for tilt in np.linspace(-1, 1, 41):
+ # with TemporaryFork():
+ # stat = {}
+ # stat["action"] = "checkBalance"
+ # stat["action_mix"] = tilt
- deposit_amount = harness.base_size * 0.5 * int(1e18)
- ws.transfer(vault_admin, deposit_amount * 2, {"from": SONIC_WS_WHALE})
+ # deposit_amount = harness.base_size * 0.1 * 10**18
- print("deposit to strategy")
- harness.vault_admin.depositToStrategy(
- harness.strat,
- [harness.amo_base],
- #[deposit_amount / 5],
- # deposit a smaller amount for an extreme tilt
- [deposit_amount / 50],
- {"from": harness.STRATEGIST},
- )
+ # if harness_rooster:
+ # # manually correct the current pool price to put it into -1 tick
+ # harness.tilt_pool(0.1)
- pb = list(harness.pool_balances().values())
- stat["pre_pool_0"] = pb[0]
- stat["pre_pool_1"] = pb[1]
- stat["before_pool_0"] = pb[0]
- stat["before_pool_1"] = pb[1]
- stat["pre_vault"] = harness.vault_core.totalValue()
- stat["before_vault"] = harness.vault_core.totalValue()
- stat["before_otoken"] = harness.otoken.totalSupply()
- stat["pool_before_check_balance"] = harness.strat.checkBalance(ws)
+ # (amount_to_swap, swap_weth) = harness.estimate_swap_amount_to_reach_weth_ratio(0.2e18) # 20%
+ # harness.swap_pool(amount_to_swap, swap_weth)
+
+ # print("deposit to strategy")
+ # harness.vault_admin.depositToStrategy(
+ # harness.strat,
+ # [harness.amo_base],
+ # [deposit_amount],
+ # {"from": harness.STRATEGIST},
+ # )
+
+ # pb = list(harness.pool_balances().values())
+ # stat["pre_pool_0"] = pb[0]
+ # stat["pre_pool_1"] = pb[1]
+ # stat["before_pool_0"] = pb[0]
+ # stat["before_pool_1"] = pb[1]
+ # stat["pre_vault"] = harness.vault_core.totalValue()
+ # stat["before_vault"] = harness.vault_core.totalValue()
+ # stat["before_otoken"] = harness.otoken.totalSupply()
+ # stat["pool_before_check_balance"] = harness.strat.checkBalance(harness.amo_base)
- harness.tilt_pool(tilt)
+ # harness.tilt_pool(tilt)
- pb = list(harness.pool_balances().values())
- stat["after_pool_0"] = pb[0]
- stat["after_pool_1"] = pb[1]
- stat["after_vault"] = harness.vault_core.totalValue()
- stat["after_otoken"] = harness.otoken.totalSupply()
- stat["pool_after_check_balance"] = harness.strat.checkBalance(ws)
+ # pb = list(harness.pool_balances().values())
+ # stat["after_pool_0"] = pb[0]
+ # stat["after_pool_1"] = pb[1]
+ # stat["after_vault"] = harness.vault_core.totalValue()
+ # stat["after_otoken"] = harness.otoken.totalSupply()
+ # stat["pool_after_check_balance"] = harness.strat.checkBalance(harness.amo_base)
- check_balance_stats.append(stat)
+ # check_balance_stats.append(stat)
- pd.DataFrame.from_records(check_balance_stats).to_csv(workspace + "check_balance_stats.csv")
+ # pd.DataFrame.from_records(check_balance_stats).to_csv(workspace + "check_balance_stats.csv")
# # Test Deposits
# print("# Test Deposits")
# deposit_stats = []
- # vault_core.mint(ws, harness.base_size * 1e18, 0, {"from": SONIC_WS_WHALE})
# for initial_tilt in np.linspace(-0.1, 0.32, 41):
# with TemporaryFork():
# stat = {}
@@ -369,13 +511,10 @@ def run_simulations_amo(strategy_name):
# stat["action"] = "deposit"
# stat["action_mix"] = initial_tilt
- # # mint some OS in case vault doesn't have enough WS liquidity
- # # vault_core.mint(ws, 5 * 10**24, 0, {"from": SONIC_WS_WHALE})
-
# harness.vault_admin.depositToStrategy(
# harness.strat,
# [harness.amo_base],
- # [harness.base_size * 0.5],
+ # [harness.base_size * 0.5 * 10**18],
# {"from": harness.STRATEGIST},
# )
@@ -396,10 +535,12 @@ def run_simulations_amo(strategy_name):
# harness.vault_admin.depositToStrategy(
# harness.strat,
# [harness.amo_base],
- # [harness.base_size * 0.2],
+ # [harness.base_size * 0.2 * 10**18],
# {"from": harness.STRATEGIST},
# )
+ # harness.write_debug_data(initial_tilt)
+
# stat["after_vault"] = harness.vault_core.totalValue()
# stat["after_otoken"] = harness.otoken.totalSupply()
# pb = list(harness.pool_balances().values())
@@ -408,77 +549,32 @@ def run_simulations_amo(strategy_name):
# deposit_stats.append(stat)
# pd.DataFrame.from_records(deposit_stats).to_csv(workspace + "deposit_stats.csv")
-
# # Test Withdraws
# withdraw_stats = []
# for initial_tilt in np.linspace(-0.7, 0.7, 41):
- # with TemporaryFork():
- # stat = {}
-
- # stat["action"] = "withdraw"
- # stat["action_mix"] = initial_tilt
- # print("w Deposit")
- # pb = list(harness.pool_balances().values())
- # stat["pre_pool_0"] = pb[0]
- # stat["pre_pool_1"] = pb[1]
- # print(pb[0]/10**18,pb[1]/10**18)
-
- # harness.vault_admin.depositToStrategy(
- # harness.strat,
- # [harness.amo_base],
- # [harness.base_size * 1 * 10**18],
- # {"from": harness.STRATEGIST},
- # )
-
- # stat["pre_vault"] = harness.vault_core.totalValue()
- # pb = list(harness.pool_balances().values())
- # stat["pre_pool_0"] = pb[0]
- # stat["pre_pool_1"] = pb[1]
- # print(pb[0]/10**18,pb[1]/10**18)
-
- # print("Withdraw Tilt")
- # harness.tilt_pool(initial_tilt)
-
- # stat["before_vault"] = harness.vault_core.totalValue()
- # stat["before_otoken"] = harness.otoken.totalSupply()
- # pb = list(harness.pool_balances().values())
- # stat["before_pool_0"] = pb[0]
- # stat["before_pool_1"] = pb[1]
- # print(pb[0]/10**18,pb[1]/10**18)
-
- # print("Withdraw Withdraw")
- # harness.vault_admin.withdrawFromStrategy(
- # harness.strat,
- # [harness.amo_base],
- # [1e18],
- # {"from": harness.STRATEGIST, "allow_revert": True},
- # )
-
- # stat["after_vault"] = harness.vault_core.totalValue()
- # stat["after_otoken"] = harness.otoken.totalSupply()
- # pb = list(harness.pool_balances().values())
- # stat["after_pool_0"] = pb[0]
- # stat["after_pool_1"] = pb[1]
-
- # withdraw_stats.append(stat)
-
- # pd.DataFrame.from_records(withdraw_stats).to_csv(workspace + "withdraw_stats.csv")
-
- # Test WithdrawAll
-
- # withdrawall_stats = []
- # for initial_tilt in np.linspace(-1.5, 1.5, 41):
# with TemporaryFork():
# stat = {}
- # stat["action"] = "withdrawall"
+ # if harness_rooster:
+ # # manually correct the current pool price to put it into -1 tick
+ # harness.tilt_pool(0.1)
+
+ # (amount_to_swap, swap_weth) = harness.estimate_swap_amount_to_reach_weth_ratio(0.2e18) # 20%
+ # harness.swap_pool(amount_to_swap, swap_weth)
+
+ # stat["action"] = "withdraw"
# stat["action_mix"] = initial_tilt
+ # print("w Deposit")
+ # pb = list(harness.pool_balances().values())
+ # stat["pre_pool_0"] = pb[0]
+ # stat["pre_pool_1"] = pb[1]
+ # print(pb[0]/10**18,pb[1]/10**18)
# harness.vault_admin.depositToStrategy(
# harness.strat,
# [harness.amo_base],
- # [harness.base_size * 2.0],
+ # [harness.base_size * 1 * 10**18],
# {"from": harness.STRATEGIST},
# )
@@ -486,7 +582,9 @@ def run_simulations_amo(strategy_name):
# pb = list(harness.pool_balances().values())
# stat["pre_pool_0"] = pb[0]
# stat["pre_pool_1"] = pb[1]
+ # print(pb[0]/10**18,pb[1]/10**18)
+ # print("Withdraw Tilt")
# harness.tilt_pool(initial_tilt)
# stat["before_vault"] = harness.vault_core.totalValue()
@@ -494,9 +592,16 @@ def run_simulations_amo(strategy_name):
# pb = list(harness.pool_balances().values())
# stat["before_pool_0"] = pb[0]
# stat["before_pool_1"] = pb[1]
+ # print(pb[0]/10**18,pb[1]/10**18)
+
+ # harness.write_debug_data(initial_tilt)
- # harness.vault_admin.withdrawAllFromStrategy(
- # harness.strat, {"from": harness.STRATEGIST}
+ # print("Withdraw Withdraw")
+ # harness.vault_admin.withdrawFromStrategy(
+ # harness.strat,
+ # [harness.amo_base],
+ # [1e18],
+ # {"from": harness.STRATEGIST, "allow_revert": True},
# )
# stat["after_vault"] = harness.vault_core.totalValue()
@@ -505,12 +610,65 @@ def run_simulations_amo(strategy_name):
# stat["after_pool_0"] = pb[0]
# stat["after_pool_1"] = pb[1]
- # withdrawall_stats.append(stat)
+ # withdraw_stats.append(stat)
+
+ # pd.DataFrame.from_records(withdraw_stats).to_csv(workspace + "withdraw_stats.csv")
- # pd.DataFrame.from_records(withdrawall_stats).to_csv(
- # workspace + "withdrawall_stats.csv"
- # )
+ # Test WithdrawAll
+ withdrawall_stats = []
+ for initial_tilt in np.linspace(-1.5, 1.5, 41):
+ with TemporaryFork():
+ stat = {}
+
+ stat["action"] = "withdrawall"
+ stat["action_mix"] = initial_tilt
+
+ if harness_rooster:
+ # manually correct the current pool price to put it into -1 tick
+ harness.tilt_pool(0.1)
+
+ (amount_to_swap, swap_weth) = harness.estimate_swap_amount_to_reach_weth_ratio(0.2e18) # 20%
+ harness.swap_pool(amount_to_swap, swap_weth)
+
+ harness.vault_admin.depositToStrategy(
+ harness.strat,
+ [harness.amo_base],
+ [harness.base_size * 1 * 10**18],
+ {"from": harness.STRATEGIST},
+ )
+
+ stat["pre_vault"] = harness.vault_core.totalValue()
+ pb = list(harness.pool_balances().values())
+ stat["pre_pool_0"] = pb[0]
+ stat["pre_pool_1"] = pb[1]
+
+ harness.tilt_pool(initial_tilt)
+
+ stat["before_vault"] = harness.vault_core.totalValue()
+ stat["before_otoken"] = harness.otoken.totalSupply()
+ pb = list(harness.pool_balances().values())
+ stat["before_pool_0"] = pb[0]
+ stat["before_pool_1"] = pb[1]
+
+ harness.write_debug_data(initial_tilt)
+
+ harness.vault_admin.withdrawAllFromStrategy(
+ harness.strat, {"from": harness.STRATEGIST}
+ )
+
+ stat["after_vault"] = harness.vault_core.totalValue()
+ stat["after_otoken"] = harness.otoken.totalSupply()
+ pb = list(harness.pool_balances().values())
+ stat["after_pool_0"] = pb[0]
+ stat["after_pool_1"] = pb[1]
+
+ withdrawall_stats.append(stat)
+
+ pd.DataFrame.from_records(withdrawall_stats).to_csv(
+ workspace + "withdrawall_stats.csv"
+ )
+ harness.print_debug_data();
@@ -519,9 +677,9 @@ def run_report(strategy_name):
workspace = _getWorkspace(strategy_name)
harness = _getHarness(strategy_name)
- # deposit_base = _load_data(workspace + "/deposit_stats.csv")
- # withdraw_base = _load_data(workspace + "/withdraw_stats.csv")
- # withdrawall_base = _load_data(workspace + "/withdrawall_stats.csv")
+ deposit_base = _load_data(workspace + "/deposit_stats.csv")
+ withdraw_base = _load_data(workspace + "/withdraw_stats.csv")
+ withdrawall_base = _load_data(workspace + "/withdrawall_stats.csv")
checkbalance_base = _load_data(workspace + "/check_balance_stats.csv")
sections = []
@@ -542,53 +700,54 @@ def run_report(strategy_name):
plt.close()
sections.append('
Check Balance
')
- # # Deposit Section
- # df = deposit_base.sort_values('action_mix')
- # plt.title("Deposit profit")
- # plt.axhline(0, c="black", linewidth=0.4)
- # # for before_mix, rows in df.groupby(df["before_mix"]):
- # plt.plot(
- # df["action_mix"] * 100,
- # df["after_profit"],
- # )
- # # plt.ylim([-1e18, 1e18])
- # plt.xlabel("Pool before deposit")
- # plt.ylabel("Deposit Profit")
- # plt.legend()
- # plt.savefig(workspace + "deposit.svg")
- # plt.close()
- # sections.append('Deposit
')
-
- # # # Withdraw Section
- # df = withdraw_base
- # # df = df[df.before_mix != df.after_mix]
- # plt.title("Withdraw profit")
- # plt.axhline(0, c="black", linewidth=0.4)
- # print(df['after_profit'])
- # plt.plot(
- # df["action_mix"] * 100,
- # df["after_profit"],
- # )
- # # plt.ylim([-1e18, 1e18])
- # plt.xlabel("Pool Mix")
- # plt.ylabel("Withdraw Profit")
- # plt.legend()
- # plt.savefig(workspace + "withdraw.svg")
- # plt.close()
- # sections.append('Withdraw
')
-
- # # # Withdraw All
- # df = withdrawall_base
- # plt.axhline(0, c="black", linewidth=0.4)
- # plt.scatter(df["action_mix"]/1.5+0.5, df["after_profit"])
- # plt.scatter(df["before_mix"], df["after_profit"])
- # plt.plot(df["before_mix"], df["after_profit"])
- # # plt.ylim([-1e18,1e18])
- # plt.xlabel("Pool Mix")
- # plt.ylabel("Withdraw All Profit")
- # plt.savefig(workspace + "withdrawall.svg")
- # plt.close()
- # sections.append('Withdraw All
')
+ # Deposit Section
+ df = deposit_base.sort_values('action_mix')
+ plt.title("Deposit profit")
+ plt.axhline(0, c="black", linewidth=0.4)
+ # for before_mix, rows in df.groupby(df["before_mix"]):
+ plt.plot(
+ df["action_mix"] * 100,
+ df["after_profit"],
+ )
+ # plt.ylim([-1e18, 1e18])
+ plt.xlabel("Pool before deposit")
+ plt.ylabel("Deposit Profit")
+ plt.legend()
+ plt.savefig(workspace + "deposit.svg")
+ plt.close()
+ sections.append('Deposit
')
+
+ # Withdraw Section
+ df = withdraw_base
+ # df = df[df.before_mix != df.after_mix]
+ plt.title("Withdraw profit")
+ plt.axhline(0, c="black", linewidth=0.4)
+ print(df['after_profit'])
+ plt.plot(
+ df["action_mix"] * 100,
+ df["after_profit"],
+ )
+ # plt.ylim([-1e18, 1e18])
+ plt.xlabel("Pool Mix")
+ plt.ylabel("Withdraw Profit")
+ plt.legend()
+ plt.savefig(workspace + "withdraw.svg")
+ plt.close()
+ sections.append('Withdraw
')
+
+ # # Withdraw All
+ df = withdrawall_base
+ plt.axhline(0, c="black", linewidth=0.4)
+ #plt.scatter(df["action_mix"]/1.5+0.5, df["after_profit"])
+ plt.scatter(df["action_mix"] * 100, df["after_profit"])
+ plt.scatter(df["before_mix"], df["after_profit"])
+ plt.plot(df["before_mix"], df["after_profit"])
+ # plt.ylim([-1e18,1e18])
+ plt.xlabel("Pool Mix")
+ plt.ylabel("Withdraw All Profit")
+ plt.savefig(workspace + "withdrawall.svg")
+ plt.close()
+ sections.append('Withdraw All
')
template = """
@@ -627,5 +786,5 @@ def run_report(strategy_name):
with open(filename, "w") as f:
f.write(html)
-#run_report("SwapxOsWS")
-#run_complete("SwapxOsWS")
\ No newline at end of file
+#run_report("RoosterWETHOethp")
+run_complete("RoosterWETHOethp")
\ No newline at end of file
diff --git a/brownie/world_plume.py b/brownie/world_plume.py
index 5cd70d4f04..4aa72cf56c 100644
--- a/brownie/world_plume.py
+++ b/brownie/world_plume.py
@@ -14,6 +14,5 @@
woeth_strat = load_contract('woeth_strategy', OETHP_WOETH_STRATEGY)
+oethpWETHpool = load_contract('maverick_v2_pool', "0x3F86B564A9B530207876d2752948268b9Bf04F71")
plume_woeth_omnichain_adapter = load_contract('omnichain_l2_adapter', PLUME_WOETH_OMNICHAIN_ADAPTER)
-
-oethp = load_contract('ERC20', OETHP)
diff --git a/contracts/contracts/interfaces/plume/IFeeRegistry.sol b/contracts/contracts/interfaces/plume/IFeeRegistry.sol
new file mode 100644
index 0000000000..93dc2eb42e
--- /dev/null
+++ b/contracts/contracts/interfaces/plume/IFeeRegistry.sol
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// As the copyright holder of this work, Ubiquity Labs retains
+// the right to distribute, use, and modify this code under any license of
+// their choosing, in addition to the terms of the GPL-v2 or later.
+pragma solidity ^0.8.25;
+
+interface IFeeRegistry {
+ function registerFee(
+ bool isTokenA,
+ uint32 binId,
+ uint256 binFeeInQuote
+ ) external;
+}
diff --git a/contracts/contracts/interfaces/plume/ILiquidityRegistry.sol b/contracts/contracts/interfaces/plume/ILiquidityRegistry.sol
new file mode 100644
index 0000000000..65e9c9eeb3
--- /dev/null
+++ b/contracts/contracts/interfaces/plume/ILiquidityRegistry.sol
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// As the copyright holder of this work, Ubiquity Labs retains
+// the right to distribute, use, and modify this code under any license of
+// their choosing, in addition to the terms of the GPL-v2 or later.
+pragma solidity ^0.8.25;
+
+import { IMaverickV2Pool } from "./IMaverickV2Pool.sol";
+
+interface ILiquidityRegistry {
+ function notifyBinLiquidity(
+ IMaverickV2Pool pool,
+ uint256 tokenId,
+ uint32 binId,
+ uint256 currentBinLpBalance
+ ) external;
+}
diff --git a/contracts/contracts/interfaces/plume/IMaverickV2Factory.sol b/contracts/contracts/interfaces/plume/IMaverickV2Factory.sol
new file mode 100644
index 0000000000..9003c224e9
--- /dev/null
+++ b/contracts/contracts/interfaces/plume/IMaverickV2Factory.sol
@@ -0,0 +1,212 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// As the copyright holder of this work, Ubiquity Labs retains
+// the right to distribute, use, and modify this code under any license of
+// their choosing, in addition to the terms of the GPL-v2 or later.
+pragma solidity ^0.8.25;
+
+import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+
+import { IFeeRegistry } from "./IFeeRegistry.sol";
+import { IMaverickV2Pool } from "./IMaverickV2Pool.sol";
+
+interface IMaverickV2Factory {
+ error FactorAlreadyInitialized();
+ error FactorNotInitialized();
+ error FactoryInvalidTokenOrder(IERC20 _tokenA, IERC20 _tokenB);
+ error FactoryInvalidFee();
+ error FactoryInvalidKinds(uint8 kinds);
+ error FactoryInvalidTickSpacing(uint256 tickSpacing);
+ error FactoryInvalidLookback(uint256 lookback);
+ error FactoryInvalidTokenDecimals(uint8 decimalsA, uint8 decimalsB);
+ error FactoryPoolAlreadyExists(
+ uint256 feeAIn,
+ uint256 feeBIn,
+ uint256 tickSpacing,
+ uint256 lookback,
+ IERC20 tokenA,
+ IERC20 tokenB,
+ uint8 kinds,
+ address accessor
+ );
+ error FactoryAccessorMustBeNonZero();
+ error NotImplemented();
+
+ event PoolCreated(
+ IMaverickV2Pool poolAddress,
+ uint8 protocolFeeRatio,
+ uint256 feeAIn,
+ uint256 feeBIn,
+ uint256 tickSpacing,
+ uint256 lookback,
+ int32 activeTick,
+ IERC20 tokenA,
+ IERC20 tokenB,
+ uint8 kinds,
+ address accessor
+ );
+ event SetFactoryProtocolFeeReceiver(address receiver);
+ event SetFactoryProtocolFeeRegistry(IFeeRegistry registry);
+
+ struct DeployParameters {
+ uint64 feeAIn;
+ uint64 feeBIn;
+ uint32 lookback;
+ int32 activeTick;
+ uint64 tokenAScale;
+ uint64 tokenBScale;
+ // slot
+ IERC20 tokenA;
+ // slot
+ IERC20 tokenB;
+ // slot
+ uint16 tickSpacing;
+ uint8 options;
+ address accessor;
+ }
+
+ /**
+ * @notice Called by deployer library to initialize a pool.
+ */
+ function deployParameters()
+ external
+ view
+ returns (
+ uint64 feeAIn,
+ uint64 feeBIn,
+ uint32 lookback,
+ int32 activeTick,
+ uint64 tokenAScale,
+ uint64 tokenBScale,
+ // slot
+ IERC20 tokenA,
+ // slot
+ IERC20 tokenB,
+ // slot
+ uint16 tickSpacing,
+ uint8 options,
+ address accessor
+ );
+
+ /**
+ * @notice Create a new MaverickV2Pool with symmetric swap fees.
+ * @param fee Fraction of the pool swap amount that is retained as an LP in
+ * D18 scale.
+ * @param tickSpacing Tick spacing of pool where 1.0001^tickSpacing is the
+ * bin width.
+ * @param lookback Pool lookback in seconds.
+ * @param tokenA Address of tokenA.
+ * @param tokenB Address of tokenB.
+ * @param activeTick Tick position that contains the active bins.
+ * @param kinds 1-15 number to represent the active kinds
+ * 0b0001 = static;
+ * 0b0010 = right;
+ * 0b0100 = left;
+ * 0b1000 = both.
+ * E.g. a pool with all 4 modes will have kinds = b1111 = 15
+ */
+ function create(
+ uint64 fee,
+ uint16 tickSpacing,
+ uint32 lookback,
+ IERC20 tokenA,
+ IERC20 tokenB,
+ int32 activeTick,
+ uint8 kinds
+ ) external returns (IMaverickV2Pool);
+
+ /**
+ * @notice Create a new MaverickV2Pool.
+ * @param feeAIn Fraction of the pool swap amount for tokenA-input swaps
+ * that is retained as an LP in D18 scale.
+ * @param feeBIn Fraction of the pool swap amount for tokenB-input swaps
+ * that is retained as an LP in D18 scale.
+ * @param tickSpacing Tick spacing of pool where 1.0001^tickSpacing is the
+ * bin width.
+ * @param lookback Pool lookback in seconds.
+ * @param tokenA Address of tokenA.
+ * @param tokenB Address of tokenB.
+ * @param activeTick Tick position that contains the active bins.
+ * @param kinds 1-15 number to represent the active kinds
+ * 0b0001 = static;
+ * 0b0010 = right;
+ * 0b0100 = left;
+ * 0b1000 = both.
+ * e.g. a pool with all 4 modes will have kinds = b1111 = 15
+ */
+ function create(
+ uint64 feeAIn,
+ uint64 feeBIn,
+ uint16 tickSpacing,
+ uint32 lookback,
+ IERC20 tokenA,
+ IERC20 tokenB,
+ int32 activeTick,
+ uint8 kinds
+ ) external returns (IMaverickV2Pool);
+
+ /**
+ * @notice Bool indicating whether the pool was deployed from this factory.
+ */
+ function isFactoryPool(IMaverickV2Pool pool) external view returns (bool);
+
+ /**
+ * @notice Address that receives the protocol fee
+ */
+ function protocolFeeReceiver() external view returns (address);
+
+ /**
+ * @notice Address notified on swaps of the protocol fee
+ */
+ function protocolFeeRegistry() external view returns (IFeeRegistry);
+
+ /**
+ * @notice Lookup a pool for given parameters.
+ */
+ function lookup(
+ uint256 feeAIn,
+ uint256 feeBIn,
+ uint256 tickSpacing,
+ uint256 lookback,
+ IERC20 tokenA,
+ IERC20 tokenB,
+ uint8 kinds
+ ) external view returns (IMaverickV2Pool);
+
+ /**
+ * @notice Lookup a pool for given parameters.
+ */
+ function lookup(
+ IERC20 _tokenA,
+ IERC20 _tokenB,
+ uint256 startIndex,
+ uint256 endIndex
+ ) external view returns (IMaverickV2Pool[] memory pools);
+
+ /**
+ * @notice Lookup a pool for given parameters.
+ */
+ function lookup(uint256 startIndex, uint256 endIndex)
+ external
+ view
+ returns (IMaverickV2Pool[] memory pools);
+
+ /**
+ * @notice Count of permissionless pools.
+ */
+ function poolCount() external view returns (uint256 _poolCount);
+
+ /**
+ * @notice Count of pools for a given accessor and token pair. For
+ * permissionless pools, pass `accessor = address(0)`.
+ */
+ function poolByTokenCount(
+ IERC20 _tokenA,
+ IERC20 _tokenB,
+ address accessor
+ ) external view returns (uint256 _poolCount);
+
+ /**
+ * @notice Get the current factory owner.
+ */
+ function owner() external view returns (address);
+}
diff --git a/contracts/contracts/interfaces/plume/IMaverickV2LiquidityManager.sol b/contracts/contracts/interfaces/plume/IMaverickV2LiquidityManager.sol
new file mode 100644
index 0000000000..5f6b40854d
--- /dev/null
+++ b/contracts/contracts/interfaces/plume/IMaverickV2LiquidityManager.sol
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+pragma solidity ^0.8.25;
+
+import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import { IMaverickV2Pool } from "./IMaverickV2Pool.sol";
+
+import { IMaverickV2Position } from "./IMaverickV2Position.sol";
+import { IMaverickV2PoolLens } from "./IMaverickV2PoolLens.sol";
+
+interface IMaverickV2LiquidityManager {
+ error LiquidityManagerNotFactoryPool();
+ error LiquidityManagerNotTokenIdOwner();
+
+ /**
+ * @notice Maverick V2 NFT position contract that tracks NFT-based
+ * liquditiy positions.
+ */
+ function position() external view returns (IMaverickV2Position);
+
+ /**
+ * @notice Create Maverick V2 pool. Function is a pass through to the pool
+ * factory and is provided here so that is can be assembled as part of a
+ * multicall transaction.
+ */
+ function createPool(
+ uint64 fee,
+ uint16 tickSpacing,
+ uint32 lookback,
+ IERC20 tokenA,
+ IERC20 tokenB,
+ int32 activeTick,
+ uint8 kinds
+ ) external payable returns (IMaverickV2Pool pool);
+
+ /**
+ * @notice Add Liquidity position NFT for msg.sender by specifying
+ * msg.sender's token index.
+ * @dev Token index is different from tokenId.
+ * On the Position NFT contract a user can own multiple NFT tokenIds and
+ * these are indexes by an enumeration index which is the `index` input
+ * here.
+ *
+ * See addLiquidity for a description of the add params.
+ */
+ function addPositionLiquidityToSenderByTokenIndex(
+ IMaverickV2Pool pool,
+ uint256 index,
+ bytes memory packedSqrtPriceBreaks,
+ bytes[] memory packedArgs
+ )
+ external
+ payable
+ returns (
+ uint256 tokenAAmount,
+ uint256 tokenBAmount,
+ uint32[] memory binIds
+ );
+
+ /**
+ * @notice Mint new tokenId in the Position NFt contract to msg.sender.
+ * Both mints an NFT and adds liquidity to the pool that is held by the
+ * NFT.
+ */
+ function mintPositionNftToSender(
+ IMaverickV2Pool pool,
+ bytes calldata packedSqrtPriceBreaks,
+ bytes[] calldata packedArgs
+ )
+ external
+ payable
+ returns (
+ uint256 tokenAAmount,
+ uint256 tokenBAmount,
+ uint32[] memory binIds,
+ uint256 tokenId
+ );
+
+ /**
+ * @notice Donates liqudity to a pool that is held by the position contract
+ * and will never be retrievable. Can be used to start a pool and ensure
+ * there will always be a base level of liquditiy in the pool.
+ */
+ function donateLiquidity(
+ IMaverickV2Pool pool,
+ IMaverickV2Pool.AddLiquidityParams memory args
+ ) external payable;
+
+ /**
+ * @notice Packs sqrtPrice breaks array with this format: [length,
+ * array[0], array[1],..., array[length-1]] where length is 1 byte.
+ */
+ function packUint88Array(uint88[] memory fullArray)
+ external
+ pure
+ returns (bytes memory packedArray);
+
+ /**
+ * @notice Packs addLiquidity paramters array element-wise.
+ */
+ function packAddLiquidityArgsArray(
+ IMaverickV2Pool.AddLiquidityParams[] memory args
+ ) external pure returns (bytes[] memory argsPacked);
+}
diff --git a/contracts/contracts/interfaces/plume/IMaverickV2Pool.sol b/contracts/contracts/interfaces/plume/IMaverickV2Pool.sol
new file mode 100644
index 0000000000..3f9d4eafbd
--- /dev/null
+++ b/contracts/contracts/interfaces/plume/IMaverickV2Pool.sol
@@ -0,0 +1,440 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// As the copyright holder of this work, Ubiquity Labs retains
+// the right to distribute, use, and modify this code under any license of
+// their choosing, in addition to the terms of the GPL-v2 or later.
+pragma solidity ^0.8.0;
+
+import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import { IMaverickV2Factory } from "./IMaverickV2Factory.sol";
+
+interface IMaverickV2Pool {
+ error PoolZeroLiquidityAdded();
+ error PoolMinimumLiquidityNotMet();
+ error PoolLocked();
+ error PoolInvalidFee();
+ error PoolTicksNotSorted(uint256 index, int256 previousTick, int256 tick);
+ error PoolTicksAmountsLengthMismatch(
+ uint256 ticksLength,
+ uint256 amountsLength
+ );
+ error PoolBinIdsAmountsLengthMismatch(
+ uint256 binIdsLength,
+ uint256 amountsLength
+ );
+ error PoolKindNotSupported(uint256 kinds, uint256 kind);
+ error PoolInsufficientBalance(
+ uint256 deltaLpAmount,
+ uint256 accountBalance
+ );
+ error PoolReservesExceedMaximum(uint256 amount);
+ error PoolValueExceedsBits(uint256 amount, uint256 bits);
+ error PoolTickMaxExceeded(uint256 tick);
+ error PoolMigrateBinFirst();
+ error PoolCurrentTickBeyondSwapLimit(int32 startingTick);
+ error PoolSenderNotAccessor(address sender_, address accessor);
+ error PoolSenderNotFactory(address sender_, address accessor);
+ error PoolFunctionNotImplemented();
+ error PoolTokenNotSolvent(
+ uint256 internalReserve,
+ uint256 tokenBalance,
+ IERC20 token
+ );
+ error PoolNoProtocolFeeReceiverSet();
+
+ event PoolSwap(
+ address sender,
+ address recipient,
+ SwapParams params,
+ uint256 amountIn,
+ uint256 amountOut
+ );
+ event PoolFlashLoan(
+ address sender,
+ address recipient,
+ uint256 amountA,
+ uint256 amountB
+ );
+ event PoolProtocolFeeCollected(IERC20 token, uint256 protocolFee);
+
+ event PoolAddLiquidity(
+ address sender,
+ address recipient,
+ uint256 subaccount,
+ AddLiquidityParams params,
+ uint256 tokenAAmount,
+ uint256 tokenBAmount,
+ uint32[] binIds
+ );
+
+ event PoolMigrateBinsUpStack(
+ address sender,
+ uint32 binId,
+ uint32 maxRecursion
+ );
+
+ event PoolRemoveLiquidity(
+ address sender,
+ address recipient,
+ uint256 subaccount,
+ RemoveLiquidityParams params,
+ uint256 tokenAOut,
+ uint256 tokenBOut
+ );
+
+ event PoolTickState(int32 tick, uint256 reserveA, uint256 reserveB);
+ event PoolTickBinUpdate(int32 tick, uint8 kind, uint32 binId);
+ event PoolSqrtPrice(uint256 sqrtPrice);
+
+ /**
+ * @notice Tick state parameters.
+ */
+ struct TickState {
+ uint128 reserveA;
+ uint128 reserveB;
+ uint128 totalSupply;
+ uint32[4] binIdsByTick;
+ }
+
+ /**
+ * @notice Tick data parameters.
+ * @param currentReserveA Current reserve of token A.
+ * @param currentReserveB Current reserve of token B.
+ * @param currentLiquidity Current liquidity amount.
+ */
+ struct TickData {
+ uint256 currentReserveA;
+ uint256 currentReserveB;
+ uint256 currentLiquidity;
+ }
+
+ /**
+ * @notice Bin state parameters.
+ * @param mergeBinBalance LP token balance that this bin possesses of the merge bin.
+ * @param mergeId Bin ID of the bin that this bin has merged into.
+ * @param totalSupply Total amount of LP tokens in this bin.
+ * @param kind One of the 4 kinds (0=static, 1=right, 2=left, 3=both).
+ * @param tick The lower price tick of the bin in its current state.
+ * @param tickBalance Balance of the tick.
+ */
+ struct BinState {
+ uint128 mergeBinBalance;
+ uint128 tickBalance;
+ uint128 totalSupply;
+ uint8 kind;
+ int32 tick;
+ uint32 mergeId;
+ }
+
+ /**
+ * @notice Parameters for swap.
+ * @param amount Amount of the token that is either the input if exactOutput is false
+ * or the output if exactOutput is true.
+ * @param tokenAIn Boolean indicating whether tokenA is the input.
+ * @param exactOutput Boolean indicating whether the amount specified is
+ * the exact output amount (true).
+ * @param tickLimit The furthest tick a swap will execute in. If no limit
+ * is desired, value should be set to type(int32).max for a tokenAIn swap
+ * and type(int32).min for a swap where tokenB is the input.
+ */
+ struct SwapParams {
+ uint256 amount;
+ bool tokenAIn;
+ bool exactOutput;
+ int32 tickLimit;
+ }
+
+ /**
+ * @notice Parameters associated with adding liquidity.
+ * @param kind One of the 4 kinds (0=static, 1=right, 2=left, 3=both).
+ * @param ticks Array of ticks to add liquidity to.
+ * @param amounts Array of bin LP amounts to add.
+ */
+ struct AddLiquidityParams {
+ uint8 kind;
+ int32[] ticks;
+ uint128[] amounts;
+ }
+
+ /**
+ * @notice Parameters for each bin that will have liquidity removed.
+ * @param binIds Index array of the bins losing liquidity.
+ * @param amounts Array of bin LP amounts to remove.
+ */
+ struct RemoveLiquidityParams {
+ uint32[] binIds;
+ uint128[] amounts;
+ }
+
+ /**
+ * @notice State of the pool.
+ * @param reserveA Pool tokenA balanceOf at end of last operation
+ * @param reserveB Pool tokenB balanceOf at end of last operation
+ * @param lastTwaD8 Value of log time weighted average price at last block.
+ * Value is 8-decimal scale and is in the fractional tick domain. E.g. a
+ * value of 12.3e8 indicates the TWAP was 3/10ths of the way into the 12th
+ * tick.
+ * @param lastLogPriceD8 Value of log price at last block. Value is
+ * 8-decimal scale and is in the fractional tick domain. E.g. a value of
+ * 12.3e8 indicates the price was 3/10ths of the way into the 12th tick.
+ * @param lastTimestamp Last block.timestamp value in seconds for latest
+ * swap transaction.
+ * @param activeTick Current tick position that contains the active bins.
+ * @param isLocked Pool isLocked, E.g., locked or unlocked; isLocked values
+ * defined in Pool.sol.
+ * @param binCounter Index of the last bin created.
+ * @param protocolFeeRatioD3 Ratio of the swap fee that is kept for the
+ * protocol.
+ */
+ struct State {
+ uint128 reserveA;
+ uint128 reserveB;
+ int64 lastTwaD8;
+ int64 lastLogPriceD8;
+ uint40 lastTimestamp;
+ int32 activeTick;
+ bool isLocked;
+ uint32 binCounter;
+ uint8 protocolFeeRatioD3;
+ }
+
+ /**
+ * @notice Internal data used for data passing between Pool and Bin code.
+ */
+ struct BinDelta {
+ uint128 deltaA;
+ uint128 deltaB;
+ }
+
+ /**
+ * @notice 1-15 number to represent the active kinds.
+ * @notice 0b0001 = static;
+ * @notice 0b0010 = right;
+ * @notice 0b0100 = left;
+ * @notice 0b1000 = both;
+ *
+ * E.g. a pool with all 4 modes will have kinds = b1111 = 15
+ */
+ function kinds() external view returns (uint8 _kinds);
+
+ /**
+ * @notice Pool swap fee for the given direction (A-in or B-in swap) in
+ * 18-decimal format. E.g. 0.01e18 is a 1% swap fee.
+ */
+ function fee(bool tokenAIn) external view returns (uint256);
+
+ /**
+ * @notice TickSpacing of pool where 1.0001^tickSpacing is the bin width.
+ */
+ function tickSpacing() external view returns (uint256);
+
+ /**
+ * @notice Lookback period of pool in seconds.
+ */
+ function lookback() external view returns (uint256);
+
+ /**
+ * @notice Address of Pool accessor. This is Zero address for
+ * permissionless pools.
+ */
+ function accessor() external view returns (address);
+
+ /**
+ * @notice Pool tokenA. Address of tokenA is such that tokenA < tokenB.
+ */
+ function tokenA() external view returns (IERC20);
+
+ /**
+ * @notice Pool tokenB.
+ */
+ function tokenB() external view returns (IERC20);
+
+ /**
+ * @notice Deploying factory of the pool and also contract that has ability
+ * to set and collect protocol fees for the pool.
+ */
+ function factory() external view returns (IMaverickV2Factory);
+
+ /**
+ * @notice Most significant bit of scale value is a flag to indicate whether
+ * tokenA has more or less than 18 decimals. Scale is used in conjuction
+ * with Math.toScale/Math.fromScale functions to convert from token amounts
+ * to D18 scale internal pool accounting.
+ */
+ function tokenAScale() external view returns (uint256);
+
+ /**
+ * @notice Most significant bit of scale value is a flag to indicate whether
+ * tokenA has more or less than 18 decimals. Scale is used in conjuction
+ * with Math.toScale/Math.fromScale functions to convert from token amounts
+ * to D18 scale internal pool accounting.
+ */
+ function tokenBScale() external view returns (uint256);
+
+ /**
+ * @notice ID of bin at input tick position and kind.
+ */
+ function binIdByTickKind(int32 tick, uint256 kind)
+ external
+ view
+ returns (uint32);
+
+ /**
+ * @notice Accumulated tokenA protocol fee.
+ */
+ function protocolFeeA() external view returns (uint128);
+
+ /**
+ * @notice Accumulated tokenB protocol fee.
+ */
+ function protocolFeeB() external view returns (uint128);
+
+ /**
+ * @notice Lending fee rate on flash loans.
+ */
+ function lendingFeeRateD18() external view returns (uint256);
+
+ /**
+ * @notice External function to get the current time-weighted average price.
+ */
+ function getCurrentTwa() external view returns (int256);
+
+ /**
+ * @notice External function to get the state of the pool.
+ */
+ function getState() external view returns (State memory);
+
+ /**
+ * @notice Return state of Bin at input binId.
+ */
+ function getBin(uint32 binId) external view returns (BinState memory bin);
+
+ /**
+ * @notice Return state of Tick at input tick position.
+ */
+ function getTick(int32 tick)
+ external
+ view
+ returns (TickState memory tickState);
+
+ /**
+ * @notice Retrieves the balance of a user within a bin.
+ * @param user The user's address.
+ * @param subaccount The subaccount for the user.
+ * @param binId The ID of the bin.
+ */
+ function balanceOf(
+ address user,
+ uint256 subaccount,
+ uint32 binId
+ ) external view returns (uint128 lpToken);
+
+ /**
+ * @notice Add liquidity to a pool. This function allows users to deposit
+ * tokens into a liquidity pool.
+ * @dev This function will call `maverickV2AddLiquidityCallback` on the
+ * calling contract to collect the tokenA/tokenB payment.
+ * @param recipient The account that will receive credit for the added liquidity.
+ * @param subaccount The account that will receive credit for the added liquidity.
+ * @param params Parameters containing the details for adding liquidity,
+ * such as token types and amounts.
+ * @param data Bytes information that gets passed to the callback.
+ * @return tokenAAmount The amount of token A added to the pool.
+ * @return tokenBAmount The amount of token B added to the pool.
+ * @return binIds An array of bin IDs where the liquidity is stored.
+ */
+ function addLiquidity(
+ address recipient,
+ uint256 subaccount,
+ AddLiquidityParams calldata params,
+ bytes calldata data
+ )
+ external
+ returns (
+ uint256 tokenAAmount,
+ uint256 tokenBAmount,
+ uint32[] memory binIds
+ );
+
+ /**
+ * @notice Removes liquidity from the pool.
+ * @dev Liquidy can only be removed from a bin that is either unmerged or
+ * has a mergeId of an unmerged bin. If a bin is merged more than one
+ * level deep, it must be migrated up the merge stack to the root bin
+ * before liquidity removal.
+ * @param recipient The address to receive the tokens.
+ * @param subaccount The subaccount for the recipient.
+ * @param params The parameters for removing liquidity.
+ * @return tokenAOut The amount of token A received.
+ * @return tokenBOut The amount of token B received.
+ */
+ function removeLiquidity(
+ address recipient,
+ uint256 subaccount,
+ RemoveLiquidityParams calldata params
+ ) external returns (uint256 tokenAOut, uint256 tokenBOut);
+
+ /**
+ * @notice Migrate bins up the linked list of merged bins so that its
+ * mergeId is the currrent active bin.
+ * @dev Liquidy can only be removed from a bin that is either unmerged or
+ * has a mergeId of an unmerged bin. If a bin is merged more than one
+ * level deep, it must be migrated up the merge stack to the root bin
+ * before liquidity removal.
+ * @param binId The ID of the bin to migrate.
+ * @param maxRecursion The maximum recursion depth for the migration.
+ */
+ function migrateBinUpStack(uint32 binId, uint32 maxRecursion) external;
+
+ /**
+ * @notice Swap tokenA/tokenB assets in the pool. The swap user has two
+ * options for funding their swap.
+ * - The user can push the input token amount to the pool before calling
+ * the swap function. In order to avoid having the pool call the callback,
+ * the user should pass a zero-length `data` bytes object with the swap
+ * call.
+ * - The user can send the input token amount to the pool when the pool
+ * calls the `maverickV2SwapCallback` function on the calling contract.
+ * That callback has input parameters that specify the token address of the
+ * input token, the input and output amounts, and the bytes data sent to
+ * the swap function.
+ * @dev If the users elects to do a callback-based swap, the output
+ * assets will be sent before the callback is called, allowing the user to
+ * execute flash swaps. However, the pool does have reentrancy protection,
+ * so a swapper will not be able to interact with the same pool again
+ * while they are in the callback function.
+ * @param recipient The address to receive the output tokens.
+ * @param params Parameters containing the details of the swap
+ * @param data Bytes information that gets passed to the callback.
+ */
+ function swap(
+ address recipient,
+ SwapParams memory params,
+ bytes calldata data
+ ) external returns (uint256 amountIn, uint256 amountOut);
+
+ /**
+ * @notice Loan tokenA/tokenB assets from the pool to recipient. The fee
+ * rate of a loan is determined by `lendingFeeRateD18`, which is set at the
+ * protocol level by the factory. This function calls
+ * `maverickV2FlashLoanCallback` on the calling contract. At the end of
+ * the callback, the caller must pay back the loan with fee (if there is a
+ * fee).
+ * @param recipient The address to receive the loaned tokens.
+ * @param amountB Loan amount of tokenA sent to recipient.
+ * @param amountB Loan amount of tokenB sent to recipient.
+ * @param data Bytes information that gets passed to the callback.
+ */
+ function flashLoan(
+ address recipient,
+ uint256 amountA,
+ uint256 amountB,
+ bytes calldata data
+ ) external;
+
+ /**
+ * @notice Distributes accumulated protocol fee to factory protocolFeeReceiver
+ */
+ function distributeFees(bool isTokenA)
+ external
+ returns (uint256 protocolFee, IERC20 token);
+}
diff --git a/contracts/contracts/interfaces/plume/IMaverickV2PoolLens.sol b/contracts/contracts/interfaces/plume/IMaverickV2PoolLens.sol
new file mode 100644
index 0000000000..2cca0c7d78
--- /dev/null
+++ b/contracts/contracts/interfaces/plume/IMaverickV2PoolLens.sol
@@ -0,0 +1,306 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+pragma solidity ^0.8.25;
+
+import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import { IMaverickV2Factory } from "./IMaverickV2Factory.sol";
+import { IMaverickV2Pool } from "./IMaverickV2Pool.sol";
+
+interface IMaverickV2PoolLens {
+ error LensTargetPriceOutOfBounds(
+ uint256 targetSqrtPrice,
+ uint256 sqrtLowerTickPrice,
+ uint256 sqrtUpperTickPrice
+ );
+ error LensTooLittleLiquidity(
+ uint256 relativeLiquidityAmount,
+ uint256 deltaA,
+ uint256 deltaB
+ );
+ error LensTargetingTokenWithNoDelta(
+ bool targetIsA,
+ uint256 deltaA,
+ uint256 deltaB
+ );
+
+ /**
+ * @notice Add liquidity slippage parameters for a distribution of liquidity.
+ * @param pool Pool where liquidity is being added.
+ * @param kind Bin kind; all bins must have the same kind in a given call
+ * to addLiquidity.
+ * @param ticks Array of tick values to add liquidity to.
+ * @param relativeLiquidityAmounts Relative liquidity amounts for the
+ * specified ticks. Liquidity in this case is not bin LP balance, it is
+ * the bin liquidity as defined by liquidity = deltaA / (sqrt(upper) -
+ * sqrt(lower)) or deltaB = liquidity / sqrt(lower) - liquidity /
+ * sqrt(upper).
+ * @param addSpec Slippage specification.
+ */
+ struct AddParamsViewInputs {
+ IMaverickV2Pool pool;
+ uint8 kind;
+ int32[] ticks;
+ uint128[] relativeLiquidityAmounts;
+ AddParamsSpecification addSpec;
+ }
+
+ /**
+ * @notice Multi-price add param specification.
+ * @param slippageFactorD18 Max slippage allowed as a percent in D18 scale. e.g. 1% slippage is 0.01e18
+ * @param numberOfPriceBreaksPerSide Number of price break values on either
+ * side of current price.
+ * @param targetAmount Target token contribution amount in tokenA if
+ * targetIsA is true, otherwise this is the target amount for tokenB.
+ * @param targetIsA Indicates if the target amount is for tokenA or tokenB
+ */
+ struct AddParamsSpecification {
+ uint256 slippageFactorD18;
+ uint256 numberOfPriceBreaksPerSide;
+ uint256 targetAmount;
+ bool targetIsA;
+ }
+
+ /**
+ * @notice Specification for deriving create pool parameters. Creating a
+ * pool in the liquidity manager has several steps:
+ *
+ * - Deploy pool
+ * - Donate a small amount of initial liquidity in the activeTick
+ * - Execute a small swap to set the pool price to the desired value
+ * - Add liquidity
+ *
+ * In order to execute these steps, the caller must specify the parameters
+ * of each step. The PoolLens has helper function to derive the values
+ * used by the LiquidityManager, but this struct is the input to that
+ * helper function and represents the core intent of the pool creator.
+ *
+ * @param fee Fraction of the pool swap amount that is retained as an LP in
+ * D18 scale.
+ * @param tickSpacing Tick spacing of pool where 1.0001^tickSpacing is the
+ * bin width.
+ * @param lookback Pool lookback in seconds.
+ * @param tokenA Address of tokenA.
+ * @param tokenB Address of tokenB.
+ * @param activeTick Tick position that contains the active bins.
+ * @param kinds 1-15 number to represent the active kinds
+ * 0b0001 = static;
+ * 0b0010 = right;
+ * 0b0100 = left;
+ * 0b1000 = both.
+ * e.g. a pool with all 4 modes will have kinds = b1111 = 15
+ * @param initialTargetB Amount of B to be donated to the pool after pool
+ * create. This amount needs to be big enough to meet the minimum bin
+ * liquidity.
+ * @param sqrtPrice Target sqrt price of the pool.
+ * @param kind Bin kind; all bins must have the same kind in a given call
+ * to addLiquidity.
+ * @param ticks Array of tick values to add liquidity to.
+ * @param relativeLiquidityAmounts Relative liquidity amounts for the
+ * specified ticks. Liquidity in this case is not bin LP balance, it is
+ * the bin liquidity as defined by liquidity = deltaA / (sqrt(upper) -
+ * sqrt(lower)) or deltaB = liquidity / sqrt(lower) - liquidity /
+ * sqrt(upper).
+ * @param targetAmount Target token contribution amount in tokenA if
+ * targetIsA is true, otherwise this is the target amount for tokenB.
+ * @param targetIsA Indicates if the target amount is for tokenA or tokenB
+ */
+ struct CreateAndAddParamsViewInputs {
+ uint64 feeAIn;
+ uint64 feeBIn;
+ uint16 tickSpacing;
+ uint32 lookback;
+ IERC20 tokenA;
+ IERC20 tokenB;
+ int32 activeTick;
+ uint8 kinds;
+ // donate params
+ uint256 initialTargetB;
+ uint256 sqrtPrice;
+ // add target
+ uint8 kind;
+ int32[] ticks;
+ uint128[] relativeLiquidityAmounts;
+ uint256 targetAmount;
+ bool targetIsA;
+ }
+
+ struct Output {
+ uint256 deltaAOut;
+ uint256 deltaBOut;
+ uint256[] deltaAs;
+ uint256[] deltaBs;
+ uint128[] deltaLpBalances;
+ }
+
+ struct Reserves {
+ uint256 amountA;
+ uint256 amountB;
+ }
+
+ struct BinPositionKinds {
+ uint128[4] values;
+ }
+
+ struct PoolState {
+ IMaverickV2Pool.TickState[] tickStateMapping;
+ IMaverickV2Pool.BinState[] binStateMapping;
+ BinPositionKinds[] binIdByTickKindMapping;
+ IMaverickV2Pool.State state;
+ Reserves protocolFees;
+ }
+
+ struct BoostedPositionSpecification {
+ IMaverickV2Pool pool;
+ uint32[] binIds;
+ uint128[] ratios;
+ uint8 kind;
+ }
+
+ struct CreateAndAddParamsInputs {
+ uint64 feeAIn;
+ uint64 feeBIn;
+ uint16 tickSpacing;
+ uint32 lookback;
+ IERC20 tokenA;
+ IERC20 tokenB;
+ int32 activeTick;
+ uint8 kinds;
+ // donate params
+ IMaverickV2Pool.AddLiquidityParams donateParams;
+ // swap params
+ uint256 swapAmount;
+ // add params
+ IMaverickV2Pool.AddLiquidityParams addParams;
+ bytes[] packedAddParams;
+ uint256 deltaAOut;
+ uint256 deltaBOut;
+ uint256 preAddReserveA;
+ uint256 preAddReserveB;
+ }
+
+ struct TickDeltas {
+ uint256 deltaAOut;
+ uint256 deltaBOut;
+ uint256[] deltaAs;
+ uint256[] deltaBs;
+ }
+
+ /**
+ * @notice Converts add parameter slippage specification into add
+ * parameters. The return values are given in both raw format and as packed
+ * values that can be used in the LiquidityManager contract.
+ */
+ function getAddLiquidityParams(AddParamsViewInputs memory params)
+ external
+ view
+ returns (
+ bytes memory packedSqrtPriceBreaks,
+ bytes[] memory packedArgs,
+ uint88[] memory sqrtPriceBreaks,
+ IMaverickV2Pool.AddLiquidityParams[] memory addParams,
+ IMaverickV2PoolLens.TickDeltas[] memory tickDeltas
+ );
+
+ /**
+ * @notice Converts add parameter slippage specification and new pool
+ * specification into CreateAndAddParamsInputs parameters that can be used in the
+ * LiquidityManager contract.
+ */
+ function getCreatePoolAtPriceAndAddLiquidityParams(
+ CreateAndAddParamsViewInputs memory params
+ ) external view returns (CreateAndAddParamsInputs memory output);
+
+ /**
+ * @notice View function that provides information about pool ticks within
+ * a tick radius from the activeTick. Ticks with no reserves are not
+ * included in part o f the return array.
+ */
+ function getTicksAroundActive(IMaverickV2Pool pool, int32 tickRadius)
+ external
+ view
+ returns (
+ int32[] memory ticks,
+ IMaverickV2Pool.TickState[] memory tickStates
+ );
+
+ /**
+ * @notice View function that provides information about pool ticks within
+ * a range. Ticks with no reserves are not included in part o f the return
+ * array.
+ */
+ function getTicks(
+ IMaverickV2Pool pool,
+ int32 tickStart,
+ int32 tickEnd
+ )
+ external
+ view
+ returns (
+ int32[] memory ticks,
+ IMaverickV2Pool.TickState[] memory tickStates
+ );
+
+ /**
+ * @notice View function that provides information about pool ticks within
+ * a range. Information returned includes all pool state needed to emulate
+ * a swap off chain. Ticks with no reserves are not included in part o f
+ * the return array.
+ */
+ function getTicksAroundActiveWLiquidity(
+ IMaverickV2Pool pool,
+ int32 tickRadius
+ )
+ external
+ view
+ returns (
+ int32[] memory ticks,
+ IMaverickV2Pool.TickState[] memory tickStates,
+ uint256[] memory liquidities,
+ uint256[] memory sqrtLowerTickPrices,
+ uint256[] memory sqrtUpperTickPrices,
+ IMaverickV2Pool.State memory poolState,
+ uint256 sqrtPrice,
+ uint256 feeAIn,
+ uint256 feeBIn
+ );
+
+ /**
+ * @notice View function that provides pool state information.
+ */
+ function getFullPoolState(
+ IMaverickV2Pool pool,
+ uint32 binStart,
+ uint32 binEnd
+ ) external view returns (PoolState memory poolState);
+
+ /**
+ * @notice View function that provides price and liquidity of a given tick.
+ */
+ function getTickSqrtPriceAndL(IMaverickV2Pool pool, int32 tick)
+ external
+ view
+ returns (uint256 sqrtPrice, uint256 liquidity);
+
+ /**
+ * @notice Pool sqrt price.
+ */
+ function getPoolSqrtPrice(IMaverickV2Pool pool)
+ external
+ view
+ returns (uint256 sqrtPrice);
+
+ /**
+ * @notice Pool price.
+ */
+ function getPoolPrice(IMaverickV2Pool pool)
+ external
+ view
+ returns (uint256 price);
+
+ /**
+ * @notice Token scale of two tokens in a pool.
+ */
+ function tokenScales(IMaverickV2Pool pool)
+ external
+ view
+ returns (uint256 tokenAScale, uint256 tokenBScale);
+}
diff --git a/contracts/contracts/interfaces/plume/IMaverickV2Position.sol b/contracts/contracts/interfaces/plume/IMaverickV2Position.sol
new file mode 100644
index 0000000000..7e751b438b
--- /dev/null
+++ b/contracts/contracts/interfaces/plume/IMaverickV2Position.sol
@@ -0,0 +1,175 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+pragma solidity ^0.8.25;
+
+import { IMaverickV2Factory } from "./IMaverickV2Factory.sol";
+import { IMaverickV2Pool } from "./IMaverickV2Pool.sol";
+import { ILiquidityRegistry } from "./ILiquidityRegistry.sol";
+
+interface IMaverickV2Position {
+ event PositionClearData(uint256 indexed tokenId);
+ event PositionSetData(
+ uint256 indexed tokenId,
+ uint256 index,
+ PositionPoolBinIds newData
+ );
+ event SetLpReward(ILiquidityRegistry lpReward);
+
+ error PositionDuplicatePool(uint256 index, IMaverickV2Pool pool);
+ error PositionNotFactoryPool();
+ error PositionPermissionedLiquidityPool();
+
+ struct PositionPoolBinIds {
+ IMaverickV2Pool pool;
+ uint32[] binIds;
+ }
+
+ struct PositionFullInformation {
+ PositionPoolBinIds poolBinIds;
+ uint256 amountA;
+ uint256 amountB;
+ uint256[] binAAmounts;
+ uint256[] binBAmounts;
+ int32[] ticks;
+ uint256[] liquidities;
+ }
+
+ /**
+ * @notice Factory that tracks lp rewards.
+ */
+ function lpReward() external view returns (ILiquidityRegistry);
+
+ /**
+ * @notice Pool factory.
+ */
+ function factory() external view returns (IMaverickV2Factory);
+
+ /**
+ * @notice Mint NFT that holds liquidity in a Maverick V2 Pool. To mint
+ * liquidity to an NFT, add liquidity to bins in a pool where the
+ * add liquidity recipient is this contract and the subaccount is the
+ * tokenId. LiquidityManager can be used to simplify minting Position NFTs.
+ */
+ function mint(
+ address recipient,
+ IMaverickV2Pool pool,
+ uint32[] memory binIds
+ ) external returns (uint256 tokenId);
+
+ /**
+ * @notice Overwrites tokenId pool/binId information for a given data index.
+ */
+ function setTokenIdData(
+ uint256 tokenId,
+ uint256 index,
+ IMaverickV2Pool pool,
+ uint32[] memory binIds
+ ) external;
+
+ /**
+ * @notice Overwrites entire pool/binId data set for a given tokenId.
+ */
+ function setTokenIdData(uint256 tokenId, PositionPoolBinIds[] memory data)
+ external;
+
+ /**
+ * @notice Append new pool/binIds data array to tokenId.
+ */
+ function appendTokenIdData(
+ uint256 tokenId,
+ IMaverickV2Pool pool,
+ uint32[] memory binIds
+ ) external;
+
+ /**
+ * @notice Get array pool/binIds data for a given tokenId.
+ */
+ function getTokenIdData(uint256 tokenId)
+ external
+ view
+ returns (PositionPoolBinIds[] memory);
+
+ /**
+ * @notice Get value from array of pool/binIds data for a given tokenId.
+ */
+ function getTokenIdData(uint256 tokenId, uint256 index)
+ external
+ view
+ returns (PositionPoolBinIds memory);
+
+ /**
+ * @notice Length of array of pool/binIds data for a given tokenId.
+ */
+ function tokenIdDataLength(uint256 tokenId)
+ external
+ view
+ returns (uint256 length);
+
+ /**
+ * @notice Remove liquidity from tokenId for a given pool. User can
+ * specify arbitrary bins to remove from for their subaccount in the pool
+ * even if those bins are not in the tokenIdData set.
+ */
+ function removeLiquidity(
+ uint256 tokenId,
+ address recipient,
+ IMaverickV2Pool pool,
+ IMaverickV2Pool.RemoveLiquidityParams memory params
+ ) external returns (uint256 tokenAAmount, uint256 tokenBAmount);
+
+ /**
+ * @notice Remove liquidity from tokenId for a given pool to sender. User
+ * can specify arbitrary bins to remove from for their subaccount in the
+ * pool even if those bins are not in the tokenIdData set.
+ */
+ function removeLiquidityToSender(
+ uint256 tokenId,
+ IMaverickV2Pool pool,
+ IMaverickV2Pool.RemoveLiquidityParams memory params
+ ) external returns (uint256 tokenAAmount, uint256 tokenBAmount);
+
+ /**
+ * @notice NFT asset information for a given range of pool/binIds indexes.
+ * This function only returns the liquidity in the pools/binIds stored as
+ * part of the tokenIdData, but it is possible that the NFT has additional
+ * liquidity in pools/binIds that have not been recorded.
+ */
+ function tokenIdPositionInformation(
+ uint256 tokenId,
+ uint256 startIndex,
+ uint256 stopIndex
+ ) external view returns (PositionFullInformation[] memory output);
+
+ /**
+ * @notice NFT asset information for a given pool/binIds index. This
+ * function only returns the liquidity in the pools/binIds stored as part
+ * of the tokenIdData, but it is possible that the NFT has additional
+ * liquidity in pools/binIds that have not been recorded.
+ */
+ function tokenIdPositionInformation(uint256 tokenId, uint256 index)
+ external
+ view
+ returns (PositionFullInformation memory output);
+
+ /**
+ * @notice Get remove parameters for removing a fractional part of the
+ * liquidity owned by a given tokenId. The fractional factor to remove is
+ * given by proporationD18 in 18-decimal scale.
+ */
+ function getRemoveParams(
+ uint256 tokenId,
+ uint256 index,
+ uint256 proportionD18
+ )
+ external
+ view
+ returns (IMaverickV2Pool.RemoveLiquidityParams memory params);
+
+ /**
+ * @notice Register the bin balances in the nft with the LpReward contract.
+ */
+ function checkpointBinLpBalance(
+ uint256 tokenId,
+ IMaverickV2Pool pool,
+ uint32[] memory binIds
+ ) external;
+}
diff --git a/contracts/contracts/interfaces/plume/IMaverickV2Quoter.sol b/contracts/contracts/interfaces/plume/IMaverickV2Quoter.sol
new file mode 100644
index 0000000000..8b18506e01
--- /dev/null
+++ b/contracts/contracts/interfaces/plume/IMaverickV2Quoter.sol
@@ -0,0 +1,78 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+pragma solidity ^0.8.25;
+
+import { IMaverickV2Pool } from "./IMaverickV2Pool.sol";
+
+interface IMaverickV2Quoter {
+ error QuoterInvalidSwap();
+ error QuoterInvalidAddLiquidity();
+
+ /**
+ * @notice Calculates a swap on a MaverickV2Pool and returns the resulting
+ * amount and estimated gas. The gas estimate is only a rough estimate and
+ * may not match a swap's gas.
+ * @param pool The MaverickV2Pool to swap on.
+ * @param amount The input amount.
+ * @param tokenAIn Indicates if token A is the input token.
+ * @param exactOutput Indicates if the amount is the output amount (true)
+ * or input amount (false). If the tickLimit is reached, the full value of
+ * the exactOutput may not be returned because the pool will stop swapping
+ * before the whole order is filled.
+ * @param tickLimit The tick limit for the swap. Once the swap lands in
+ * this tick, it will stop and return the output amount swapped up to that
+ * tick.
+ */
+ function calculateSwap(
+ IMaverickV2Pool pool,
+ uint128 amount,
+ bool tokenAIn,
+ bool exactOutput,
+ int32 tickLimit
+ )
+ external
+ returns (
+ uint256 amountIn,
+ uint256 amountOut,
+ uint256 gasEstimate
+ );
+
+ /**
+ * @notice Calculates a multihop swap and returns the resulting amount and
+ * estimated gas. The gas estimate is only a rough estimate and
+ * may not match a swap's gas.
+ * @param path The path of pools to swap through. Path is given by an
+ * packed array of (pool, tokenAIn) tuples. So each step in the path is 160
+ * + 8 = 168 bits of data. e.g. path = abi.encodePacked(pool1, true, pool2, false);
+ * @param amount The input amount.
+ * @param exactOutput A boolean indicating if exact output is required.
+ */
+ function calculateMultiHopSwap(
+ bytes memory path,
+ uint256 amount,
+ bool exactOutput
+ ) external returns (uint256 returnAmount, uint256 gasEstimate);
+
+ /**
+ * @notice Computes the token amounts required for a given set of
+ * addLiquidity parameters. The gas estimate is only a rough estimate and
+ * may not match a add's gas.
+ */
+ function calculateAddLiquidity(
+ IMaverickV2Pool pool,
+ IMaverickV2Pool.AddLiquidityParams calldata params
+ )
+ external
+ returns (
+ uint256 amountA,
+ uint256 amountB,
+ uint256 gasEstimate
+ );
+
+ /**
+ * @notice Pool's sqrt price.
+ */
+ function poolSqrtPrice(IMaverickV2Pool pool)
+ external
+ view
+ returns (uint256 sqrtPrice);
+}
diff --git a/contracts/contracts/interfaces/plume/IPoolDistributor.sol b/contracts/contracts/interfaces/plume/IPoolDistributor.sol
new file mode 100644
index 0000000000..432816a5bc
--- /dev/null
+++ b/contracts/contracts/interfaces/plume/IPoolDistributor.sol
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+pragma solidity ^0.8.0;
+
+import { IMaverickV2Pool } from "./IMaverickV2Pool.sol";
+
+interface IPoolDistributor {
+ function rewardToken() external view returns (address);
+
+ function claimLp(
+ address recipient,
+ uint256 tokenId,
+ IMaverickV2Pool pool,
+ uint32[] memory binIds,
+ uint256 epoch
+ ) external returns (uint256 amount);
+}
diff --git a/contracts/contracts/interfaces/plume/IVotingDistributor.sol b/contracts/contracts/interfaces/plume/IVotingDistributor.sol
new file mode 100644
index 0000000000..e3060b9f0c
--- /dev/null
+++ b/contracts/contracts/interfaces/plume/IVotingDistributor.sol
@@ -0,0 +1,6 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+pragma solidity ^0.8.0;
+
+interface IVotingDistributor {
+ function lastEpoch() external view returns (uint256);
+}
diff --git a/contracts/contracts/mocks/MockMaverickDistributor.sol b/contracts/contracts/mocks/MockMaverickDistributor.sol
new file mode 100644
index 0000000000..e2d5f2df37
--- /dev/null
+++ b/contracts/contracts/mocks/MockMaverickDistributor.sol
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.0;
+
+import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import { IMaverickV2Pool } from "../interfaces/plume/IMaverickV2Pool.sol";
+
+contract MockMaverickDistributor {
+ IERC20 public immutable rewardToken;
+
+ uint256 public lastEpoch;
+ uint256 public rewardAmount;
+
+ constructor(address _rewardToken) {
+ rewardToken = IERC20(_rewardToken);
+ }
+
+ function setLastEpoch(uint256 _epoch) external {
+ lastEpoch = _epoch;
+ }
+
+ function setRewardTokenAmount(uint256 _amount) external {
+ rewardAmount = _amount;
+ }
+
+ function claimLp(
+ address recipient,
+ uint256,
+ IMaverickV2Pool,
+ uint32[] memory,
+ uint256
+ ) external returns (uint256 amount) {
+ rewardToken.transfer(recipient, rewardAmount);
+ return rewardAmount;
+ }
+}
diff --git a/contracts/contracts/mocks/MockRoosterAMOStrategy.sol b/contracts/contracts/mocks/MockRoosterAMOStrategy.sol
new file mode 100644
index 0000000000..46627a7342
--- /dev/null
+++ b/contracts/contracts/mocks/MockRoosterAMOStrategy.sol
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.0;
+
+/**
+ * @title Rooster AMO strategy exposing extra functionality
+ * @author Origin Protocol Inc
+ */
+
+import { RoosterAMOStrategy } from "../strategies/plume/RoosterAMOStrategy.sol";
+import { IMaverickV2Pool } from "../interfaces/plume/IMaverickV2Pool.sol";
+
+contract MockRoosterAMOStrategy is RoosterAMOStrategy {
+ constructor(
+ BaseStrategyConfig memory _stratConfig,
+ address _wethAddress,
+ address _oethpAddress,
+ address _liquidityManager,
+ address _poolLens,
+ address _maverickPosition,
+ address _maverickQuoter,
+ address _mPool,
+ bool _upperTickAtParity,
+ address _votingDistributor,
+ address _poolDistributor
+ )
+ RoosterAMOStrategy(
+ _stratConfig,
+ _wethAddress,
+ _oethpAddress,
+ _liquidityManager,
+ _poolLens,
+ _maverickPosition,
+ _maverickQuoter,
+ _mPool,
+ _upperTickAtParity,
+ _votingDistributor,
+ _poolDistributor
+ )
+ {}
+
+ function getCurrentWethShare() external view returns (uint256) {
+ uint256 _currentPrice = getPoolSqrtPrice();
+
+ return _getWethShare(_currentPrice);
+ }
+}
diff --git a/contracts/contracts/proxies/PlumeProxies.sol b/contracts/contracts/proxies/PlumeProxies.sol
index f8a77b0dcc..49ec1f599e 100644
--- a/contracts/contracts/proxies/PlumeProxies.sol
+++ b/contracts/contracts/proxies/PlumeProxies.sol
@@ -23,3 +23,10 @@ contract OETHPlumeProxy is InitializeGovernedUpgradeabilityProxy {
contract WOETHPlumeProxy is InitializeGovernedUpgradeabilityProxy {
}
+
+/**
+ * @notice RoosterAMOStrategyProxy delegates calls to a RoosterAMOStrategy implementation
+ */
+contract RoosterAMOStrategyProxy is InitializeGovernedUpgradeabilityProxy {
+
+}
diff --git a/contracts/contracts/strategies/BridgedWOETHStrategy.sol b/contracts/contracts/strategies/BridgedWOETHStrategy.sol
index f17c0c3d97..1f602318d0 100644
--- a/contracts/contracts/strategies/BridgedWOETHStrategy.sol
+++ b/contracts/contracts/strategies/BridgedWOETHStrategy.sol
@@ -27,17 +27,6 @@ contract BridgedWOETHStrategy is InitializableAbstractStrategy {
uint128 public lastOraclePrice;
uint128 public maxPriceDiffBps;
- /**
- * @dev Verifies that the caller is the Governor or Strategist.
- */
- modifier onlyGovernorOrStrategist() {
- require(
- isGovernor() || msg.sender == IVault(vaultAddress).strategistAddr(),
- "Caller is not the Strategist or Governor"
- );
- _;
- }
-
constructor(
BaseStrategyConfig memory _stratConfig,
address _weth,
diff --git a/contracts/contracts/strategies/aerodrome/AerodromeAMOStrategy.sol b/contracts/contracts/strategies/aerodrome/AerodromeAMOStrategy.sol
index 48d1a69b08..15472dc243 100644
--- a/contracts/contracts/strategies/aerodrome/AerodromeAMOStrategy.sol
+++ b/contracts/contracts/strategies/aerodrome/AerodromeAMOStrategy.sol
@@ -137,18 +137,6 @@ contract AerodromeAMOStrategy is InitializableAbstractStrategy {
event UnderlyingAssetsUpdated(uint256 underlyingAssets);
- /**
- * @dev Verifies that the caller is the Governor, or Strategist.
- */
- modifier onlyGovernorOrStrategist() {
- require(
- msg.sender == IVault(vaultAddress).strategistAddr() ||
- msg.sender == governor(),
- "Not the Governor or Strategist"
- );
- _;
- }
-
/**
* @dev Un-stakes the token from the gauge for the execution duration of
* the function and after that re-stakes it back in.
diff --git a/contracts/contracts/strategies/plume/RoosterAMOStrategy.sol b/contracts/contracts/strategies/plume/RoosterAMOStrategy.sol
new file mode 100644
index 0000000000..2f4190b96e
--- /dev/null
+++ b/contracts/contracts/strategies/plume/RoosterAMOStrategy.sol
@@ -0,0 +1,1204 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.0;
+
+/**
+ * @title Rooster AMO strategy
+ * @author Origin Protocol Inc
+ * @custom:security-contact security@originprotocol.com
+ */
+import { Math as MathRooster } from "../../../lib/rooster/v2-common/libraries/Math.sol";
+import { Math as Math_v5 } from "../../../lib/rooster/openzeppelin-custom/contracts/utils/math/Math.sol";
+import { StableMath } from "../../utils/StableMath.sol";
+
+import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
+import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
+
+import { IERC20, InitializableAbstractStrategy } from "../../utils/InitializableAbstractStrategy.sol";
+import { IVault } from "../../interfaces/IVault.sol";
+import { IMaverickV2Pool } from "../../interfaces/plume/IMaverickV2Pool.sol";
+import { IMaverickV2Quoter } from "../../interfaces/plume/IMaverickV2Quoter.sol";
+import { IMaverickV2LiquidityManager } from "../../interfaces/plume/IMaverickV2LiquidityManager.sol";
+import { IMaverickV2PoolLens } from "../../interfaces/plume/IMaverickV2PoolLens.sol";
+import { IMaverickV2Position } from "../../interfaces/plume/IMaverickV2Position.sol";
+import { IVotingDistributor } from "../../interfaces/plume/IVotingDistributor.sol";
+import { IPoolDistributor } from "../../interfaces/plume/IPoolDistributor.sol";
+// importing custom version of rooster TickMath because of dependency collision. Maverick uses
+// a newer OpenZepplin Math library with functionality that is not present in 4.4.2 (the one we use)
+import { TickMath } from "../../../lib/rooster/v2-common/libraries/TickMath.sol";
+
+contract RoosterAMOStrategy is InitializableAbstractStrategy {
+ using StableMath for uint256;
+ using SafeERC20 for IERC20;
+ using SafeCast for uint256;
+
+ /***************************************
+ Storage slot members
+ ****************************************/
+
+ /// @notice NFT tokenId of the liquidity position
+ ///
+ /// @dev starts with value of 1 and can not be 0
+ // solhint-disable-next-line max-line-length
+ /// https://github.com/rooster-protocol/rooster-contracts/blob/fbfecbc519e4495b12598024a42630b4a8ea4489/v2-common/contracts/base/Nft.sol#L14
+ uint256 public tokenId;
+ /// @dev Minimum amount of tokens the strategy would be able to withdraw from the pool.
+ /// minimum amount of tokens are withdrawn at a 1:1 price
+ /// Important: Underlying assets contains only assets that are deposited in the underlying Rooster pool.
+ /// WETH or OETH held by this contract is not accounted for in underlying assets
+ uint256 public underlyingAssets;
+ /// @notice Marks the start of the interval that defines the allowed range of WETH share in
+ /// the pre-configured pool's liquidity ticker
+ uint256 public allowedWethShareStart;
+ /// @notice Marks the end of the interval that defines the allowed range of WETH share in
+ /// the pre-configured pool's liquidity ticker
+ uint256 public allowedWethShareEnd;
+ /// @dev reserved for inheritance
+ int256[46] private __reserved;
+
+ /***************************************
+ Constants, structs and events
+ ****************************************/
+
+ /// @notice The address of the Wrapped ETH (WETH) token contract
+ address public immutable WETH;
+ /// @notice The address of the OETH token contract
+ address public immutable OETH;
+ /// @notice the underlying AMO Maverick (Rooster) pool
+ IMaverickV2Pool public immutable mPool;
+ /// @notice the Liquidity manager used to add liquidity to the pool
+ IMaverickV2LiquidityManager public immutable liquidityManager;
+ /// @notice the Maverick V2 poolLens
+ ///
+ /// @dev only used to provide the pool's current sqrtPrice
+ IMaverickV2PoolLens public immutable poolLens;
+ /// @notice the Maverick V2 position
+ ///
+ /// @dev provides details of the NFT LP position and offers functions to
+ /// remove the liquidity.
+ IMaverickV2Position public immutable maverickPosition;
+ /// @notice the Maverick Quoter
+ IMaverickV2Quoter public immutable quoter;
+ /// @notice the Maverick Voting Distributor
+ IVotingDistributor public immutable votingDistributor;
+ /// @notice the Maverick Pool Distributor
+ IPoolDistributor public immutable poolDistributor;
+
+ /// @notice sqrtPriceTickLower
+ /// @dev tick lower represents the lower price of OETH priced in WETH. Meaning the pool
+ /// offers more than 1 OETH for 1 WETH. In other terms to get 1 OETH the swap needs to offer 0.9999 WETH
+ /// this is where purchasing OETH with WETH within the liquidity position is the cheapest.
+ ///
+ /// _____________________
+ /// | | |
+ /// | WETH | OETH |
+ /// | | |
+ /// | | |
+ /// --------- * ---- * ---------- * ---------
+ /// currentPrice
+ /// sqrtPriceHigher-(1:1 parity)
+ /// sqrtPriceLower
+ ///
+ ///
+ /// Price is defined as price of token1 in terms of token0. (token1 / token0)
+ /// @notice sqrtPriceTickLower - OETH is priced 0.9999 WETH
+ uint256 public immutable sqrtPriceTickLower;
+ /// @notice sqrtPriceTickHigher
+ /// @dev tick higher represents 1:1 price parity of WETH to OETH
+ uint256 public immutable sqrtPriceTickHigher;
+ /// @dev price at parity (in OETH this is equal to sqrtPriceTickHigher)
+ uint256 public immutable sqrtPriceAtParity;
+ /// @notice The tick where the strategy deploys the liquidity to
+ int32 public constant TICK_NUMBER = -1;
+ /// @notice Minimum liquidity that must be exceeded to continue with the action
+ /// e.g. deposit, add liquidity
+ uint256 public constant ACTION_THRESHOLD = 1e12;
+ /// @notice Maverick pool static liquidity bin type
+ uint8 public constant MAV_STATIC_BIN_KIND = 0;
+ /// @dev a threshold under which the contract no longer allows for the protocol to rebalance. Guarding
+ /// against a strategist / guardian being taken over and with multiple transactions draining the
+ /// protocol funds.
+ uint256 public constant SOLVENCY_THRESHOLD = 0.998 ether;
+ /// @notice Emitted when the allowed interval within which the strategy contract is allowed to deposit
+ /// liquidity to the underlying pool is updated.
+ /// @param allowedWethShareStart The start of the interval
+ /// @param allowedWethShareEnd The end of the interval
+ event PoolWethShareIntervalUpdated(
+ uint256 allowedWethShareStart,
+ uint256 allowedWethShareEnd
+ );
+ /// @notice Emitted when liquidity is removed from the underlying pool
+ /// @param withdrawLiquidityShare Share of strategy's liquidity that has been removed
+ /// @param removedWETHAmount The amount of WETH removed
+ /// @param removedOETHAmount The amount of OETH removed
+ /// @param underlyingAssets Updated amount of strategy's underlying assets
+ event LiquidityRemoved(
+ uint256 withdrawLiquidityShare,
+ uint256 removedWETHAmount,
+ uint256 removedOETHAmount,
+ uint256 underlyingAssets
+ );
+
+ /// @notice Emitted when the underlying pool is rebalanced
+ /// @param currentPoolWethShare The resulting share of strategy's liquidity
+ /// in the TICK_NUMBER
+ event PoolRebalanced(uint256 currentPoolWethShare);
+
+ /// @notice Emitted when the amount of underlying assets the strategy hold as
+ /// liquidity in the pool is updated.
+ /// @param underlyingAssets Updated amount of strategy's underlying assets
+ event UnderlyingAssetsUpdated(uint256 underlyingAssets);
+
+ /// @notice Emitted when liquidity is added to the underlying pool
+ /// @param wethAmountDesired Amount of WETH desired to be deposited
+ /// @param oethAmountDesired Amount of OETH desired to be deposited
+ /// @param wethAmountSupplied Amount of WETH deposited
+ /// @param oethAmountSupplied Amount of OETH deposited
+ /// @param tokenId NFT liquidity token id
+ /// @param underlyingAssets Updated amount of underlying assets
+ event LiquidityAdded(
+ uint256 wethAmountDesired,
+ uint256 oethAmountDesired,
+ uint256 wethAmountSupplied,
+ uint256 oethAmountSupplied,
+ uint256 tokenId,
+ uint256 underlyingAssets
+ ); // 0x1530ec74
+
+ error PoolRebalanceOutOfBounds(
+ uint256 currentPoolWethShare,
+ uint256 allowedWethShareStart,
+ uint256 allowedWethShareEnd
+ ); // 0x3681e8e0
+
+ error NotEnoughWethForSwap(uint256 wethBalance, uint256 requiredWeth); // 0x989e5ca8
+ error NotEnoughWethLiquidity(uint256 wethBalance, uint256 requiredWeth); // 0xa6737d87
+ error OutsideExpectedTickRange(); // 0xa6e1bad2
+ error SlippageCheck(uint256 tokenReceived); // 0x355cdb78
+
+ /// @notice the constructor
+ /// @dev This contract is intended to be used as a proxy. To prevent the
+ /// potential confusion of having a functional implementation contract
+ /// the constructor has the `initializer` modifier. This way the
+ /// `initialize` function can not be called on the implementation contract.
+ /// For the same reason the implementation contract also has the governor
+ /// set to a zero address.
+ /// @param _stratConfig the basic strategy configuration
+ /// @param _wethAddress Address of the Erc20 WETH Token contract
+ /// @param _oethAddress Address of the Erc20 OETH Token contract
+ /// @param _liquidityManager Address of liquidity manager to add
+ /// the liquidity
+ /// @param _poolLens Address of the pool lens contract
+ /// @param _maverickPosition Address of the Maverick's position contract
+ /// @param _maverickQuoter Address of the Maverick's Quoter contract
+ /// @param _mPool Address of the Rooster concentrated liquidity pool
+ /// @param _upperTickAtParity Bool when true upperTick is the one where the
+ /// price of OETH and WETH are at parity
+ constructor(
+ BaseStrategyConfig memory _stratConfig,
+ address _wethAddress,
+ address _oethAddress,
+ address _liquidityManager,
+ address _poolLens,
+ address _maverickPosition,
+ address _maverickQuoter,
+ address _mPool,
+ bool _upperTickAtParity,
+ address _votingDistributor,
+ address _poolDistributor
+ ) initializer InitializableAbstractStrategy(_stratConfig) {
+ require(
+ address(IMaverickV2Pool(_mPool).tokenA()) == _wethAddress,
+ "WETH not TokenA"
+ );
+ require(
+ address(IMaverickV2Pool(_mPool).tokenB()) == _oethAddress,
+ "OETH not TokenB"
+ );
+ require(
+ _liquidityManager != address(0),
+ "LiquidityManager zero address not allowed"
+ );
+ require(
+ _maverickQuoter != address(0),
+ "Quoter zero address not allowed"
+ );
+ require(_poolLens != address(0), "PoolLens zero address not allowed");
+ require(
+ _maverickPosition != address(0),
+ "Position zero address not allowed"
+ );
+ require(
+ _votingDistributor != address(0),
+ "Voting distributor zero address not allowed"
+ );
+ require(
+ _poolDistributor != address(0),
+ "Pool distributor zero address not allowed"
+ );
+
+ uint256 _tickSpacing = IMaverickV2Pool(_mPool).tickSpacing();
+ require(_tickSpacing == 1, "Unsupported tickSpacing");
+
+ // tickSpacing == 1
+ (sqrtPriceTickLower, sqrtPriceTickHigher) = TickMath.tickSqrtPrices(
+ _tickSpacing,
+ TICK_NUMBER
+ );
+ sqrtPriceAtParity = _upperTickAtParity
+ ? sqrtPriceTickHigher
+ : sqrtPriceTickLower;
+
+ WETH = _wethAddress;
+ OETH = _oethAddress;
+ liquidityManager = IMaverickV2LiquidityManager(_liquidityManager);
+ poolLens = IMaverickV2PoolLens(_poolLens);
+ maverickPosition = IMaverickV2Position(_maverickPosition);
+ quoter = IMaverickV2Quoter(_maverickQuoter);
+ mPool = IMaverickV2Pool(_mPool);
+ votingDistributor = IVotingDistributor(_votingDistributor);
+ poolDistributor = IPoolDistributor(_poolDistributor);
+
+ // prevent implementation contract to be governed
+ _setGovernor(address(0));
+ }
+
+ /**
+ * @notice initialize function, to set up initial internal state
+ */
+ function initialize() external onlyGovernor initializer {
+ // Read reward
+ address[] memory _rewardTokens = new address[](1);
+ _rewardTokens[0] = poolDistributor.rewardToken();
+
+ require(_rewardTokens[0] != address(0), "No reward token configured");
+
+ InitializableAbstractStrategy._initialize(
+ _rewardTokens,
+ new address[](0),
+ new address[](0)
+ );
+ }
+
+ /***************************************
+ Configuration
+ ****************************************/
+
+ /**
+ * @notice Set allowed pool weth share interval. After the rebalance happens
+ * the share of WETH token in the ticker needs to be within the specifications
+ * of the interval.
+ *
+ * @param _allowedWethShareStart Start of WETH share interval expressed as 18 decimal amount
+ * @param _allowedWethShareEnd End of WETH share interval expressed as 18 decimal amount
+ */
+ function setAllowedPoolWethShareInterval(
+ uint256 _allowedWethShareStart,
+ uint256 _allowedWethShareEnd
+ ) external onlyGovernor {
+ require(
+ _allowedWethShareStart < _allowedWethShareEnd,
+ "Invalid interval"
+ );
+ // can not go below 1% weth share
+ require(_allowedWethShareStart > 0.01 ether, "Invalid interval start");
+ // can not go above 95% weth share
+ require(_allowedWethShareEnd < 0.95 ether, "Invalid interval end");
+
+ allowedWethShareStart = _allowedWethShareStart;
+ allowedWethShareEnd = _allowedWethShareEnd;
+ emit PoolWethShareIntervalUpdated(
+ _allowedWethShareStart,
+ _allowedWethShareEnd
+ );
+ }
+
+ /***************************************
+ Strategy overrides
+ ****************************************/
+
+ /**
+ * @notice Deposits funds to the strategy which deposits them to the
+ * underlying Rooster pool if the pool price is within the expected interval.
+ * @param _asset Address for the asset
+ * @param _amount Units of asset to deposit
+ */
+ function deposit(address _asset, uint256 _amount)
+ external
+ override
+ onlyVault
+ nonReentrant
+ {
+ _deposit(_asset, _amount);
+ }
+
+ /**
+ * @notice Deposits all the funds to the strategy which deposits them to the
+ * underlying Rooster pool if the pool price is within the expected interval.
+ */
+ function depositAll() external override onlyVault nonReentrant {
+ uint256 _wethBalance = IERC20(WETH).balanceOf(address(this));
+ _deposit(WETH, _wethBalance);
+ }
+
+ /**
+ * @dev Deposits funds to the strategy which deposits them to the
+ * underlying Rooster pool if the pool price is within the expected interval.
+ * Before this function can be called the initial pool position needs to already
+ * be minted.
+ * @param _asset Address of the asset to deposit
+ * @param _amount Amount of assets to deposit
+ */
+ function _deposit(address _asset, uint256 _amount) internal {
+ require(_asset == WETH, "Unsupported asset");
+ require(_amount > 0, "Must deposit something");
+ require(tokenId > 0, "Initial position not minted");
+ emit Deposit(_asset, address(0), _amount);
+
+ // if the pool price is not within the expected interval leave the WETH on the contract
+ // as to not break the mints - in case it would be configured as a default asset strategy
+ (bool _isExpectedRange, ) = _checkForExpectedPoolPrice(false);
+ if (_isExpectedRange) {
+ // deposit funds into the underlying pool. Because no swap is performed there is no
+ // need to remove any of the liquidity beforehand.
+ _rebalance(0, false, 0, 0);
+ }
+ }
+
+ /**
+ * @notice Withdraw an `amount` of WETH from the platform and
+ * send to the `_recipient`.
+ * @param _recipient Address to which the asset should be sent
+ * @param _asset WETH address
+ * @param _amount Amount of WETH to withdraw
+ */
+ function withdraw(
+ address _recipient,
+ address _asset,
+ uint256 _amount
+ ) external override onlyVault nonReentrant {
+ require(_asset == WETH, "Unsupported asset");
+ require(_amount > 0, "Must withdraw something");
+ require(_recipient == vaultAddress, "Only withdraw to vault allowed");
+
+ _ensureWETHBalance(_amount);
+
+ _withdraw(_recipient, _amount);
+ }
+
+ /**
+ * @notice Withdraw WETH and sends it to the Vault.
+ */
+ function withdrawAll() external override onlyVault nonReentrant {
+ if (tokenId != 0) {
+ _removeLiquidity(1e18);
+ }
+
+ uint256 _balance = IERC20(WETH).balanceOf(address(this));
+ if (_balance > 0) {
+ _withdraw(vaultAddress, _balance);
+ }
+ }
+
+ function _withdraw(address _recipient, uint256 _amount) internal {
+ IERC20(WETH).safeTransfer(_recipient, _amount);
+ emit Withdrawal(WETH, address(0), _amount);
+ }
+
+ /**
+ * @dev Retuns bool indicating whether asset is supported by strategy
+ * @param _asset Address of the asset
+ * @return bool True when the _asset is WETH
+ */
+ function supportsAsset(address _asset) public view override returns (bool) {
+ return _asset == WETH;
+ }
+
+ /**
+ * @dev Approve the spending amounts for the assets
+ */
+ function _approveTokenAmounts(
+ uint256 _wethAllowance,
+ uint256 _oethAllowance
+ ) internal {
+ IERC20(WETH).approve(address(liquidityManager), _wethAllowance);
+ IERC20(OETH).approve(address(liquidityManager), _oethAllowance);
+ }
+
+ /***************************************
+ Liquidity management
+ ****************************************/
+ /**
+ * @dev Add liquidity into the pool in the pre-configured WETH to OETH share ratios
+ * defined by the allowedPoolWethShareStart|End interval.
+ *
+ * Normally a PoolLens contract is used to prepare the parameters to add liquidity to the
+ * Rooster pools. It has some errors when doing those calculation and for that reason a
+ * much more accurate Quoter contract is used. This is possible due to our requirement of
+ * adding liquidity only to one tick - PoolLens supports adding liquidity into multiple ticks
+ * using different distribution ratios.
+ */
+ function _addLiquidity() internal {
+ uint256 _wethBalance = IERC20(WETH).balanceOf(address(this));
+ uint256 _oethBalance = IERC20(OETH).balanceOf(address(this));
+ // don't deposit small liquidity amounts
+ if (_wethBalance <= ACTION_THRESHOLD) {
+ return;
+ }
+
+ (
+ bytes memory packedSqrtPriceBreaks,
+ bytes[] memory packedArgs,
+ uint256 WETHRequired,
+ uint256 OETHRequired
+ ) = _getAddLiquidityParams(_wethBalance, 1e30);
+
+ if (OETHRequired > _oethBalance) {
+ IVault(vaultAddress).mintForStrategy(OETHRequired - _oethBalance);
+ }
+
+ _approveTokenAmounts(WETHRequired, OETHRequired);
+
+ (
+ uint256 _wethAmount,
+ uint256 _oethAmount,
+ uint32[] memory binIds
+ ) = liquidityManager.addPositionLiquidityToSenderByTokenIndex(
+ mPool,
+ 0, // NFT token index
+ packedSqrtPriceBreaks,
+ packedArgs
+ );
+
+ require(binIds.length == 1, "Unexpected binIds length");
+
+ // burn remaining OETH
+ _burnOethOnTheContract();
+ _updateUnderlyingAssets();
+
+ // needs to be called after _updateUnderlyingAssets so the updated amount
+ // is reflected in the event
+ emit LiquidityAdded(
+ _wethBalance, // wethAmountDesired
+ OETHRequired, // oethAmountDesired
+ _wethAmount, // wethAmountSupplied
+ _oethAmount, // oethAmountSupplied
+ tokenId, // tokenId
+ underlyingAssets
+ );
+ }
+
+ /**
+ * @dev The function creates liquidity parameters required to be able to add liquidity to the pool.
+ * The function needs to handle the 3 different cases of the way liquidity is added:
+ * - only WETH present in the tick
+ * - only OETH present in the tick
+ * - both tokens present in the tick
+ *
+ */
+ function _getAddLiquidityParams(uint256 _maxWETH, uint256 _maxOETH)
+ internal
+ returns (
+ bytes memory packedSqrtPriceBreaks,
+ bytes[] memory packedArgs,
+ uint256 WETHRequired,
+ uint256 OETHRequired
+ )
+ {
+ IMaverickV2Pool.AddLiquidityParams[]
+ memory addParams = new IMaverickV2Pool.AddLiquidityParams[](1);
+ int32[] memory ticks = new int32[](1);
+ uint128[] memory amounts = new uint128[](1);
+ ticks[0] = TICK_NUMBER;
+ // arbitrary LP amount
+ amounts[0] = 1e24;
+
+ // construct value for Quoter with arbitrary LP amount
+ IMaverickV2Pool.AddLiquidityParams memory addParam = IMaverickV2Pool
+ .AddLiquidityParams({
+ kind: MAV_STATIC_BIN_KIND,
+ ticks: ticks,
+ amounts: amounts
+ });
+
+ // get the WETH and OETH required to get the proportion of tokens required
+ // given the arbitrary liquidity
+ (WETHRequired, OETHRequired, ) = quoter.calculateAddLiquidity(
+ mPool,
+ addParam
+ );
+
+ /**
+ * If either token required is 0 then the tick consists only of the other token. In that
+ * case the liquidity calculations need to be done using the non 0 token. By setting the
+ * tokenRequired from 0 to 1 the `min` in next step will ignore that (the bigger) value.
+ */
+ WETHRequired = WETHRequired == 0 ? 1 : WETHRequired;
+ OETHRequired = OETHRequired == 0 ? 1 : OETHRequired;
+
+ addParam.amounts[0] = Math_v5
+ .min(
+ ((_maxWETH - 1) * 1e24) / WETHRequired,
+ ((_maxOETH - 1) * 1e24) / OETHRequired
+ )
+ .toUint128();
+
+ // update the quotes with the actual amounts
+ (WETHRequired, OETHRequired, ) = quoter.calculateAddLiquidity(
+ mPool,
+ addParam
+ );
+
+ require(_maxWETH >= WETHRequired, "More WETH required than specified");
+ require(_maxOETH >= OETHRequired, "More OETH required than specified");
+
+ // organize values to be used by manager
+ addParams[0] = addParam;
+ packedArgs = liquidityManager.packAddLiquidityArgsArray(addParams);
+ // price can stay 0 if array only has one element
+ packedSqrtPriceBreaks = liquidityManager.packUint88Array(
+ new uint88[](1)
+ );
+ }
+
+ /**
+ * @dev Check that the Rooster pool price is within the expected
+ * parameters.
+ * This function works whether the strategy contract has liquidity
+ * position in the pool or not. The function returns _wethSharePct
+ * as a gas optimization measure.
+ * @param _throwException when set to true the function throws an exception
+ * when pool's price is not within expected range.
+ * @return _isExpectedRange Bool expressing price is within expected range
+ * @return _wethSharePct Share of WETH owned by this strategy contract in the
+ * configured ticker.
+ */
+ function _checkForExpectedPoolPrice(bool _throwException)
+ internal
+ view
+ returns (bool _isExpectedRange, uint256 _wethSharePct)
+ {
+ require(
+ allowedWethShareStart != 0 && allowedWethShareEnd != 0,
+ "Weth share interval not set"
+ );
+
+ uint256 _currentPrice = getPoolSqrtPrice();
+
+ /**
+ * First check pool price is in expected tick range
+ *
+ * A revert is issued even though price being equal to the lower bound as that can not
+ * be within the approved tick range.
+ */
+ if (
+ _currentPrice <= sqrtPriceTickLower ||
+ _currentPrice >= sqrtPriceTickHigher
+ ) {
+ if (_throwException) {
+ revert OutsideExpectedTickRange();
+ }
+
+ return (false, _currentPrice <= sqrtPriceTickLower ? 0 : 1e18);
+ }
+
+ // 18 decimal number expressed WETH tick share
+ _wethSharePct = _getWethShare(_currentPrice);
+
+ if (
+ _wethSharePct < allowedWethShareStart ||
+ _wethSharePct > allowedWethShareEnd
+ ) {
+ if (_throwException) {
+ revert PoolRebalanceOutOfBounds(
+ _wethSharePct,
+ allowedWethShareStart,
+ allowedWethShareEnd
+ );
+ }
+ return (false, _wethSharePct);
+ }
+
+ return (true, _wethSharePct);
+ }
+
+ /**
+ * @notice Rebalance the pool to the desired token split and Deposit any WETH on the contract to the
+ * underlying rooster pool. Print the required amount of corresponding OETH. After the rebalancing is
+ * done burn any potentially remaining OETH tokens still on the strategy contract.
+ *
+ * This function has a slightly different behaviour depending on the status of the underlying Rooster
+ * pool. The function consists of the following 3 steps:
+ * 1. withdrawLiquidityOption -> this is a configurable option where either only part of the liquidity
+ * necessary for the swap is removed, or all of it. This way the rebalance
+ * is able to optimize for volume, for efficiency or anything in between
+ * 2. swapToDesiredPosition -> move active trading price in the pool to be able to deposit WETH & OETH
+ * tokens with the desired pre-configured ratios
+ * 3. addLiquidity -> add liquidity into the pool respecting ratio split configuration
+ *
+ *
+ * Exact _amountToSwap, _swapWeth & _minTokenReceived parameters shall be determined by simulating the
+ * transaction off-chain. The strategy checks that after the swap the share of the tokens is in the
+ * expected ranges.
+ *
+ * @param _amountToSwap The amount of the token to swap
+ * @param _swapWeth Swap using WETH when true, use OETH when false
+ * @param _minTokenReceived Slippage check -> minimum amount of token expected in return
+ * @param _liquidityToRemovePct Percentage of liquidity to remove -> the percentage amount of liquidity to
+ * remove before performing the swap. 1e18 denominated
+ */
+ function rebalance(
+ uint256 _amountToSwap,
+ bool _swapWeth,
+ uint256 _minTokenReceived,
+ uint256 _liquidityToRemovePct
+ ) external nonReentrant onlyGovernorOrStrategist {
+ _rebalance(
+ _amountToSwap,
+ _swapWeth,
+ _minTokenReceived,
+ _liquidityToRemovePct
+ );
+ }
+
+ // slither-disable-start reentrancy-no-eth
+ function _rebalance(
+ uint256 _amountToSwap,
+ bool _swapWeth,
+ uint256 _minTokenReceived,
+ uint256 _liquidityToRemovePct
+ ) internal {
+ // Remove the required amount of liquidity
+ if (_liquidityToRemovePct > 0) {
+ _removeLiquidity(_liquidityToRemovePct);
+ }
+
+ // in some cases (e.g. deposits) we will just want to add liquidity and not
+ // issue a swap to move the active trading position within the pool. Before or after a
+ // deposit or as a standalone call the strategist might issue a rebalance to move the
+ // active trading price to a more desired position.
+ if (_amountToSwap > 0) {
+ // In case liquidity has been removed and there is still not enough WETH owned by the
+ // strategy contract remove additional required amount of WETH.
+ if (_swapWeth) _ensureWETHBalance(_amountToSwap);
+
+ _swapToDesiredPosition(_amountToSwap, _swapWeth, _minTokenReceived);
+ }
+
+ // calling check liquidity early so we don't get unexpected errors when adding liquidity
+ // in the later stages of this function
+ _checkForExpectedPoolPrice(true);
+
+ _addLiquidity();
+
+ // this call shouldn't be necessary, since adding liquidity shouldn't affect the active
+ // trading price. It is a defensive programming measure.
+ (, uint256 _wethSharePct) = _checkForExpectedPoolPrice(true);
+
+ // revert if protocol insolvent
+ _solvencyAssert();
+
+ emit PoolRebalanced(_wethSharePct);
+ }
+
+ // slither-disable-end reentrancy-no-eth
+
+ /**
+ * @dev Perform a swap so that after the swap the tick has the desired WETH to OETH token share.
+ */
+ function _swapToDesiredPosition(
+ uint256 _amountToSwap,
+ bool _swapWeth,
+ uint256 _minTokenReceived
+ ) internal {
+ IERC20 _tokenToSwap = IERC20(_swapWeth ? WETH : OETH);
+ uint256 _balance = _tokenToSwap.balanceOf(address(this));
+
+ if (_balance < _amountToSwap) {
+ // This should never trigger since _ensureWETHBalance will already
+ // throw an error if there is not enough WETH
+ if (_swapWeth) {
+ revert NotEnoughWethForSwap(_balance, _amountToSwap);
+ }
+ // if swapping OETH
+ uint256 mintForSwap = _amountToSwap - _balance;
+ IVault(vaultAddress).mintForStrategy(mintForSwap);
+ }
+
+ // SafeERC20 is used for IERC20 transfers. Not sure why slither complains
+ // slither-disable-next-line unchecked-transfer
+ _tokenToSwap.transfer(address(mPool), _amountToSwap);
+
+ // tickLimit: the furthest tick a swap will execute in. If no limit is desired,
+ // value should be set to type(int32).max for a tokenAIn (WETH) swap
+ // and type(int32).min for a swap where tokenB (OETH) is the input
+
+ IMaverickV2Pool.SwapParams memory swapParams = IMaverickV2Pool
+ // exactOutput defines whether the amount specified is the output
+ // or the input amount of the swap
+ .SwapParams({
+ amount: _amountToSwap,
+ tokenAIn: _swapWeth,
+ exactOutput: false,
+ tickLimit: TICK_NUMBER
+ });
+
+ // swaps without a callback as the assets are already sent to the pool
+ (, uint256 amountOut) = mPool.swap(
+ address(this),
+ swapParams,
+ bytes("")
+ );
+
+ /**
+ * There could be additional checks here for validating minTokenReceived is within the
+ * expected range (e.g. 99% - 101% of the token sent in). Though that doesn't provide
+ * any additional security. After the swap the `_checkForExpectedPoolPrice` validates
+ * that the swap has moved the price into the expected tick (# -1).
+ *
+ * If the guardian forgets to set a `_minTokenReceived` and a sandwich attack bends
+ * the pool before the swap the `_checkForExpectedPoolPrice` will fail the transaction.
+ *
+ * A check would not prevent a compromised guardian from stealing funds as multiple
+ * transactions each loosing smaller amount of funds are still possible.
+ */
+ if (amountOut < _minTokenReceived) {
+ revert SlippageCheck(amountOut);
+ }
+
+ /**
+ * In the interest of each function in `_rebalance` to leave the contract state as
+ * clean as possible the OETH tokens here are burned. This decreases the
+ * dependence where `_swapToDesiredPosition` function relies on later functions
+ * (`addLiquidity`) to burn the OETH. Reducing the risk of error introduction.
+ */
+ _burnOethOnTheContract();
+ }
+
+ /**
+ * @dev This function removes the appropriate amount of liquidity to ensure that the required
+ * amount of WETH is available on the contract
+ *
+ * @param _amount WETH balance required on the contract
+ */
+ function _ensureWETHBalance(uint256 _amount) internal {
+ uint256 _wethBalance = IERC20(WETH).balanceOf(address(this));
+ if (_wethBalance >= _amount) {
+ return;
+ }
+
+ require(tokenId != 0, "No liquidity available");
+ uint256 _additionalWethRequired = _amount - _wethBalance;
+ (uint256 _wethInThePool, ) = getPositionPrincipal();
+
+ if (_wethInThePool < _additionalWethRequired) {
+ revert NotEnoughWethLiquidity(
+ _wethInThePool,
+ _additionalWethRequired
+ );
+ }
+
+ uint256 shareOfWethToRemove = _wethInThePool <= 1
+ ? 1e18
+ : Math_v5.min(
+ /**
+ * When dealing with shares of liquidity to remove there is always some
+ * rounding involved. After extensive fuzz testing the below approach
+ * yielded the best results where the strategy overdraws the least and
+ * never removes insufficient amount of WETH.
+ */
+ (_additionalWethRequired + 2).divPrecisely(_wethInThePool - 1) + 2,
+ 1e18
+ );
+
+ _removeLiquidity(shareOfWethToRemove);
+ }
+
+ /**
+ * @dev Decrease partial or all liquidity from the pool.
+ * @param _liquidityToDecrease The amount of liquidity to remove denominated in 1e18
+ */
+ function _removeLiquidity(uint256 _liquidityToDecrease) internal {
+ require(_liquidityToDecrease > 0, "Must remove some liquidity");
+ require(
+ _liquidityToDecrease <= 1e18,
+ "Can not remove more than 100% of liquidity"
+ );
+
+ // 0 indicates the first (and only) bin in the NFT LP position.
+ IMaverickV2Pool.RemoveLiquidityParams memory params = maverickPosition
+ .getRemoveParams(tokenId, 0, _liquidityToDecrease);
+ (uint256 _amountWeth, uint256 _amountOeth) = maverickPosition
+ .removeLiquidityToSender(tokenId, mPool, params);
+
+ _burnOethOnTheContract();
+ _updateUnderlyingAssets();
+
+ // needs to be called after the _updateUnderlyingAssets so the updated amount is reflected
+ // in the event
+ emit LiquidityRemoved(
+ _liquidityToDecrease,
+ _amountWeth,
+ _amountOeth,
+ underlyingAssets
+ );
+ }
+
+ /**
+ * @dev Burns any OETH tokens remaining on the strategy contract if the balance is
+ * above the action threshold.
+ */
+ function _burnOethOnTheContract() internal {
+ uint256 _oethBalance = IERC20(OETH).balanceOf(address(this));
+ IVault(vaultAddress).burnForStrategy(_oethBalance);
+ }
+
+ /**
+ * @notice Returns the percentage of WETH liquidity in the configured ticker
+ * owned by this strategy contract.
+ * @return uint256 1e18 denominated percentage expressing the share
+ */
+ function getWETHShare() external view returns (uint256) {
+ uint256 _currentPrice = getPoolSqrtPrice();
+ return _getWethShare(_currentPrice);
+ }
+
+ /**
+ * @dev Returns the share of WETH in tick denominated in 1e18
+ */
+ function _getWethShare(uint256 _currentPrice)
+ internal
+ view
+ returns (uint256)
+ {
+ (
+ uint256 wethAmount,
+ uint256 oethAmount
+ ) = _reservesInTickForGivenPriceAndLiquidity(
+ sqrtPriceTickLower,
+ sqrtPriceTickHigher,
+ _currentPrice,
+ 1e24
+ );
+
+ return wethAmount.divPrecisely(wethAmount + oethAmount);
+ }
+
+ /**
+ * @notice Returns the current pool price in square root
+ * @return Square root of the pool price
+ */
+ function getPoolSqrtPrice() public view returns (uint256) {
+ return poolLens.getPoolSqrtPrice(mPool);
+ }
+
+ /**
+ * @notice Returns the current active trading tick of the underlying pool
+ * @return _currentTick Current pool trading tick
+ */
+ function getCurrentTradingTick() public view returns (int32 _currentTick) {
+ _currentTick = mPool.getState().activeTick;
+ }
+
+ /**
+ * @notice Mint the initial NFT position
+ *
+ * @dev This amount is "gifted" to the strategy contract and will count as a yield
+ * surplus.
+ */
+ // slither-disable-start reentrancy-no-eth
+ function mintInitialPosition() external onlyGovernor nonReentrant {
+ require(tokenId == 0, "Initial position already minted");
+ (
+ bytes memory packedSqrtPriceBreaks,
+ bytes[] memory packedArgs,
+ uint256 WETHRequired,
+ uint256 OETHRequired
+ ) = _getAddLiquidityParams(1e16, 1e16);
+
+ // Mint rounded up OETH amount
+ if (OETHRequired > 0) {
+ IVault(vaultAddress).mintForStrategy(OETHRequired);
+ }
+
+ _approveTokenAmounts(WETHRequired, OETHRequired);
+
+ // Store the tokenId before calling updateUnderlyingAssets as it relies on the tokenId
+ // not being 0
+ (, , , tokenId) = liquidityManager.mintPositionNftToSender(
+ mPool,
+ packedSqrtPriceBreaks,
+ packedArgs
+ );
+
+ // burn remaining OETH
+ _burnOethOnTheContract();
+ _updateUnderlyingAssets();
+ }
+
+ // slither-disable-end reentrancy-no-eth
+
+ /**
+ * @notice Returns the balance of tokens the strategy holds in the LP position
+ * @return _amountWeth Amount of WETH in position
+ * @return _amountOeth Amount of OETH in position
+ */
+ function getPositionPrincipal()
+ public
+ view
+ returns (uint256 _amountWeth, uint256 _amountOeth)
+ {
+ if (tokenId == 0) {
+ return (0, 0);
+ }
+
+ (_amountWeth, _amountOeth, ) = _getPositionInformation();
+ }
+
+ /**
+ * @dev Returns the balance of tokens the strategy holds in the LP position
+ * @return _amountWeth Amount of WETH in position
+ * @return _amountOeth Amount of OETH in position
+ * @return liquidity Amount of liquidity in the position
+ */
+ function _getPositionInformation()
+ internal
+ view
+ returns (
+ uint256 _amountWeth,
+ uint256 _amountOeth,
+ uint256 liquidity
+ )
+ {
+ IMaverickV2Position.PositionFullInformation
+ memory positionInfo = maverickPosition.tokenIdPositionInformation(
+ tokenId,
+ 0
+ );
+
+ require(
+ positionInfo.liquidities.length == 1,
+ "Unexpected liquidities length"
+ );
+ require(positionInfo.ticks.length == 1, "Unexpected ticks length");
+
+ _amountWeth = positionInfo.amountA;
+ _amountOeth = positionInfo.amountB;
+ liquidity = positionInfo.liquidities[0];
+ }
+
+ /**
+ * Checks that the protocol is solvent, protecting from a rogue Strategist / Guardian that can
+ * keep rebalancing the pool in both directions making the protocol lose a tiny amount of
+ * funds each time.
+ *
+ * Protocol must be at least SOLVENCY_THRESHOLD (99.8%) backed in order for the rebalances to
+ * function.
+ */
+ function _solvencyAssert() internal view {
+ uint256 _totalVaultValue = IVault(vaultAddress).totalValue();
+ uint256 _totalOethSupply = IERC20(OETH).totalSupply();
+
+ if (
+ _totalVaultValue.divPrecisely(_totalOethSupply) < SOLVENCY_THRESHOLD
+ ) {
+ revert("Protocol insolvent");
+ }
+ }
+
+ /**
+ * @dev Collect Rooster reward token, and send it to the harvesterAddress
+ */
+ function collectRewardTokens()
+ external
+ override
+ onlyHarvester
+ nonReentrant
+ {
+ // Do nothing if there's no position minted
+ if (tokenId > 0) {
+ uint32[] memory binIds = new uint32[](1);
+ IMaverickV2Pool.TickState memory tickState = mPool.getTick(
+ TICK_NUMBER
+ );
+ // get the binId for the MAV_STATIC_BIN_KIND in tick TICK_NUMBER (-1)
+ binIds[0] = tickState.binIdsByTick[0];
+
+ uint256 lastEpoch = votingDistributor.lastEpoch();
+
+ poolDistributor.claimLp(
+ address(this),
+ tokenId,
+ mPool,
+ binIds,
+ lastEpoch
+ );
+ }
+
+ // Run the internal inherited function
+ _collectRewardTokens();
+ }
+
+ /***************************************
+ Balances and Fees
+ ****************************************/
+
+ /**
+ * @dev Get the total asset value held in the platform
+ * @param _asset Address of the asset
+ * @return balance Total value of the asset in the platform
+ */
+ function checkBalance(address _asset)
+ external
+ view
+ override
+ returns (uint256)
+ {
+ require(_asset == WETH, "Only WETH supported");
+
+ // because of PoolLens inaccuracy there is usually some dust WETH left on the contract
+ uint256 _wethBalance = IERC20(WETH).balanceOf(address(this));
+ // just paranoia check, in case there is OETH in the strategy that for some reason hasn't
+ // been burned yet. This should always be 0.
+ uint256 _oethBalance = IERC20(OETH).balanceOf(address(this));
+ return underlyingAssets + _wethBalance + _oethBalance;
+ }
+
+ /// @dev This function updates the amount of underlying assets with the approach of the least possible
+ /// total tokens extracted for the current liquidity in the pool.
+ function _updateUnderlyingAssets() internal {
+ /**
+ * Our net value represent the smallest amount of tokens we are able to extract from the position
+ * given our liquidity.
+ *
+ * The least amount of tokens ex-tractable from the position is where the active trading price is
+ * at the edge between tick -1 & tick 0. There the pool is offering 1:1 trades between WETH & OETH.
+ * At that moment the pool consists completely of WETH and no OETH.
+ *
+ * The more swaps from OETH -> WETH happen on the pool the more the price starts to move away from the tick 0
+ * towards the middle of tick -1 making OETH (priced in WETH) cheaper.
+ */
+
+ uint256 _wethAmount = tokenId == 0 ? 0 : _balanceInPosition();
+
+ underlyingAssets = _wethAmount;
+ emit UnderlyingAssetsUpdated(_wethAmount);
+ }
+
+ /**
+ * @dev Strategy reserves (which consist only of WETH in case of Rooster - Plume pool)
+ * when the tick price is closest to parity - assuring the lowest amount of tokens
+ * returned for the current position liquidity.
+ */
+ function _balanceInPosition() internal view returns (uint256 _wethBalance) {
+ (, , uint256 liquidity) = _getPositionInformation();
+
+ uint256 _oethBalance;
+
+ (_wethBalance, _oethBalance) = _reservesInTickForGivenPriceAndLiquidity(
+ sqrtPriceTickLower,
+ sqrtPriceTickHigher,
+ sqrtPriceAtParity,
+ liquidity
+ );
+
+ require(_oethBalance == 0, "Non zero oethBalance");
+ }
+
+ /**
+ * @notice Tick dominance denominated in 1e18
+ * @return _tickDominance The share of liquidity in TICK_NUMBER tick owned
+ * by the strategy contract denominated in 1e18
+ */
+ function tickDominance() public view returns (uint256 _tickDominance) {
+ IMaverickV2Pool.TickState memory tickState = mPool.getTick(TICK_NUMBER);
+
+ uint256 wethReserve = tickState.reserveA;
+ uint256 oethReserve = tickState.reserveB;
+
+ // prettier-ignore
+ (uint256 _amountWeth, uint256 _amountOeth, ) = _getPositionInformation();
+
+ if (wethReserve + oethReserve == 0) {
+ return 0;
+ }
+
+ _tickDominance = (_amountWeth + _amountOeth).divPrecisely(
+ wethReserve + oethReserve
+ );
+ }
+
+ /***************************************
+ Hidden functions
+ ****************************************/
+ /**
+ * @dev Unsupported
+ */
+ function setPTokenAddress(address, address) external pure override {
+ // The pool tokens can never change.
+ revert("Unsupported method");
+ }
+
+ /**
+ * @dev Unsupported
+ */
+ function removePToken(uint256) external pure override {
+ // The pool tokens can never change.
+ revert("Unsupported method");
+ }
+
+ /**
+ * @dev Unsupported
+ */
+ function _abstractSetPToken(address, address) internal pure override {
+ revert("Unsupported method");
+ }
+
+ /**
+ * @dev Unsupported
+ */
+ function safeApproveAllTokens() external pure override {
+ // all the amounts are approved at the time required
+ revert("Unsupported method");
+ }
+
+ /***************************************
+ Maverick liquidity utilities
+ ****************************************/
+
+ /// @notice Calculates deltaA = liquidity * (sqrt(upper) - sqrt(lower))
+ /// Calculates deltaB = liquidity / sqrt(lower) - liquidity / sqrt(upper),
+ /// i.e. liquidity * (sqrt(upper) - sqrt(lower)) / (sqrt(upper) * sqrt(lower))
+ ///
+ /// @dev refactored from here:
+ // solhint-disable-next-line max-line-length
+ /// https://github.com/rooster-protocol/rooster-contracts/blob/main/v2-supplemental/contracts/libraries/LiquidityUtilities.sol#L665-L695
+ function _reservesInTickForGivenPriceAndLiquidity(
+ uint256 _lowerSqrtPrice,
+ uint256 _upperSqrtPrice,
+ uint256 _newSqrtPrice,
+ uint256 _liquidity
+ ) internal pure returns (uint128 reserveA, uint128 reserveB) {
+ if (_liquidity == 0) {
+ (reserveA, reserveB) = (0, 0);
+ } else {
+ uint256 lowerEdge = MathRooster.max(_lowerSqrtPrice, _newSqrtPrice);
+
+ reserveA = MathRooster
+ .mulCeil(
+ _liquidity,
+ MathRooster.clip(
+ MathRooster.min(_upperSqrtPrice, _newSqrtPrice),
+ _lowerSqrtPrice
+ )
+ )
+ .toUint128();
+ reserveB = MathRooster
+ .mulDivCeil(
+ _liquidity,
+ 1e18 * MathRooster.clip(_upperSqrtPrice, lowerEdge),
+ _upperSqrtPrice * lowerEdge
+ )
+ .toUint128();
+ }
+ }
+}
diff --git a/contracts/contracts/utils/InitializableAbstractStrategy.sol b/contracts/contracts/utils/InitializableAbstractStrategy.sol
index 28ef3033f2..890da82e57 100644
--- a/contracts/contracts/utils/InitializableAbstractStrategy.sol
+++ b/contracts/contracts/utils/InitializableAbstractStrategy.sol
@@ -78,6 +78,17 @@ abstract contract InitializableAbstractStrategy is Initializable, Governable {
address vaultAddress; // Address of the OToken's Vault
}
+ /**
+ * @dev Verifies that the caller is the Governor or Strategist.
+ */
+ modifier onlyGovernorOrStrategist() {
+ require(
+ isGovernor() || msg.sender == IVault(vaultAddress).strategistAddr(),
+ "Caller is not the Strategist or Governor"
+ );
+ _;
+ }
+
/**
* @param _config The platform and OToken vault addresses
*/
diff --git a/contracts/deploy/deployActions.js b/contracts/deploy/deployActions.js
index 4c2ffdaf18..8d1015e623 100644
--- a/contracts/deploy/deployActions.js
+++ b/contracts/deploy/deployActions.js
@@ -9,6 +9,8 @@ const {
isHoleskyOrFork,
isSonicOrFork,
isTest,
+ isFork,
+ isPlume,
} = require("../test/helpers.js");
const { deployWithConfirmation, withConfirmation } = require("../utils/deploy");
const { metapoolLPCRVPid } = require("../utils/constants");
@@ -1347,6 +1349,74 @@ const deployBaseAerodromeAMOStrategyImplementation = async () => {
return await ethers.getContract("AerodromeAMOStrategy");
};
+const deployPlumeRoosterAMOStrategyImplementation = async (poolAddress) => {
+ return _deployPlumeRoosterAMOImplementationConfigurable(
+ poolAddress,
+ "RoosterAMOStrategy"
+ );
+};
+
+const deployPlumeMockRoosterAMOStrategyImplementation = async (poolAddress) => {
+ return _deployPlumeRoosterAMOImplementationConfigurable(
+ poolAddress,
+ "MockRoosterAMOStrategy"
+ );
+};
+
+const _deployPlumeRoosterAMOImplementationConfigurable = async (
+ poolAddress,
+ stratContractName
+) => {
+ const cOETHpProxy = await ethers.getContract("OETHPlumeProxy");
+ const cOETHpVaultProxy = await ethers.getContract("OETHPlumeVaultProxy");
+
+ if (!isFork && isPlume) {
+ throw new Error("You cannot deploy this strategy yet");
+ }
+
+ const cMockMaverickDistributor = await ethers.getContract(
+ "MockMaverickDistributor"
+ );
+
+ await deployWithConfirmation(stratContractName, [
+ /* Used first by the 002_rooster_amo_ deploy file
+ */
+ [addresses.zero, cOETHpVaultProxy.address], // platformAddress, VaultAddress
+ addresses.plume.WETH, // weth address
+ cOETHpProxy.address, // OETHp address
+ addresses.plume.MaverickV2LiquidityManager, // liquidity mananger
+ addresses.plume.MaverickV2PoolLens, // pool lens
+ addresses.plume.MaverickV2Position, // position
+ addresses.plume.MaverickV2Quoter, // quoter
+ poolAddress, // superOETHp/WPLUME pool
+ true, // uppperTickAtParity
+ // TODO: change these to actual addresses
+ cMockMaverickDistributor.address, // votingDistributor
+ cMockMaverickDistributor.address, // poolDistributor
+ ]);
+
+ return await ethers.getContract(stratContractName);
+};
+
+const getPlumeContracts = async () => {
+ const maverickV2LiquidityManager = await ethers.getContractAt(
+ "IMaverickV2LiquidityManager",
+ addresses.plume.MaverickV2LiquidityManager
+ );
+ const maverickV2PoolLens = await ethers.getContractAt(
+ "IMaverickV2PoolLens",
+ addresses.plume.MaverickV2PoolLens
+ );
+ const cOETHpProxy = await ethers.getContract("OETHPlumeProxy");
+ const cOETHp = await ethers.getContractAt("OETHPlume", cOETHpProxy.address);
+
+ return {
+ maverickV2LiquidityManager,
+ maverickV2PoolLens,
+ cOETHp,
+ };
+};
+
const deploySonicSwapXAMOStrategyImplementation = async () => {
const { deployerAddr } = await getNamedAccounts();
const sDeployer = await ethers.provider.getSigner(deployerAddr);
@@ -1421,5 +1491,8 @@ module.exports = {
upgradeNativeStakingSSVStrategy,
upgradeNativeStakingFeeAccumulator,
deployBaseAerodromeAMOStrategyImplementation,
+ deployPlumeRoosterAMOStrategyImplementation,
+ deployPlumeMockRoosterAMOStrategyImplementation,
+ getPlumeContracts,
deploySonicSwapXAMOStrategyImplementation,
};
diff --git a/contracts/deploy/plume/005_rooster_amo.js b/contracts/deploy/plume/005_rooster_amo.js
new file mode 100644
index 0000000000..c865b8d1c8
--- /dev/null
+++ b/contracts/deploy/plume/005_rooster_amo.js
@@ -0,0 +1,145 @@
+const hre = require("hardhat");
+const { deployOnPlume } = require("../../utils/deploy-l2");
+const {
+ deployWithConfirmation,
+ withConfirmation,
+} = require("../../utils/deploy");
+const addresses = require("../../utils/addresses");
+const {
+ deployPlumeRoosterAMOStrategyImplementation,
+} = require("../deployActions");
+const { isFork, oethUnits } = require("../../test/helpers");
+const { setERC20TokenBalance } = require("../../test/_fund");
+const { utils } = require("ethers");
+
+module.exports = deployOnPlume(
+ {
+ deployName: "005_rooster_amo",
+ },
+ async () => {
+ const { deployerAddr } = await getNamedAccounts();
+ const sDeployer = await ethers.getSigner(deployerAddr);
+ const cOETHpVaultProxy = await ethers.getContract("OETHPlumeVaultProxy");
+ const weth = await ethers.getContractAt("IWETH9", addresses.plume.WETH);
+ const deployerWethBalance = await weth
+ .connect(sDeployer)
+ .balanceOf(sDeployer.address);
+
+ console.log("Deployer WETH balance", deployerWethBalance.toString());
+ if (!isFork) {
+ if (deployerWethBalance.lt(oethUnits("1"))) {
+ throw new Error(
+ "Deployer needs at least 1e18 of WETH to mint the initial balance"
+ );
+ }
+ }
+
+ const cOETHpVault = await ethers.getContractAt(
+ "IVault",
+ cOETHpVaultProxy.address
+ );
+
+ await deployWithConfirmation("RoosterAMOStrategyProxy");
+ const cAMOStrategyProxy = await ethers.getContract(
+ "RoosterAMOStrategyProxy"
+ );
+
+ if (isFork) {
+ // Just pretend wPlume is the reward token for testing
+ await deployWithConfirmation("MockMaverickDistributor", [
+ addresses.plume.WPLUME,
+ ]);
+ const cMockMaverickDistributor = await ethers.getContract(
+ "MockMaverickDistributor"
+ );
+
+ await withConfirmation(
+ cMockMaverickDistributor
+ .connect(sDeployer)
+ .setRewardTokenAmount(oethUnits("1"))
+ );
+
+ // Fund the mock contract
+ const wPlume = await ethers.getContractAt(
+ "IWETH9",
+ addresses.plume.WPLUME
+ );
+ await withConfirmation(
+ wPlume.connect(sDeployer).deposit({ value: oethUnits("10") })
+ );
+ await withConfirmation(
+ wPlume
+ .connect(sDeployer)
+ .transfer(cMockMaverickDistributor.address, oethUnits("10"))
+ );
+ }
+
+ const cAMOStrategyImpl = await deployPlumeRoosterAMOStrategyImplementation(
+ addresses.plume.OethpWETHRoosterPool
+ );
+ const strategyImplInitData = cAMOStrategyImpl.interface.encodeFunctionData(
+ "initialize()",
+ []
+ );
+
+ // prettier-ignore
+ await withConfirmation(
+ cAMOStrategyProxy
+ .connect(sDeployer)["initialize(address,address,bytes)"](
+ cAMOStrategyImpl.address,
+ addresses.plume.timelock,
+ strategyImplInitData
+ )
+ );
+
+ const cAMOStrategy = await ethers.getContractAt(
+ "RoosterAMOStrategy",
+ cAMOStrategyProxy.address
+ );
+
+ if (isFork) {
+ // 1 WETH
+ await setERC20TokenBalance(sDeployer.address, weth, "1", hre);
+ }
+
+ // transfer 1e16 of WETH to the strategy to mint the initial position
+ await weth
+ .connect(sDeployer)
+ .transfer(cAMOStrategy.address, oethUnits("0.01"));
+
+ return {
+ actions: [
+ {
+ // Approve the AMO strategy on the Vault
+ contract: cOETHpVault,
+ signature: "approveStrategy(address)",
+ args: [cAMOStrategy.address],
+ },
+ {
+ // Set strategy as whitelisted one to mint OETHp tokens
+ contract: cOETHpVault,
+ signature: "addStrategyToMintWhitelist(address)",
+ args: [cAMOStrategy.address],
+ },
+ {
+ // Safe approve tokens
+ contract: cAMOStrategy,
+ signature: "mintInitialPosition()",
+ args: [],
+ },
+ {
+ // Safe approve tokens
+ contract: cAMOStrategy,
+ signature: "setAllowedPoolWethShareInterval(uint256,uint256)",
+ args: [utils.parseUnits("0.10", 18), utils.parseUnits("0.25", 18)],
+ },
+ {
+ // Set Harvester address to the multisig
+ contract: cAMOStrategy,
+ signature: "setHarvesterAddress(address)",
+ args: [addresses.multichainStrategist],
+ },
+ ],
+ };
+ }
+);
diff --git a/contracts/lib/rooster/openzeppelin-custom/contracts/utils/Panic.sol b/contracts/lib/rooster/openzeppelin-custom/contracts/utils/Panic.sol
new file mode 100644
index 0000000000..1c66c7c67e
--- /dev/null
+++ b/contracts/lib/rooster/openzeppelin-custom/contracts/utils/Panic.sol
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: MIT
+// OpenZeppelin Contracts (last updated v5.1.0) (utils/Panic.sol)
+
+pragma solidity ^0.8.20;
+
+/**
+ * @dev Helper library for emitting standardized panic codes.
+ *
+ * ```solidity
+ * contract Example {
+ * using Panic for uint256;
+ *
+ * // Use any of the declared internal constants
+ * function foo() { Panic.GENERIC.panic(); }
+ *
+ * // Alternatively
+ * function foo() { Panic.panic(Panic.GENERIC); }
+ * }
+ * ```
+ *
+ * Follows the list from https://github.com/ethereum/solidity/blob/v0.8.24/libsolutil/ErrorCodes.h[libsolutil].
+ *
+ * _Available since v5.1._
+ */
+// slither-disable-next-line unused-state
+library Panic {
+ /// @dev generic / unspecified error
+ uint256 internal constant GENERIC = 0x00;
+ /// @dev used by the assert() builtin
+ uint256 internal constant ASSERT = 0x01;
+ /// @dev arithmetic underflow or overflow
+ uint256 internal constant UNDER_OVERFLOW = 0x11;
+ /// @dev division or modulo by zero
+ uint256 internal constant DIVISION_BY_ZERO = 0x12;
+ /// @dev enum conversion error
+ uint256 internal constant ENUM_CONVERSION_ERROR = 0x21;
+ /// @dev invalid encoding in storage
+ uint256 internal constant STORAGE_ENCODING_ERROR = 0x22;
+ /// @dev empty array pop
+ uint256 internal constant EMPTY_ARRAY_POP = 0x31;
+ /// @dev array out of bounds access
+ uint256 internal constant ARRAY_OUT_OF_BOUNDS = 0x32;
+ /// @dev resource error (too large allocation or too large array)
+ uint256 internal constant RESOURCE_ERROR = 0x41;
+ /// @dev calling invalid internal function
+ uint256 internal constant INVALID_INTERNAL_FUNCTION = 0x51;
+
+ /// @dev Reverts with a panic code. Recommended to use with
+ /// the internal constants with predefined codes.
+ function panic(uint256 code) internal pure {
+ assembly ("memory-safe") {
+ mstore(0x00, 0x4e487b71)
+ mstore(0x20, code)
+ revert(0x1c, 0x24)
+ }
+ }
+}
\ No newline at end of file
diff --git a/contracts/lib/rooster/openzeppelin-custom/contracts/utils/math/Math.sol b/contracts/lib/rooster/openzeppelin-custom/contracts/utils/math/Math.sol
new file mode 100644
index 0000000000..0ff9234cff
--- /dev/null
+++ b/contracts/lib/rooster/openzeppelin-custom/contracts/utils/math/Math.sol
@@ -0,0 +1,749 @@
+// SPDX-License-Identifier: MIT
+// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/Math.sol)
+
+pragma solidity ^0.8.20;
+
+import {Panic} from "../Panic.sol";
+import {SafeCast} from "./SafeCast.sol";
+
+/**
+ * @dev Standard math utilities missing in the Solidity language.
+ */
+library Math {
+ enum Rounding {
+ Floor, // Toward negative infinity
+ Ceil, // Toward positive infinity
+ Trunc, // Toward zero
+ Expand // Away from zero
+ }
+
+ /**
+ * @dev Return the 512-bit addition of two uint256.
+ *
+ * The result is stored in two 256 variables such that sum = high * 2²⁵⁶ + low.
+ */
+ function add512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {
+ assembly ("memory-safe") {
+ low := add(a, b)
+ high := lt(low, a)
+ }
+ }
+
+ /**
+ * @dev Return the 512-bit multiplication of two uint256.
+ *
+ * The result is stored in two 256 variables such that product = high * 2²⁵⁶ + low.
+ */
+ function mul512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {
+ // 512-bit multiply [high low] = x * y. Compute the product mod 2²⁵⁶ and mod 2²⁵⁶ - 1, then use
+ // the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
+ // variables such that product = high * 2²⁵⁶ + low.
+ assembly ("memory-safe") {
+ let mm := mulmod(a, b, not(0))
+ low := mul(a, b)
+ high := sub(sub(mm, low), lt(mm, low))
+ }
+ }
+
+ /**
+ * @dev Returns the addition of two unsigned integers, with a success flag (no overflow).
+ */
+ function tryAdd(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
+ unchecked {
+ uint256 c = a + b;
+ success = c >= a;
+ result = c * SafeCast.toUint(success);
+ }
+ }
+
+ /**
+ * @dev Returns the subtraction of two unsigned integers, with a success flag (no overflow).
+ */
+ function trySub(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
+ unchecked {
+ uint256 c = a - b;
+ success = c <= a;
+ result = c * SafeCast.toUint(success);
+ }
+ }
+
+ /**
+ * @dev Returns the multiplication of two unsigned integers, with a success flag (no overflow).
+ */
+ function tryMul(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
+ unchecked {
+ uint256 c = a * b;
+ assembly ("memory-safe") {
+ // Only true when the multiplication doesn't overflow
+ // (c / a == b) || (a == 0)
+ success := or(eq(div(c, a), b), iszero(a))
+ }
+ // equivalent to: success ? c : 0
+ result = c * SafeCast.toUint(success);
+ }
+ }
+
+ /**
+ * @dev Returns the division of two unsigned integers, with a success flag (no division by zero).
+ */
+ function tryDiv(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
+ unchecked {
+ success = b > 0;
+ assembly ("memory-safe") {
+ // The `DIV` opcode returns zero when the denominator is 0.
+ result := div(a, b)
+ }
+ }
+ }
+
+ /**
+ * @dev Returns the remainder of dividing two unsigned integers, with a success flag (no division by zero).
+ */
+ function tryMod(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
+ unchecked {
+ success = b > 0;
+ assembly ("memory-safe") {
+ // The `MOD` opcode returns zero when the denominator is 0.
+ result := mod(a, b)
+ }
+ }
+ }
+
+ /**
+ * @dev Unsigned saturating addition, bounds to `2²⁵⁶ - 1` instead of overflowing.
+ */
+ function saturatingAdd(uint256 a, uint256 b) internal pure returns (uint256) {
+ (bool success, uint256 result) = tryAdd(a, b);
+ return ternary(success, result, type(uint256).max);
+ }
+
+ /**
+ * @dev Unsigned saturating subtraction, bounds to zero instead of overflowing.
+ */
+ function saturatingSub(uint256 a, uint256 b) internal pure returns (uint256) {
+ (, uint256 result) = trySub(a, b);
+ return result;
+ }
+
+ /**
+ * @dev Unsigned saturating multiplication, bounds to `2²⁵⁶ - 1` instead of overflowing.
+ */
+ function saturatingMul(uint256 a, uint256 b) internal pure returns (uint256) {
+ (bool success, uint256 result) = tryMul(a, b);
+ return ternary(success, result, type(uint256).max);
+ }
+
+ /**
+ * @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant.
+ *
+ * IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone.
+ * However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute
+ * one branch when needed, making this function more expensive.
+ */
+ function ternary(bool condition, uint256 a, uint256 b) internal pure returns (uint256) {
+ unchecked {
+ // branchless ternary works because:
+ // b ^ (a ^ b) == a
+ // b ^ 0 == b
+ return b ^ ((a ^ b) * SafeCast.toUint(condition));
+ }
+ }
+
+ /**
+ * @dev Returns the largest of two numbers.
+ */
+ function max(uint256 a, uint256 b) internal pure returns (uint256) {
+ return ternary(a > b, a, b);
+ }
+
+ /**
+ * @dev Returns the smallest of two numbers.
+ */
+ function min(uint256 a, uint256 b) internal pure returns (uint256) {
+ return ternary(a < b, a, b);
+ }
+
+ /**
+ * @dev Returns the average of two numbers. The result is rounded towards
+ * zero.
+ */
+ function average(uint256 a, uint256 b) internal pure returns (uint256) {
+ // (a + b) / 2 can overflow.
+ return (a & b) + (a ^ b) / 2;
+ }
+
+ /**
+ * @dev Returns the ceiling of the division of two numbers.
+ *
+ * This differs from standard division with `/` in that it rounds towards infinity instead
+ * of rounding towards zero.
+ */
+ function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
+ if (b == 0) {
+ // Guarantee the same behavior as in a regular Solidity division.
+ Panic.panic(Panic.DIVISION_BY_ZERO);
+ }
+
+ // The following calculation ensures accurate ceiling division without overflow.
+ // Since a is non-zero, (a - 1) / b will not overflow.
+ // The largest possible result occurs when (a - 1) / b is type(uint256).max,
+ // but the largest value we can obtain is type(uint256).max - 1, which happens
+ // when a = type(uint256).max and b = 1.
+ unchecked {
+ return SafeCast.toUint(a > 0) * ((a - 1) / b + 1);
+ }
+ }
+
+ /**
+ * @dev Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
+ * denominator == 0.
+ *
+ * Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
+ * Uniswap Labs also under MIT license.
+ */
+ function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
+ unchecked {
+ (uint256 high, uint256 low) = mul512(x, y);
+
+ // Handle non-overflow cases, 256 by 256 division.
+ if (high == 0) {
+ // Solidity will revert if denominator == 0, unlike the div opcode on its own.
+ // The surrounding unchecked block does not change this fact.
+ // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
+ return low / denominator;
+ }
+
+ // Make sure the result is less than 2²⁵⁶. Also prevents denominator == 0.
+ if (denominator <= high) {
+ Panic.panic(ternary(denominator == 0, Panic.DIVISION_BY_ZERO, Panic.UNDER_OVERFLOW));
+ }
+
+ ///////////////////////////////////////////////
+ // 512 by 256 division.
+ ///////////////////////////////////////////////
+
+ // Make division exact by subtracting the remainder from [high low].
+ uint256 remainder;
+ assembly ("memory-safe") {
+ // Compute remainder using mulmod.
+ remainder := mulmod(x, y, denominator)
+
+ // Subtract 256 bit number from 512 bit number.
+ high := sub(high, gt(remainder, low))
+ low := sub(low, remainder)
+ }
+
+ // Factor powers of two out of denominator and compute largest power of two divisor of denominator.
+ // Always >= 1. See https://cs.stackexchange.com/q/138556/92363.
+
+ uint256 twos = denominator & (0 - denominator);
+ assembly ("memory-safe") {
+ // Divide denominator by twos.
+ denominator := div(denominator, twos)
+
+ // Divide [high low] by twos.
+ low := div(low, twos)
+
+ // Flip twos such that it is 2²⁵⁶ / twos. If twos is zero, then it becomes one.
+ twos := add(div(sub(0, twos), twos), 1)
+ }
+
+ // Shift in bits from high into low.
+ low |= high * twos;
+
+ // Invert denominator mod 2²⁵⁶. Now that denominator is an odd number, it has an inverse modulo 2²⁵⁶ such
+ // that denominator * inv ≡ 1 mod 2²⁵⁶. Compute the inverse by starting with a seed that is correct for
+ // four bits. That is, denominator * inv ≡ 1 mod 2⁴.
+ uint256 inverse = (3 * denominator) ^ 2;
+
+ // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also
+ // works in modular arithmetic, doubling the correct bits in each step.
+ inverse *= 2 - denominator * inverse; // inverse mod 2⁸
+ inverse *= 2 - denominator * inverse; // inverse mod 2¹⁶
+ inverse *= 2 - denominator * inverse; // inverse mod 2³²
+ inverse *= 2 - denominator * inverse; // inverse mod 2⁶⁴
+ inverse *= 2 - denominator * inverse; // inverse mod 2¹²⁸
+ inverse *= 2 - denominator * inverse; // inverse mod 2²⁵⁶
+
+ // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
+ // This will give us the correct result modulo 2²⁵⁶. Since the preconditions guarantee that the outcome is
+ // less than 2²⁵⁶, this is the final result. We don't need to compute the high bits of the result and high
+ // is no longer required.
+ result = low * inverse;
+ return result;
+ }
+ }
+
+ /**
+ * @dev Calculates x * y / denominator with full precision, following the selected rounding direction.
+ */
+ function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
+ return mulDiv(x, y, denominator) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0);
+ }
+
+ /**
+ * @dev Calculates floor(x * y >> n) with full precision. Throws if result overflows a uint256.
+ */
+ function mulShr(uint256 x, uint256 y, uint8 n) internal pure returns (uint256 result) {
+ unchecked {
+ (uint256 high, uint256 low) = mul512(x, y);
+ if (high >= 1 << n) {
+ Panic.panic(Panic.UNDER_OVERFLOW);
+ }
+ return (high << (256 - n)) | (low >> n);
+ }
+ }
+
+ /**
+ * @dev Calculates x * y >> n with full precision, following the selected rounding direction.
+ */
+ function mulShr(uint256 x, uint256 y, uint8 n, Rounding rounding) internal pure returns (uint256) {
+ return mulShr(x, y, n) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, 1 << n) > 0);
+ }
+
+ /**
+ * @dev Calculate the modular multiplicative inverse of a number in Z/nZ.
+ *
+ * If n is a prime, then Z/nZ is a field. In that case all elements are inversible, except 0.
+ * If n is not a prime, then Z/nZ is not a field, and some elements might not be inversible.
+ *
+ * If the input value is not inversible, 0 is returned.
+ *
+ * NOTE: If you know for sure that n is (big) a prime, it may be cheaper to use Fermat's little theorem and get the
+ * inverse using `Math.modExp(a, n - 2, n)`. See {invModPrime}.
+ */
+ function invMod(uint256 a, uint256 n) internal pure returns (uint256) {
+ unchecked {
+ if (n == 0) return 0;
+
+ // The inverse modulo is calculated using the Extended Euclidean Algorithm (iterative version)
+ // Used to compute integers x and y such that: ax + ny = gcd(a, n).
+ // When the gcd is 1, then the inverse of a modulo n exists and it's x.
+ // ax + ny = 1
+ // ax = 1 + (-y)n
+ // ax ≡ 1 (mod n) # x is the inverse of a modulo n
+
+ // If the remainder is 0 the gcd is n right away.
+ uint256 remainder = a % n;
+ uint256 gcd = n;
+
+ // Therefore the initial coefficients are:
+ // ax + ny = gcd(a, n) = n
+ // 0a + 1n = n
+ int256 x = 0;
+ int256 y = 1;
+
+ while (remainder != 0) {
+ uint256 quotient = gcd / remainder;
+
+ (gcd, remainder) = (
+ // The old remainder is the next gcd to try.
+ remainder,
+ // Compute the next remainder.
+ // Can't overflow given that (a % gcd) * (gcd // (a % gcd)) <= gcd
+ // where gcd is at most n (capped to type(uint256).max)
+ gcd - remainder * quotient
+ );
+
+ (x, y) = (
+ // Increment the coefficient of a.
+ y,
+ // Decrement the coefficient of n.
+ // Can overflow, but the result is casted to uint256 so that the
+ // next value of y is "wrapped around" to a value between 0 and n - 1.
+ x - y * int256(quotient)
+ );
+ }
+
+ if (gcd != 1) return 0; // No inverse exists.
+ return ternary(x < 0, n - uint256(-x), uint256(x)); // Wrap the result if it's negative.
+ }
+ }
+
+ /**
+ * @dev Variant of {invMod}. More efficient, but only works if `p` is known to be a prime greater than `2`.
+ *
+ * From https://en.wikipedia.org/wiki/Fermat%27s_little_theorem[Fermat's little theorem], we know that if p is
+ * prime, then `a**(p-1) ≡ 1 mod p`. As a consequence, we have `a * a**(p-2) ≡ 1 mod p`, which means that
+ * `a**(p-2)` is the modular multiplicative inverse of a in Fp.
+ *
+ * NOTE: this function does NOT check that `p` is a prime greater than `2`.
+ */
+ function invModPrime(uint256 a, uint256 p) internal view returns (uint256) {
+ unchecked {
+ return Math.modExp(a, p - 2, p);
+ }
+ }
+
+ /**
+ * @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m)
+ *
+ * Requirements:
+ * - modulus can't be zero
+ * - underlying staticcall to precompile must succeed
+ *
+ * IMPORTANT: The result is only valid if the underlying call succeeds. When using this function, make
+ * sure the chain you're using it on supports the precompiled contract for modular exponentiation
+ * at address 0x05 as specified in https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise,
+ * the underlying function will succeed given the lack of a revert, but the result may be incorrectly
+ * interpreted as 0.
+ */
+ function modExp(uint256 b, uint256 e, uint256 m) internal view returns (uint256) {
+ (bool success, uint256 result) = tryModExp(b, e, m);
+ if (!success) {
+ Panic.panic(Panic.DIVISION_BY_ZERO);
+ }
+ return result;
+ }
+
+ /**
+ * @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m).
+ * It includes a success flag indicating if the operation succeeded. Operation will be marked as failed if trying
+ * to operate modulo 0 or if the underlying precompile reverted.
+ *
+ * IMPORTANT: The result is only valid if the success flag is true. When using this function, make sure the chain
+ * you're using it on supports the precompiled contract for modular exponentiation at address 0x05 as specified in
+ * https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise, the underlying function will succeed given the lack
+ * of a revert, but the result may be incorrectly interpreted as 0.
+ */
+ function tryModExp(uint256 b, uint256 e, uint256 m) internal view returns (bool success, uint256 result) {
+ if (m == 0) return (false, 0);
+ assembly ("memory-safe") {
+ let ptr := mload(0x40)
+ // | Offset | Content | Content (Hex) |
+ // |-----------|------------|--------------------------------------------------------------------|
+ // | 0x00:0x1f | size of b | 0x0000000000000000000000000000000000000000000000000000000000000020 |
+ // | 0x20:0x3f | size of e | 0x0000000000000000000000000000000000000000000000000000000000000020 |
+ // | 0x40:0x5f | size of m | 0x0000000000000000000000000000000000000000000000000000000000000020 |
+ // | 0x60:0x7f | value of b | 0x<.............................................................b> |
+ // | 0x80:0x9f | value of e | 0x<.............................................................e> |
+ // | 0xa0:0xbf | value of m | 0x<.............................................................m> |
+ mstore(ptr, 0x20)
+ mstore(add(ptr, 0x20), 0x20)
+ mstore(add(ptr, 0x40), 0x20)
+ mstore(add(ptr, 0x60), b)
+ mstore(add(ptr, 0x80), e)
+ mstore(add(ptr, 0xa0), m)
+
+ // Given the result < m, it's guaranteed to fit in 32 bytes,
+ // so we can use the memory scratch space located at offset 0.
+ success := staticcall(gas(), 0x05, ptr, 0xc0, 0x00, 0x20)
+ result := mload(0x00)
+ }
+ }
+
+ /**
+ * @dev Variant of {modExp} that supports inputs of arbitrary length.
+ */
+ function modExp(bytes memory b, bytes memory e, bytes memory m) internal view returns (bytes memory) {
+ (bool success, bytes memory result) = tryModExp(b, e, m);
+ if (!success) {
+ Panic.panic(Panic.DIVISION_BY_ZERO);
+ }
+ return result;
+ }
+
+ /**
+ * @dev Variant of {tryModExp} that supports inputs of arbitrary length.
+ */
+ function tryModExp(
+ bytes memory b,
+ bytes memory e,
+ bytes memory m
+ ) internal view returns (bool success, bytes memory result) {
+ if (_zeroBytes(m)) return (false, new bytes(0));
+
+ uint256 mLen = m.length;
+
+ // Encode call args in result and move the free memory pointer
+ result = abi.encodePacked(b.length, e.length, mLen, b, e, m);
+
+ assembly ("memory-safe") {
+ let dataPtr := add(result, 0x20)
+ // Write result on top of args to avoid allocating extra memory.
+ success := staticcall(gas(), 0x05, dataPtr, mload(result), dataPtr, mLen)
+ // Overwrite the length.
+ // result.length > returndatasize() is guaranteed because returndatasize() == m.length
+ mstore(result, mLen)
+ // Set the memory pointer after the returned data.
+ mstore(0x40, add(dataPtr, mLen))
+ }
+ }
+
+ /**
+ * @dev Returns whether the provided byte array is zero.
+ */
+ function _zeroBytes(bytes memory byteArray) private pure returns (bool) {
+ for (uint256 i = 0; i < byteArray.length; ++i) {
+ if (byteArray[i] != 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
+ * towards zero.
+ *
+ * This method is based on Newton's method for computing square roots; the algorithm is restricted to only
+ * using integer operations.
+ */
+ function sqrt(uint256 a) internal pure returns (uint256) {
+ unchecked {
+ // Take care of easy edge cases when a == 0 or a == 1
+ if (a <= 1) {
+ return a;
+ }
+
+ // In this function, we use Newton's method to get a root of `f(x) := x² - a`. It involves building a
+ // sequence x_n that converges toward sqrt(a). For each iteration x_n, we also define the error between
+ // the current value as `ε_n = | x_n - sqrt(a) |`.
+ //
+ // For our first estimation, we consider `e` the smallest power of 2 which is bigger than the square root
+ // of the target. (i.e. `2**(e-1) ≤ sqrt(a) < 2**e`). We know that `e ≤ 128` because `(2¹²⁸)² = 2²⁵⁶` is
+ // bigger than any uint256.
+ //
+ // By noticing that
+ // `2**(e-1) ≤ sqrt(a) < 2**e → (2**(e-1))² ≤ a < (2**e)² → 2**(2*e-2) ≤ a < 2**(2*e)`
+ // we can deduce that `e - 1` is `log2(a) / 2`. We can thus compute `x_n = 2**(e-1)` using a method similar
+ // to the msb function.
+ uint256 aa = a;
+ uint256 xn = 1;
+
+ if (aa >= (1 << 128)) {
+ aa >>= 128;
+ xn <<= 64;
+ }
+ if (aa >= (1 << 64)) {
+ aa >>= 64;
+ xn <<= 32;
+ }
+ if (aa >= (1 << 32)) {
+ aa >>= 32;
+ xn <<= 16;
+ }
+ if (aa >= (1 << 16)) {
+ aa >>= 16;
+ xn <<= 8;
+ }
+ if (aa >= (1 << 8)) {
+ aa >>= 8;
+ xn <<= 4;
+ }
+ if (aa >= (1 << 4)) {
+ aa >>= 4;
+ xn <<= 2;
+ }
+ if (aa >= (1 << 2)) {
+ xn <<= 1;
+ }
+
+ // We now have x_n such that `x_n = 2**(e-1) ≤ sqrt(a) < 2**e = 2 * x_n`. This implies ε_n ≤ 2**(e-1).
+ //
+ // We can refine our estimation by noticing that the middle of that interval minimizes the error.
+ // If we move x_n to equal 2**(e-1) + 2**(e-2), then we reduce the error to ε_n ≤ 2**(e-2).
+ // This is going to be our x_0 (and ε_0)
+ xn = (3 * xn) >> 1; // ε_0 := | x_0 - sqrt(a) | ≤ 2**(e-2)
+
+ // From here, Newton's method give us:
+ // x_{n+1} = (x_n + a / x_n) / 2
+ //
+ // One should note that:
+ // x_{n+1}² - a = ((x_n + a / x_n) / 2)² - a
+ // = ((x_n² + a) / (2 * x_n))² - a
+ // = (x_n⁴ + 2 * a * x_n² + a²) / (4 * x_n²) - a
+ // = (x_n⁴ + 2 * a * x_n² + a² - 4 * a * x_n²) / (4 * x_n²)
+ // = (x_n⁴ - 2 * a * x_n² + a²) / (4 * x_n²)
+ // = (x_n² - a)² / (2 * x_n)²
+ // = ((x_n² - a) / (2 * x_n))²
+ // ≥ 0
+ // Which proves that for all n ≥ 1, sqrt(a) ≤ x_n
+ //
+ // This gives us the proof of quadratic convergence of the sequence:
+ // ε_{n+1} = | x_{n+1} - sqrt(a) |
+ // = | (x_n + a / x_n) / 2 - sqrt(a) |
+ // = | (x_n² + a - 2*x_n*sqrt(a)) / (2 * x_n) |
+ // = | (x_n - sqrt(a))² / (2 * x_n) |
+ // = | ε_n² / (2 * x_n) |
+ // = ε_n² / | (2 * x_n) |
+ //
+ // For the first iteration, we have a special case where x_0 is known:
+ // ε_1 = ε_0² / | (2 * x_0) |
+ // ≤ (2**(e-2))² / (2 * (2**(e-1) + 2**(e-2)))
+ // ≤ 2**(2*e-4) / (3 * 2**(e-1))
+ // ≤ 2**(e-3) / 3
+ // ≤ 2**(e-3-log2(3))
+ // ≤ 2**(e-4.5)
+ //
+ // For the following iterations, we use the fact that, 2**(e-1) ≤ sqrt(a) ≤ x_n:
+ // ε_{n+1} = ε_n² / | (2 * x_n) |
+ // ≤ (2**(e-k))² / (2 * 2**(e-1))
+ // ≤ 2**(2*e-2*k) / 2**e
+ // ≤ 2**(e-2*k)
+ xn = (xn + a / xn) >> 1; // ε_1 := | x_1 - sqrt(a) | ≤ 2**(e-4.5) -- special case, see above
+ xn = (xn + a / xn) >> 1; // ε_2 := | x_2 - sqrt(a) | ≤ 2**(e-9) -- general case with k = 4.5
+ xn = (xn + a / xn) >> 1; // ε_3 := | x_3 - sqrt(a) | ≤ 2**(e-18) -- general case with k = 9
+ xn = (xn + a / xn) >> 1; // ε_4 := | x_4 - sqrt(a) | ≤ 2**(e-36) -- general case with k = 18
+ xn = (xn + a / xn) >> 1; // ε_5 := | x_5 - sqrt(a) | ≤ 2**(e-72) -- general case with k = 36
+ xn = (xn + a / xn) >> 1; // ε_6 := | x_6 - sqrt(a) | ≤ 2**(e-144) -- general case with k = 72
+
+ // Because e ≤ 128 (as discussed during the first estimation phase), we know have reached a precision
+ // ε_6 ≤ 2**(e-144) < 1. Given we're operating on integers, then we can ensure that xn is now either
+ // sqrt(a) or sqrt(a) + 1.
+ return xn - SafeCast.toUint(xn > a / xn);
+ }
+ }
+
+ /**
+ * @dev Calculates sqrt(a), following the selected rounding direction.
+ */
+ function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
+ unchecked {
+ uint256 result = sqrt(a);
+ return result + SafeCast.toUint(unsignedRoundsUp(rounding) && result * result < a);
+ }
+ }
+
+ /**
+ * @dev Return the log in base 2 of a positive value rounded towards zero.
+ * Returns 0 if given 0.
+ */
+ function log2(uint256 x) internal pure returns (uint256 r) {
+ // If value has upper 128 bits set, log2 result is at least 128
+ r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7;
+ // If upper 64 bits of 128-bit half set, add 64 to result
+ r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6;
+ // If upper 32 bits of 64-bit half set, add 32 to result
+ r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5;
+ // If upper 16 bits of 32-bit half set, add 16 to result
+ r |= SafeCast.toUint((x >> r) > 0xffff) << 4;
+ // If upper 8 bits of 16-bit half set, add 8 to result
+ r |= SafeCast.toUint((x >> r) > 0xff) << 3;
+ // If upper 4 bits of 8-bit half set, add 4 to result
+ r |= SafeCast.toUint((x >> r) > 0xf) << 2;
+
+ // Shifts value right by the current result and use it as an index into this lookup table:
+ //
+ // | x (4 bits) | index | table[index] = MSB position |
+ // |------------|---------|-----------------------------|
+ // | 0000 | 0 | table[0] = 0 |
+ // | 0001 | 1 | table[1] = 0 |
+ // | 0010 | 2 | table[2] = 1 |
+ // | 0011 | 3 | table[3] = 1 |
+ // | 0100 | 4 | table[4] = 2 |
+ // | 0101 | 5 | table[5] = 2 |
+ // | 0110 | 6 | table[6] = 2 |
+ // | 0111 | 7 | table[7] = 2 |
+ // | 1000 | 8 | table[8] = 3 |
+ // | 1001 | 9 | table[9] = 3 |
+ // | 1010 | 10 | table[10] = 3 |
+ // | 1011 | 11 | table[11] = 3 |
+ // | 1100 | 12 | table[12] = 3 |
+ // | 1101 | 13 | table[13] = 3 |
+ // | 1110 | 14 | table[14] = 3 |
+ // | 1111 | 15 | table[15] = 3 |
+ //
+ // The lookup table is represented as a 32-byte value with the MSB positions for 0-15 in the last 16 bytes.
+ assembly ("memory-safe") {
+ r := or(r, byte(shr(r, x), 0x0000010102020202030303030303030300000000000000000000000000000000))
+ }
+ }
+
+ /**
+ * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
+ * Returns 0 if given 0.
+ */
+ function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
+ unchecked {
+ uint256 result = log2(value);
+ return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << result < value);
+ }
+ }
+
+ /**
+ * @dev Return the log in base 10 of a positive value rounded towards zero.
+ * Returns 0 if given 0.
+ */
+ function log10(uint256 value) internal pure returns (uint256) {
+ uint256 result = 0;
+ unchecked {
+ if (value >= 10 ** 64) {
+ value /= 10 ** 64;
+ result += 64;
+ }
+ if (value >= 10 ** 32) {
+ value /= 10 ** 32;
+ result += 32;
+ }
+ if (value >= 10 ** 16) {
+ value /= 10 ** 16;
+ result += 16;
+ }
+ if (value >= 10 ** 8) {
+ value /= 10 ** 8;
+ result += 8;
+ }
+ if (value >= 10 ** 4) {
+ value /= 10 ** 4;
+ result += 4;
+ }
+ if (value >= 10 ** 2) {
+ value /= 10 ** 2;
+ result += 2;
+ }
+ if (value >= 10 ** 1) {
+ result += 1;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
+ * Returns 0 if given 0.
+ */
+ function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
+ unchecked {
+ uint256 result = log10(value);
+ return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 10 ** result < value);
+ }
+ }
+
+ /**
+ * @dev Return the log in base 256 of a positive value rounded towards zero.
+ * Returns 0 if given 0.
+ *
+ * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
+ */
+ function log256(uint256 x) internal pure returns (uint256 r) {
+ // If value has upper 128 bits set, log2 result is at least 128
+ r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7;
+ // If upper 64 bits of 128-bit half set, add 64 to result
+ r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6;
+ // If upper 32 bits of 64-bit half set, add 32 to result
+ r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5;
+ // If upper 16 bits of 32-bit half set, add 16 to result
+ r |= SafeCast.toUint((x >> r) > 0xffff) << 4;
+ // Add 1 if upper 8 bits of 16-bit half set, and divide accumulated result by 8
+ return (r >> 3) | SafeCast.toUint((x >> r) > 0xff);
+ }
+
+ /**
+ * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
+ * Returns 0 if given 0.
+ */
+ function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
+ unchecked {
+ uint256 result = log256(value);
+ return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << (result << 3) < value);
+ }
+ }
+
+ /**
+ * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
+ */
+ function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
+ return uint8(rounding) % 2 == 1;
+ }
+}
\ No newline at end of file
diff --git a/contracts/lib/rooster/openzeppelin-custom/contracts/utils/math/SafeCast.sol b/contracts/lib/rooster/openzeppelin-custom/contracts/utils/math/SafeCast.sol
new file mode 100644
index 0000000000..85cc552aa3
--- /dev/null
+++ b/contracts/lib/rooster/openzeppelin-custom/contracts/utils/math/SafeCast.sol
@@ -0,0 +1,1162 @@
+// SPDX-License-Identifier: MIT
+// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/SafeCast.sol)
+// This file was procedurally generated from scripts/generate/templates/SafeCast.js.
+
+pragma solidity ^0.8.20;
+
+/**
+ * @dev Wrappers over Solidity's uintXX/intXX/bool casting operators with added overflow
+ * checks.
+ *
+ * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
+ * easily result in undesired exploitation or bugs, since developers usually
+ * assume that overflows raise errors. `SafeCast` restores this intuition by
+ * reverting the transaction when such an operation overflows.
+ *
+ * Using this library instead of the unchecked operations eliminates an entire
+ * class of bugs, so it's recommended to use it always.
+ */
+library SafeCast {
+ /**
+ * @dev Value doesn't fit in an uint of `bits` size.
+ */
+ error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value);
+
+ /**
+ * @dev An int value doesn't fit in an uint of `bits` size.
+ */
+ error SafeCastOverflowedIntToUint(int256 value);
+
+ /**
+ * @dev Value doesn't fit in an int of `bits` size.
+ */
+ error SafeCastOverflowedIntDowncast(uint8 bits, int256 value);
+
+ /**
+ * @dev An uint value doesn't fit in an int of `bits` size.
+ */
+ error SafeCastOverflowedUintToInt(uint256 value);
+
+ /**
+ * @dev Returns the downcasted uint248 from uint256, reverting on
+ * overflow (when the input is greater than largest uint248).
+ *
+ * Counterpart to Solidity's `uint248` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 248 bits
+ */
+ function toUint248(uint256 value) internal pure returns (uint248) {
+ if (value > type(uint248).max) {
+ revert SafeCastOverflowedUintDowncast(248, value);
+ }
+ return uint248(value);
+ }
+
+ /**
+ * @dev Returns the downcasted uint240 from uint256, reverting on
+ * overflow (when the input is greater than largest uint240).
+ *
+ * Counterpart to Solidity's `uint240` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 240 bits
+ */
+ function toUint240(uint256 value) internal pure returns (uint240) {
+ if (value > type(uint240).max) {
+ revert SafeCastOverflowedUintDowncast(240, value);
+ }
+ return uint240(value);
+ }
+
+ /**
+ * @dev Returns the downcasted uint232 from uint256, reverting on
+ * overflow (when the input is greater than largest uint232).
+ *
+ * Counterpart to Solidity's `uint232` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 232 bits
+ */
+ function toUint232(uint256 value) internal pure returns (uint232) {
+ if (value > type(uint232).max) {
+ revert SafeCastOverflowedUintDowncast(232, value);
+ }
+ return uint232(value);
+ }
+
+ /**
+ * @dev Returns the downcasted uint224 from uint256, reverting on
+ * overflow (when the input is greater than largest uint224).
+ *
+ * Counterpart to Solidity's `uint224` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 224 bits
+ */
+ function toUint224(uint256 value) internal pure returns (uint224) {
+ if (value > type(uint224).max) {
+ revert SafeCastOverflowedUintDowncast(224, value);
+ }
+ return uint224(value);
+ }
+
+ /**
+ * @dev Returns the downcasted uint216 from uint256, reverting on
+ * overflow (when the input is greater than largest uint216).
+ *
+ * Counterpart to Solidity's `uint216` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 216 bits
+ */
+ function toUint216(uint256 value) internal pure returns (uint216) {
+ if (value > type(uint216).max) {
+ revert SafeCastOverflowedUintDowncast(216, value);
+ }
+ return uint216(value);
+ }
+
+ /**
+ * @dev Returns the downcasted uint208 from uint256, reverting on
+ * overflow (when the input is greater than largest uint208).
+ *
+ * Counterpart to Solidity's `uint208` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 208 bits
+ */
+ function toUint208(uint256 value) internal pure returns (uint208) {
+ if (value > type(uint208).max) {
+ revert SafeCastOverflowedUintDowncast(208, value);
+ }
+ return uint208(value);
+ }
+
+ /**
+ * @dev Returns the downcasted uint200 from uint256, reverting on
+ * overflow (when the input is greater than largest uint200).
+ *
+ * Counterpart to Solidity's `uint200` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 200 bits
+ */
+ function toUint200(uint256 value) internal pure returns (uint200) {
+ if (value > type(uint200).max) {
+ revert SafeCastOverflowedUintDowncast(200, value);
+ }
+ return uint200(value);
+ }
+
+ /**
+ * @dev Returns the downcasted uint192 from uint256, reverting on
+ * overflow (when the input is greater than largest uint192).
+ *
+ * Counterpart to Solidity's `uint192` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 192 bits
+ */
+ function toUint192(uint256 value) internal pure returns (uint192) {
+ if (value > type(uint192).max) {
+ revert SafeCastOverflowedUintDowncast(192, value);
+ }
+ return uint192(value);
+ }
+
+ /**
+ * @dev Returns the downcasted uint184 from uint256, reverting on
+ * overflow (when the input is greater than largest uint184).
+ *
+ * Counterpart to Solidity's `uint184` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 184 bits
+ */
+ function toUint184(uint256 value) internal pure returns (uint184) {
+ if (value > type(uint184).max) {
+ revert SafeCastOverflowedUintDowncast(184, value);
+ }
+ return uint184(value);
+ }
+
+ /**
+ * @dev Returns the downcasted uint176 from uint256, reverting on
+ * overflow (when the input is greater than largest uint176).
+ *
+ * Counterpart to Solidity's `uint176` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 176 bits
+ */
+ function toUint176(uint256 value) internal pure returns (uint176) {
+ if (value > type(uint176).max) {
+ revert SafeCastOverflowedUintDowncast(176, value);
+ }
+ return uint176(value);
+ }
+
+ /**
+ * @dev Returns the downcasted uint168 from uint256, reverting on
+ * overflow (when the input is greater than largest uint168).
+ *
+ * Counterpart to Solidity's `uint168` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 168 bits
+ */
+ function toUint168(uint256 value) internal pure returns (uint168) {
+ if (value > type(uint168).max) {
+ revert SafeCastOverflowedUintDowncast(168, value);
+ }
+ return uint168(value);
+ }
+
+ /**
+ * @dev Returns the downcasted uint160 from uint256, reverting on
+ * overflow (when the input is greater than largest uint160).
+ *
+ * Counterpart to Solidity's `uint160` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 160 bits
+ */
+ function toUint160(uint256 value) internal pure returns (uint160) {
+ if (value > type(uint160).max) {
+ revert SafeCastOverflowedUintDowncast(160, value);
+ }
+ return uint160(value);
+ }
+
+ /**
+ * @dev Returns the downcasted uint152 from uint256, reverting on
+ * overflow (when the input is greater than largest uint152).
+ *
+ * Counterpart to Solidity's `uint152` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 152 bits
+ */
+ function toUint152(uint256 value) internal pure returns (uint152) {
+ if (value > type(uint152).max) {
+ revert SafeCastOverflowedUintDowncast(152, value);
+ }
+ return uint152(value);
+ }
+
+ /**
+ * @dev Returns the downcasted uint144 from uint256, reverting on
+ * overflow (when the input is greater than largest uint144).
+ *
+ * Counterpart to Solidity's `uint144` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 144 bits
+ */
+ function toUint144(uint256 value) internal pure returns (uint144) {
+ if (value > type(uint144).max) {
+ revert SafeCastOverflowedUintDowncast(144, value);
+ }
+ return uint144(value);
+ }
+
+ /**
+ * @dev Returns the downcasted uint136 from uint256, reverting on
+ * overflow (when the input is greater than largest uint136).
+ *
+ * Counterpart to Solidity's `uint136` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 136 bits
+ */
+ function toUint136(uint256 value) internal pure returns (uint136) {
+ if (value > type(uint136).max) {
+ revert SafeCastOverflowedUintDowncast(136, value);
+ }
+ return uint136(value);
+ }
+
+ /**
+ * @dev Returns the downcasted uint128 from uint256, reverting on
+ * overflow (when the input is greater than largest uint128).
+ *
+ * Counterpart to Solidity's `uint128` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 128 bits
+ */
+ function toUint128(uint256 value) internal pure returns (uint128) {
+ if (value > type(uint128).max) {
+ revert SafeCastOverflowedUintDowncast(128, value);
+ }
+ return uint128(value);
+ }
+
+ /**
+ * @dev Returns the downcasted uint120 from uint256, reverting on
+ * overflow (when the input is greater than largest uint120).
+ *
+ * Counterpart to Solidity's `uint120` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 120 bits
+ */
+ function toUint120(uint256 value) internal pure returns (uint120) {
+ if (value > type(uint120).max) {
+ revert SafeCastOverflowedUintDowncast(120, value);
+ }
+ return uint120(value);
+ }
+
+ /**
+ * @dev Returns the downcasted uint112 from uint256, reverting on
+ * overflow (when the input is greater than largest uint112).
+ *
+ * Counterpart to Solidity's `uint112` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 112 bits
+ */
+ function toUint112(uint256 value) internal pure returns (uint112) {
+ if (value > type(uint112).max) {
+ revert SafeCastOverflowedUintDowncast(112, value);
+ }
+ return uint112(value);
+ }
+
+ /**
+ * @dev Returns the downcasted uint104 from uint256, reverting on
+ * overflow (when the input is greater than largest uint104).
+ *
+ * Counterpart to Solidity's `uint104` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 104 bits
+ */
+ function toUint104(uint256 value) internal pure returns (uint104) {
+ if (value > type(uint104).max) {
+ revert SafeCastOverflowedUintDowncast(104, value);
+ }
+ return uint104(value);
+ }
+
+ /**
+ * @dev Returns the downcasted uint96 from uint256, reverting on
+ * overflow (when the input is greater than largest uint96).
+ *
+ * Counterpart to Solidity's `uint96` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 96 bits
+ */
+ function toUint96(uint256 value) internal pure returns (uint96) {
+ if (value > type(uint96).max) {
+ revert SafeCastOverflowedUintDowncast(96, value);
+ }
+ return uint96(value);
+ }
+
+ /**
+ * @dev Returns the downcasted uint88 from uint256, reverting on
+ * overflow (when the input is greater than largest uint88).
+ *
+ * Counterpart to Solidity's `uint88` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 88 bits
+ */
+ function toUint88(uint256 value) internal pure returns (uint88) {
+ if (value > type(uint88).max) {
+ revert SafeCastOverflowedUintDowncast(88, value);
+ }
+ return uint88(value);
+ }
+
+ /**
+ * @dev Returns the downcasted uint80 from uint256, reverting on
+ * overflow (when the input is greater than largest uint80).
+ *
+ * Counterpart to Solidity's `uint80` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 80 bits
+ */
+ function toUint80(uint256 value) internal pure returns (uint80) {
+ if (value > type(uint80).max) {
+ revert SafeCastOverflowedUintDowncast(80, value);
+ }
+ return uint80(value);
+ }
+
+ /**
+ * @dev Returns the downcasted uint72 from uint256, reverting on
+ * overflow (when the input is greater than largest uint72).
+ *
+ * Counterpart to Solidity's `uint72` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 72 bits
+ */
+ function toUint72(uint256 value) internal pure returns (uint72) {
+ if (value > type(uint72).max) {
+ revert SafeCastOverflowedUintDowncast(72, value);
+ }
+ return uint72(value);
+ }
+
+ /**
+ * @dev Returns the downcasted uint64 from uint256, reverting on
+ * overflow (when the input is greater than largest uint64).
+ *
+ * Counterpart to Solidity's `uint64` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 64 bits
+ */
+ function toUint64(uint256 value) internal pure returns (uint64) {
+ if (value > type(uint64).max) {
+ revert SafeCastOverflowedUintDowncast(64, value);
+ }
+ return uint64(value);
+ }
+
+ /**
+ * @dev Returns the downcasted uint56 from uint256, reverting on
+ * overflow (when the input is greater than largest uint56).
+ *
+ * Counterpart to Solidity's `uint56` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 56 bits
+ */
+ function toUint56(uint256 value) internal pure returns (uint56) {
+ if (value > type(uint56).max) {
+ revert SafeCastOverflowedUintDowncast(56, value);
+ }
+ return uint56(value);
+ }
+
+ /**
+ * @dev Returns the downcasted uint48 from uint256, reverting on
+ * overflow (when the input is greater than largest uint48).
+ *
+ * Counterpart to Solidity's `uint48` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 48 bits
+ */
+ function toUint48(uint256 value) internal pure returns (uint48) {
+ if (value > type(uint48).max) {
+ revert SafeCastOverflowedUintDowncast(48, value);
+ }
+ return uint48(value);
+ }
+
+ /**
+ * @dev Returns the downcasted uint40 from uint256, reverting on
+ * overflow (when the input is greater than largest uint40).
+ *
+ * Counterpart to Solidity's `uint40` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 40 bits
+ */
+ function toUint40(uint256 value) internal pure returns (uint40) {
+ if (value > type(uint40).max) {
+ revert SafeCastOverflowedUintDowncast(40, value);
+ }
+ return uint40(value);
+ }
+
+ /**
+ * @dev Returns the downcasted uint32 from uint256, reverting on
+ * overflow (when the input is greater than largest uint32).
+ *
+ * Counterpart to Solidity's `uint32` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 32 bits
+ */
+ function toUint32(uint256 value) internal pure returns (uint32) {
+ if (value > type(uint32).max) {
+ revert SafeCastOverflowedUintDowncast(32, value);
+ }
+ return uint32(value);
+ }
+
+ /**
+ * @dev Returns the downcasted uint24 from uint256, reverting on
+ * overflow (when the input is greater than largest uint24).
+ *
+ * Counterpart to Solidity's `uint24` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 24 bits
+ */
+ function toUint24(uint256 value) internal pure returns (uint24) {
+ if (value > type(uint24).max) {
+ revert SafeCastOverflowedUintDowncast(24, value);
+ }
+ return uint24(value);
+ }
+
+ /**
+ * @dev Returns the downcasted uint16 from uint256, reverting on
+ * overflow (when the input is greater than largest uint16).
+ *
+ * Counterpart to Solidity's `uint16` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 16 bits
+ */
+ function toUint16(uint256 value) internal pure returns (uint16) {
+ if (value > type(uint16).max) {
+ revert SafeCastOverflowedUintDowncast(16, value);
+ }
+ return uint16(value);
+ }
+
+ /**
+ * @dev Returns the downcasted uint8 from uint256, reverting on
+ * overflow (when the input is greater than largest uint8).
+ *
+ * Counterpart to Solidity's `uint8` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 8 bits
+ */
+ function toUint8(uint256 value) internal pure returns (uint8) {
+ if (value > type(uint8).max) {
+ revert SafeCastOverflowedUintDowncast(8, value);
+ }
+ return uint8(value);
+ }
+
+ /**
+ * @dev Converts a signed int256 into an unsigned uint256.
+ *
+ * Requirements:
+ *
+ * - input must be greater than or equal to 0.
+ */
+ function toUint256(int256 value) internal pure returns (uint256) {
+ if (value < 0) {
+ revert SafeCastOverflowedIntToUint(value);
+ }
+ return uint256(value);
+ }
+
+ /**
+ * @dev Returns the downcasted int248 from int256, reverting on
+ * overflow (when the input is less than smallest int248 or
+ * greater than largest int248).
+ *
+ * Counterpart to Solidity's `int248` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 248 bits
+ */
+ function toInt248(int256 value) internal pure returns (int248 downcasted) {
+ downcasted = int248(value);
+ if (downcasted != value) {
+ revert SafeCastOverflowedIntDowncast(248, value);
+ }
+ }
+
+ /**
+ * @dev Returns the downcasted int240 from int256, reverting on
+ * overflow (when the input is less than smallest int240 or
+ * greater than largest int240).
+ *
+ * Counterpart to Solidity's `int240` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 240 bits
+ */
+ function toInt240(int256 value) internal pure returns (int240 downcasted) {
+ downcasted = int240(value);
+ if (downcasted != value) {
+ revert SafeCastOverflowedIntDowncast(240, value);
+ }
+ }
+
+ /**
+ * @dev Returns the downcasted int232 from int256, reverting on
+ * overflow (when the input is less than smallest int232 or
+ * greater than largest int232).
+ *
+ * Counterpart to Solidity's `int232` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 232 bits
+ */
+ function toInt232(int256 value) internal pure returns (int232 downcasted) {
+ downcasted = int232(value);
+ if (downcasted != value) {
+ revert SafeCastOverflowedIntDowncast(232, value);
+ }
+ }
+
+ /**
+ * @dev Returns the downcasted int224 from int256, reverting on
+ * overflow (when the input is less than smallest int224 or
+ * greater than largest int224).
+ *
+ * Counterpart to Solidity's `int224` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 224 bits
+ */
+ function toInt224(int256 value) internal pure returns (int224 downcasted) {
+ downcasted = int224(value);
+ if (downcasted != value) {
+ revert SafeCastOverflowedIntDowncast(224, value);
+ }
+ }
+
+ /**
+ * @dev Returns the downcasted int216 from int256, reverting on
+ * overflow (when the input is less than smallest int216 or
+ * greater than largest int216).
+ *
+ * Counterpart to Solidity's `int216` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 216 bits
+ */
+ function toInt216(int256 value) internal pure returns (int216 downcasted) {
+ downcasted = int216(value);
+ if (downcasted != value) {
+ revert SafeCastOverflowedIntDowncast(216, value);
+ }
+ }
+
+ /**
+ * @dev Returns the downcasted int208 from int256, reverting on
+ * overflow (when the input is less than smallest int208 or
+ * greater than largest int208).
+ *
+ * Counterpart to Solidity's `int208` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 208 bits
+ */
+ function toInt208(int256 value) internal pure returns (int208 downcasted) {
+ downcasted = int208(value);
+ if (downcasted != value) {
+ revert SafeCastOverflowedIntDowncast(208, value);
+ }
+ }
+
+ /**
+ * @dev Returns the downcasted int200 from int256, reverting on
+ * overflow (when the input is less than smallest int200 or
+ * greater than largest int200).
+ *
+ * Counterpart to Solidity's `int200` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 200 bits
+ */
+ function toInt200(int256 value) internal pure returns (int200 downcasted) {
+ downcasted = int200(value);
+ if (downcasted != value) {
+ revert SafeCastOverflowedIntDowncast(200, value);
+ }
+ }
+
+ /**
+ * @dev Returns the downcasted int192 from int256, reverting on
+ * overflow (when the input is less than smallest int192 or
+ * greater than largest int192).
+ *
+ * Counterpart to Solidity's `int192` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 192 bits
+ */
+ function toInt192(int256 value) internal pure returns (int192 downcasted) {
+ downcasted = int192(value);
+ if (downcasted != value) {
+ revert SafeCastOverflowedIntDowncast(192, value);
+ }
+ }
+
+ /**
+ * @dev Returns the downcasted int184 from int256, reverting on
+ * overflow (when the input is less than smallest int184 or
+ * greater than largest int184).
+ *
+ * Counterpart to Solidity's `int184` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 184 bits
+ */
+ function toInt184(int256 value) internal pure returns (int184 downcasted) {
+ downcasted = int184(value);
+ if (downcasted != value) {
+ revert SafeCastOverflowedIntDowncast(184, value);
+ }
+ }
+
+ /**
+ * @dev Returns the downcasted int176 from int256, reverting on
+ * overflow (when the input is less than smallest int176 or
+ * greater than largest int176).
+ *
+ * Counterpart to Solidity's `int176` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 176 bits
+ */
+ function toInt176(int256 value) internal pure returns (int176 downcasted) {
+ downcasted = int176(value);
+ if (downcasted != value) {
+ revert SafeCastOverflowedIntDowncast(176, value);
+ }
+ }
+
+ /**
+ * @dev Returns the downcasted int168 from int256, reverting on
+ * overflow (when the input is less than smallest int168 or
+ * greater than largest int168).
+ *
+ * Counterpart to Solidity's `int168` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 168 bits
+ */
+ function toInt168(int256 value) internal pure returns (int168 downcasted) {
+ downcasted = int168(value);
+ if (downcasted != value) {
+ revert SafeCastOverflowedIntDowncast(168, value);
+ }
+ }
+
+ /**
+ * @dev Returns the downcasted int160 from int256, reverting on
+ * overflow (when the input is less than smallest int160 or
+ * greater than largest int160).
+ *
+ * Counterpart to Solidity's `int160` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 160 bits
+ */
+ function toInt160(int256 value) internal pure returns (int160 downcasted) {
+ downcasted = int160(value);
+ if (downcasted != value) {
+ revert SafeCastOverflowedIntDowncast(160, value);
+ }
+ }
+
+ /**
+ * @dev Returns the downcasted int152 from int256, reverting on
+ * overflow (when the input is less than smallest int152 or
+ * greater than largest int152).
+ *
+ * Counterpart to Solidity's `int152` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 152 bits
+ */
+ function toInt152(int256 value) internal pure returns (int152 downcasted) {
+ downcasted = int152(value);
+ if (downcasted != value) {
+ revert SafeCastOverflowedIntDowncast(152, value);
+ }
+ }
+
+ /**
+ * @dev Returns the downcasted int144 from int256, reverting on
+ * overflow (when the input is less than smallest int144 or
+ * greater than largest int144).
+ *
+ * Counterpart to Solidity's `int144` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 144 bits
+ */
+ function toInt144(int256 value) internal pure returns (int144 downcasted) {
+ downcasted = int144(value);
+ if (downcasted != value) {
+ revert SafeCastOverflowedIntDowncast(144, value);
+ }
+ }
+
+ /**
+ * @dev Returns the downcasted int136 from int256, reverting on
+ * overflow (when the input is less than smallest int136 or
+ * greater than largest int136).
+ *
+ * Counterpart to Solidity's `int136` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 136 bits
+ */
+ function toInt136(int256 value) internal pure returns (int136 downcasted) {
+ downcasted = int136(value);
+ if (downcasted != value) {
+ revert SafeCastOverflowedIntDowncast(136, value);
+ }
+ }
+
+ /**
+ * @dev Returns the downcasted int128 from int256, reverting on
+ * overflow (when the input is less than smallest int128 or
+ * greater than largest int128).
+ *
+ * Counterpart to Solidity's `int128` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 128 bits
+ */
+ function toInt128(int256 value) internal pure returns (int128 downcasted) {
+ downcasted = int128(value);
+ if (downcasted != value) {
+ revert SafeCastOverflowedIntDowncast(128, value);
+ }
+ }
+
+ /**
+ * @dev Returns the downcasted int120 from int256, reverting on
+ * overflow (when the input is less than smallest int120 or
+ * greater than largest int120).
+ *
+ * Counterpart to Solidity's `int120` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 120 bits
+ */
+ function toInt120(int256 value) internal pure returns (int120 downcasted) {
+ downcasted = int120(value);
+ if (downcasted != value) {
+ revert SafeCastOverflowedIntDowncast(120, value);
+ }
+ }
+
+ /**
+ * @dev Returns the downcasted int112 from int256, reverting on
+ * overflow (when the input is less than smallest int112 or
+ * greater than largest int112).
+ *
+ * Counterpart to Solidity's `int112` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 112 bits
+ */
+ function toInt112(int256 value) internal pure returns (int112 downcasted) {
+ downcasted = int112(value);
+ if (downcasted != value) {
+ revert SafeCastOverflowedIntDowncast(112, value);
+ }
+ }
+
+ /**
+ * @dev Returns the downcasted int104 from int256, reverting on
+ * overflow (when the input is less than smallest int104 or
+ * greater than largest int104).
+ *
+ * Counterpart to Solidity's `int104` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 104 bits
+ */
+ function toInt104(int256 value) internal pure returns (int104 downcasted) {
+ downcasted = int104(value);
+ if (downcasted != value) {
+ revert SafeCastOverflowedIntDowncast(104, value);
+ }
+ }
+
+ /**
+ * @dev Returns the downcasted int96 from int256, reverting on
+ * overflow (when the input is less than smallest int96 or
+ * greater than largest int96).
+ *
+ * Counterpart to Solidity's `int96` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 96 bits
+ */
+ function toInt96(int256 value) internal pure returns (int96 downcasted) {
+ downcasted = int96(value);
+ if (downcasted != value) {
+ revert SafeCastOverflowedIntDowncast(96, value);
+ }
+ }
+
+ /**
+ * @dev Returns the downcasted int88 from int256, reverting on
+ * overflow (when the input is less than smallest int88 or
+ * greater than largest int88).
+ *
+ * Counterpart to Solidity's `int88` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 88 bits
+ */
+ function toInt88(int256 value) internal pure returns (int88 downcasted) {
+ downcasted = int88(value);
+ if (downcasted != value) {
+ revert SafeCastOverflowedIntDowncast(88, value);
+ }
+ }
+
+ /**
+ * @dev Returns the downcasted int80 from int256, reverting on
+ * overflow (when the input is less than smallest int80 or
+ * greater than largest int80).
+ *
+ * Counterpart to Solidity's `int80` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 80 bits
+ */
+ function toInt80(int256 value) internal pure returns (int80 downcasted) {
+ downcasted = int80(value);
+ if (downcasted != value) {
+ revert SafeCastOverflowedIntDowncast(80, value);
+ }
+ }
+
+ /**
+ * @dev Returns the downcasted int72 from int256, reverting on
+ * overflow (when the input is less than smallest int72 or
+ * greater than largest int72).
+ *
+ * Counterpart to Solidity's `int72` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 72 bits
+ */
+ function toInt72(int256 value) internal pure returns (int72 downcasted) {
+ downcasted = int72(value);
+ if (downcasted != value) {
+ revert SafeCastOverflowedIntDowncast(72, value);
+ }
+ }
+
+ /**
+ * @dev Returns the downcasted int64 from int256, reverting on
+ * overflow (when the input is less than smallest int64 or
+ * greater than largest int64).
+ *
+ * Counterpart to Solidity's `int64` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 64 bits
+ */
+ function toInt64(int256 value) internal pure returns (int64 downcasted) {
+ downcasted = int64(value);
+ if (downcasted != value) {
+ revert SafeCastOverflowedIntDowncast(64, value);
+ }
+ }
+
+ /**
+ * @dev Returns the downcasted int56 from int256, reverting on
+ * overflow (when the input is less than smallest int56 or
+ * greater than largest int56).
+ *
+ * Counterpart to Solidity's `int56` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 56 bits
+ */
+ function toInt56(int256 value) internal pure returns (int56 downcasted) {
+ downcasted = int56(value);
+ if (downcasted != value) {
+ revert SafeCastOverflowedIntDowncast(56, value);
+ }
+ }
+
+ /**
+ * @dev Returns the downcasted int48 from int256, reverting on
+ * overflow (when the input is less than smallest int48 or
+ * greater than largest int48).
+ *
+ * Counterpart to Solidity's `int48` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 48 bits
+ */
+ function toInt48(int256 value) internal pure returns (int48 downcasted) {
+ downcasted = int48(value);
+ if (downcasted != value) {
+ revert SafeCastOverflowedIntDowncast(48, value);
+ }
+ }
+
+ /**
+ * @dev Returns the downcasted int40 from int256, reverting on
+ * overflow (when the input is less than smallest int40 or
+ * greater than largest int40).
+ *
+ * Counterpart to Solidity's `int40` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 40 bits
+ */
+ function toInt40(int256 value) internal pure returns (int40 downcasted) {
+ downcasted = int40(value);
+ if (downcasted != value) {
+ revert SafeCastOverflowedIntDowncast(40, value);
+ }
+ }
+
+ /**
+ * @dev Returns the downcasted int32 from int256, reverting on
+ * overflow (when the input is less than smallest int32 or
+ * greater than largest int32).
+ *
+ * Counterpart to Solidity's `int32` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 32 bits
+ */
+ function toInt32(int256 value) internal pure returns (int32 downcasted) {
+ downcasted = int32(value);
+ if (downcasted != value) {
+ revert SafeCastOverflowedIntDowncast(32, value);
+ }
+ }
+
+ /**
+ * @dev Returns the downcasted int24 from int256, reverting on
+ * overflow (when the input is less than smallest int24 or
+ * greater than largest int24).
+ *
+ * Counterpart to Solidity's `int24` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 24 bits
+ */
+ function toInt24(int256 value) internal pure returns (int24 downcasted) {
+ downcasted = int24(value);
+ if (downcasted != value) {
+ revert SafeCastOverflowedIntDowncast(24, value);
+ }
+ }
+
+ /**
+ * @dev Returns the downcasted int16 from int256, reverting on
+ * overflow (when the input is less than smallest int16 or
+ * greater than largest int16).
+ *
+ * Counterpart to Solidity's `int16` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 16 bits
+ */
+ function toInt16(int256 value) internal pure returns (int16 downcasted) {
+ downcasted = int16(value);
+ if (downcasted != value) {
+ revert SafeCastOverflowedIntDowncast(16, value);
+ }
+ }
+
+ /**
+ * @dev Returns the downcasted int8 from int256, reverting on
+ * overflow (when the input is less than smallest int8 or
+ * greater than largest int8).
+ *
+ * Counterpart to Solidity's `int8` operator.
+ *
+ * Requirements:
+ *
+ * - input must fit into 8 bits
+ */
+ function toInt8(int256 value) internal pure returns (int8 downcasted) {
+ downcasted = int8(value);
+ if (downcasted != value) {
+ revert SafeCastOverflowedIntDowncast(8, value);
+ }
+ }
+
+ /**
+ * @dev Converts an unsigned uint256 into a signed int256.
+ *
+ * Requirements:
+ *
+ * - input must be less than or equal to maxInt256.
+ */
+ function toInt256(uint256 value) internal pure returns (int256) {
+ // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
+ if (value > uint256(type(int256).max)) {
+ revert SafeCastOverflowedUintToInt(value);
+ }
+ return int256(value);
+ }
+
+ /**
+ * @dev Cast a boolean (false or true) to a uint256 (0 or 1) with no jump.
+ */
+ function toUint(bool b) internal pure returns (uint256 u) {
+ assembly ("memory-safe") {
+ u := iszero(iszero(b))
+ }
+ }
+}
\ No newline at end of file
diff --git a/contracts/lib/rooster/v2-common/libraries/ArrayOperations.sol b/contracts/lib/rooster/v2-common/libraries/ArrayOperations.sol
new file mode 100644
index 0000000000..e2629b77a9
--- /dev/null
+++ b/contracts/lib/rooster/v2-common/libraries/ArrayOperations.sol
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// As the copyright holder of this work, Ubiquity Labs retains
+// the right to distribute, use, and modify this code under any license of
+// their choosing, in addition to the terms of the GPL-v2 or later.
+pragma solidity ^0.8.25;
+
+library ArrayOperations {
+ error ArrayElementsNotUnique(uint256 index, uint256 duplicateEntry);
+
+ /**
+ * @notice Checks that array of numbers are unique.
+ * @param array Array of numbers to check.
+ * @param maxArrayElementValue Maximum value possible in Array.
+ */
+ function checkUnique(uint32[] memory array, uint256 maxArrayElementValue) internal pure {
+ // for pool with few bins (less than ~100k), the bitmap approach is
+ // more gas efficient than exhaustive search of a 10-bin list. As the
+ // bin count in a pool grows beyond this, the gas cost of the bitmap
+ // memory allocation quickly overtakes the exhaustive search cost.
+ if (maxArrayElementValue < 100_000) {
+ // allocate bitmap and set indexes to check uniqueness
+ checkUniqueViaBitMap(array, maxArrayElementValue);
+ } else {
+ // search to check uniqueness
+ checkUniqueViaSearch(array);
+ }
+ }
+
+ /**
+ * @notice Search all pair-wise combinations; low memory, but quadratic
+ * comparison cost.
+ */
+ function checkUniqueViaSearch(uint32[] memory array) internal pure {
+ uint256 length = array.length;
+ if (length <= 1) return;
+ for (uint256 i = 0; i < length - 1; i++) {
+ for (uint256 j = i + 1; j < length; j++) {
+ if (array[i] == array[j]) revert ArrayElementsNotUnique(j, array[j]);
+ }
+ }
+ }
+
+ /**
+ * @notice Fill bitmap with values and revert on collision; memory is
+ * proportional to pool bin count while comparison costs are linear in
+ * array length.
+ */
+ function checkUniqueViaBitMap(uint32[] memory array, uint256 maxArrayElementValue) internal pure {
+ uint256 length = array.length;
+ if (length <= 1) return;
+ uint256[] memory bitMap = new uint256[]((maxArrayElementValue >> 8) + 1);
+ for (uint256 i; i < length; i++) {
+ if (get(bitMap, array[i])) revert ArrayElementsNotUnique(i, array[i]);
+ set(bitMap, array[i]);
+ }
+ }
+
+ /**
+ * @notice Gets the bit at `index`.
+ */
+ function get(uint256[] memory bitmap, uint256 index) private pure returns (bool) {
+ uint256 bucket = index >> 8;
+ uint256 mask = 1 << (index & 0xff);
+ return bitmap[bucket] & mask != 0;
+ }
+
+ /**
+ * @notice Sets the bit at `index`.
+ */
+ function set(uint256[] memory bitmap, uint256 index) private pure {
+ uint256 bucket = index >> 8;
+ uint256 mask = 1 << (index & 0xff);
+ bitmap[bucket] |= mask;
+ }
+}
\ No newline at end of file
diff --git a/contracts/lib/rooster/v2-common/libraries/Constants.sol b/contracts/lib/rooster/v2-common/libraries/Constants.sol
new file mode 100644
index 0000000000..0e69b50a5a
--- /dev/null
+++ b/contracts/lib/rooster/v2-common/libraries/Constants.sol
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// As the copyright holder of this work, Ubiquity Labs retains
+// the right to distribute, use, and modify this code under any license of
+// their choosing, in addition to the terms of the GPL-v2 or later.
+pragma solidity ^0.8.25;
+
+// factory contraints on pools
+uint8 constant MAX_PROTOCOL_FEE_RATIO_D3 = 0.25e3; // 25%
+uint256 constant MAX_PROTOCOL_LENDING_FEE_RATE_D18 = 0.02e18; // 2%
+uint64 constant MAX_POOL_FEE_D18 = 0.9e18; // 90%
+uint64 constant MIN_LOOKBACK = 1 seconds;
+
+// pool constraints
+uint8 constant NUMBER_OF_KINDS = 4;
+int32 constant NUMBER_OF_KINDS_32 = int32(int8(NUMBER_OF_KINDS));
+uint256 constant MAX_TICK = 322_378; // max price 1e14 in D18 scale
+int32 constant MAX_TICK_32 = int32(int256(MAX_TICK));
+int32 constant MIN_TICK_32 = int32(-int256(MAX_TICK));
+uint256 constant MAX_BINS_TO_MERGE = 3;
+uint128 constant MINIMUM_LIQUIDITY = 1e8;
+
+// accessor named constants
+uint8 constant ALL_KINDS_MASK = 0xF; // 0b1111
+uint8 constant PERMISSIONED_LIQUIDITY_MASK = 0x10; // 0b010000
+uint8 constant PERMISSIONED_SWAP_MASK = 0x20; // 0b100000
+uint8 constant OPTIONS_MASK = ALL_KINDS_MASK | PERMISSIONED_LIQUIDITY_MASK | PERMISSIONED_SWAP_MASK; // 0b111111
+
+// named values
+address constant MERGED_LP_BALANCE_ADDRESS = address(0);
+uint256 constant MERGED_LP_BALANCE_SUBACCOUNT = 0;
+uint128 constant ONE = 1e18;
+uint128 constant ONE_SQUARED = 1e36;
+int256 constant INT256_ONE = 1e18;
+uint256 constant ONE_D8 = 1e8;
+uint256 constant ONE_D3 = 1e3;
+int40 constant INT_ONE_D8 = 1e8;
+int40 constant HALF_TICK_D8 = 0.5e8;
+uint8 constant DEFAULT_DECIMALS = 18;
+uint256 constant DEFAULT_SCALE = 1;
+bytes constant EMPTY_PRICE_BREAKS = hex"010000000000000000000000";
\ No newline at end of file
diff --git a/contracts/lib/rooster/v2-common/libraries/Math.sol b/contracts/lib/rooster/v2-common/libraries/Math.sol
new file mode 100644
index 0000000000..f6240553ad
--- /dev/null
+++ b/contracts/lib/rooster/v2-common/libraries/Math.sol
@@ -0,0 +1,398 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// As the copyright holder of this work, Ubiquity Labs retains
+// the right to distribute, use, and modify this code under any license of
+// their choosing, in addition to the terms of the GPL-v2 or later.
+pragma solidity ^0.8.25;
+
+import {Math as OzMath} from "../../openzeppelin-custom/contracts/utils/math/Math.sol";
+
+import {ONE, DEFAULT_SCALE, DEFAULT_DECIMALS, INT_ONE_D8, ONE_SQUARED} from "./Constants.sol";
+
+/**
+ * @notice Math functions.
+ */
+library Math {
+ /**
+ * @notice Returns the lesser of two values.
+ * @param x First uint256 value.
+ * @param y Second uint256 value.
+ */
+ function min(uint256 x, uint256 y) internal pure returns (uint256 z) {
+ assembly ("memory-safe") {
+ z := xor(x, mul(xor(x, y), lt(y, x)))
+ }
+ }
+
+ /**
+ * @notice Returns the lesser of two uint128 values.
+ * @param x First uint128 value.
+ * @param y Second uint128 value.
+ */
+ function min128(uint128 x, uint128 y) internal pure returns (uint128 z) {
+ assembly ("memory-safe") {
+ z := xor(x, mul(xor(x, y), lt(y, x)))
+ }
+ }
+
+ /**
+ * @notice Returns the lesser of two int256 values.
+ * @param x First int256 value.
+ * @param y Second int256 value.
+ */
+ function min(int256 x, int256 y) internal pure returns (int256 z) {
+ assembly ("memory-safe") {
+ z := xor(x, mul(xor(x, y), slt(y, x)))
+ }
+ }
+
+ /**
+ * @notice Returns the greater of two uint256 values.
+ * @param x First uint256 value.
+ * @param y Second uint256 value.
+ */
+ function max(uint256 x, uint256 y) internal pure returns (uint256 z) {
+ assembly ("memory-safe") {
+ z := xor(x, mul(xor(x, y), gt(y, x)))
+ }
+ }
+
+ /**
+ * @notice Returns the greater of two int256 values.
+ * @param x First int256 value.
+ * @param y Second int256 value.
+ */
+ function max(int256 x, int256 y) internal pure returns (int256 z) {
+ assembly ("memory-safe") {
+ z := xor(x, mul(xor(x, y), sgt(y, x)))
+ }
+ }
+
+ /**
+ * @notice Returns the greater of two uint128 values.
+ * @param x First uint128 value.
+ * @param y Second uint128 value.
+ */
+ function max128(uint128 x, uint128 y) internal pure returns (uint128 z) {
+ assembly ("memory-safe") {
+ z := xor(x, mul(xor(x, y), gt(y, x)))
+ }
+ }
+
+ /**
+ * @notice Thresholds a value to be within the specified bounds.
+ * @param value The value to bound.
+ * @param lowerLimit The minimum allowable value.
+ * @param upperLimit The maximum allowable value.
+ */
+ function boundValue(
+ uint256 value,
+ uint256 lowerLimit,
+ uint256 upperLimit
+ ) internal pure returns (uint256 outputValue) {
+ outputValue = min(max(value, lowerLimit), upperLimit);
+ }
+
+ /**
+ * @notice Returns the difference between two uint128 values or zero if the result would be negative.
+ * @param x The minuend.
+ * @param y The subtrahend.
+ */
+ function clip128(uint128 x, uint128 y) internal pure returns (uint128) {
+ unchecked {
+ return x < y ? 0 : x - y;
+ }
+ }
+
+ /**
+ * @notice Returns the difference between two uint256 values or zero if the result would be negative.
+ * @param x The minuend.
+ * @param y The subtrahend.
+ */
+ function clip(uint256 x, uint256 y) internal pure returns (uint256) {
+ unchecked {
+ return x < y ? 0 : x - y;
+ }
+ }
+
+ /**
+ * @notice Divides one uint256 by another, rounding down to the nearest
+ * integer.
+ * @param x The dividend.
+ * @param y The divisor.
+ */
+ function divFloor(uint256 x, uint256 y) internal pure returns (uint256) {
+ return mulDivFloor(x, ONE, y);
+ }
+
+ /**
+ * @notice Divides one uint256 by another, rounding up to the nearest integer.
+ * @param x The dividend.
+ * @param y The divisor.
+ */
+ function divCeil(uint256 x, uint256 y) internal pure returns (uint256) {
+ return mulDivCeil(x, ONE, y);
+ }
+
+ /**
+ * @notice Multiplies two uint256 values and then divides by ONE, rounding down.
+ * @param x The multiplicand.
+ * @param y The multiplier.
+ */
+ function mulFloor(uint256 x, uint256 y) internal pure returns (uint256) {
+ return OzMath.mulDiv(x, y, ONE);
+ }
+
+ /**
+ * @notice Multiplies two uint256 values and then divides by ONE, rounding up.
+ * @param x The multiplicand.
+ * @param y The multiplier.
+ */
+ function mulCeil(uint256 x, uint256 y) internal pure returns (uint256) {
+ return mulDivCeil(x, y, ONE);
+ }
+
+ /**
+ * @notice Calculates the multiplicative inverse of a uint256, rounding down.
+ * @param x The value to invert.
+ */
+ function invFloor(uint256 x) internal pure returns (uint256) {
+ unchecked {
+ return ONE_SQUARED / x;
+ }
+ }
+
+ /**
+ * @notice Calculates the multiplicative inverse of a uint256, rounding up.
+ * @param denominator The value to invert.
+ */
+ function invCeil(uint256 denominator) internal pure returns (uint256 z) {
+ assembly ("memory-safe") {
+ // divide z - 1 by the denominator and add 1.
+ z := add(div(sub(ONE_SQUARED, 1), denominator), 1)
+ }
+ }
+
+ /**
+ * @notice Multiplies two uint256 values and divides by a third, rounding down.
+ * @param x The multiplicand.
+ * @param y The multiplier.
+ * @param k The divisor.
+ */
+ function mulDivFloor(uint256 x, uint256 y, uint256 k) internal pure returns (uint256 result) {
+ result = OzMath.mulDiv(x, y, max(1, k));
+ }
+
+ /**
+ * @notice Multiplies two uint256 values and divides by a third, rounding up if there's a remainder.
+ * @param x The multiplicand.
+ * @param y The multiplier.
+ * @param k The divisor.
+ */
+ function mulDivCeil(uint256 x, uint256 y, uint256 k) internal pure returns (uint256 result) {
+ result = mulDivFloor(x, y, k);
+ if (mulmod(x, y, max(1, k)) != 0) result = result + 1;
+ }
+
+ /**
+ * @notice Multiplies two uint256 values and divides by a third, rounding
+ * down. Will revert if `x * y` is larger than `type(uint256).max`.
+ * @param x The first operand for multiplication.
+ * @param y The second operand for multiplication.
+ * @param denominator The divisor after multiplication.
+ */
+ function mulDivDown(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 z) {
+ assembly ("memory-safe") {
+ // Store x * y in z for now.
+ z := mul(x, y)
+ if iszero(denominator) {
+ denominator := 1
+ }
+
+ if iszero(or(iszero(x), eq(div(z, x), y))) {
+ revert(0, 0)
+ }
+
+ // Divide z by the denominator.
+ z := div(z, denominator)
+ }
+ }
+
+ /**
+ * @notice Multiplies two uint256 values and divides by a third, rounding
+ * up. Will revert if `x * y` is larger than `type(uint256).max`.
+ * @param x The first operand for multiplication.
+ * @param y The second operand for multiplication.
+ * @param denominator The divisor after multiplication.
+ */
+ function mulDivUp(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 z) {
+ assembly ("memory-safe") {
+ // Store x * y in z for now.
+ z := mul(x, y)
+ if iszero(denominator) {
+ denominator := 1
+ }
+
+ if iszero(or(iszero(x), eq(div(z, x), y))) {
+ revert(0, 0)
+ }
+
+ // First, divide z - 1 by the denominator and add 1.
+ // We allow z - 1 to underflow if z is 0, because we multiply the
+ // end result by 0 if z is zero, ensuring we return 0 if z is zero.
+ z := mul(iszero(iszero(z)), add(div(sub(z, 1), denominator), 1))
+ }
+ }
+
+ /**
+ * @notice Multiplies a uint256 by another and divides by a constant,
+ * rounding down. Will revert if `x * y` is larger than
+ * `type(uint256).max`.
+ * @param x The multiplicand.
+ * @param y The multiplier.
+ */
+ function mulDown(uint256 x, uint256 y) internal pure returns (uint256) {
+ return mulDivDown(x, y, ONE);
+ }
+
+ /**
+ * @notice Divides a uint256 by another, rounding down the result. Will
+ * revert if `x * 1e18` is larger than `type(uint256).max`.
+ * @param x The dividend.
+ * @param y The divisor.
+ */
+ function divDown(uint256 x, uint256 y) internal pure returns (uint256) {
+ return mulDivDown(x, ONE, y);
+ }
+
+ /**
+ * @notice Divides a uint256 by another, rounding up the result. Will
+ * revert if `x * 1e18` is larger than `type(uint256).max`.
+ * @param x The dividend.
+ * @param y The divisor.
+ */
+ function divUp(uint256 x, uint256 y) internal pure returns (uint256) {
+ return mulDivUp(x, ONE, y);
+ }
+
+ /**
+ * @notice Scales a number based on a difference in decimals from a default.
+ * @param decimals The new decimal precision.
+ */
+ function scale(uint8 decimals) internal pure returns (uint256) {
+ unchecked {
+ if (decimals == DEFAULT_DECIMALS) {
+ return DEFAULT_SCALE;
+ } else {
+ return 10 ** (DEFAULT_DECIMALS - decimals);
+ }
+ }
+ }
+
+ /**
+ * @notice Adjusts a scaled amount to the token decimal scale.
+ * @param amount The scaled amount.
+ * @param scaleFactor The scaling factor to adjust by.
+ * @param ceil Whether to round up (true) or down (false).
+ */
+ function ammScaleToTokenScale(uint256 amount, uint256 scaleFactor, bool ceil) internal pure returns (uint256 z) {
+ unchecked {
+ if (scaleFactor == DEFAULT_SCALE || amount == 0) {
+ return amount;
+ } else {
+ if (!ceil) return amount / scaleFactor;
+ assembly ("memory-safe") {
+ z := add(div(sub(amount, 1), scaleFactor), 1)
+ }
+ }
+ }
+ }
+
+ /**
+ * @notice Adjusts a token amount to the D18 AMM scale.
+ * @param amount The amount in token scale.
+ * @param scaleFactor The scale factor for adjustment.
+ */
+ function tokenScaleToAmmScale(uint256 amount, uint256 scaleFactor) internal pure returns (uint256) {
+ if (scaleFactor == DEFAULT_SCALE) {
+ return amount;
+ } else {
+ return amount * scaleFactor;
+ }
+ }
+
+ /**
+ * @notice Returns the absolute value of a signed 32-bit integer.
+ * @param x The integer to take the absolute value of.
+ */
+ function abs32(int32 x) internal pure returns (uint32) {
+ unchecked {
+ return uint32(x < 0 ? -x : x);
+ }
+ }
+
+ /**
+ * @notice Returns the absolute value of a signed 256-bit integer.
+ * @param x The integer to take the absolute value of.
+ */
+ function abs(int256 x) internal pure returns (uint256) {
+ unchecked {
+ return uint256(x < 0 ? -x : x);
+ }
+ }
+
+ /**
+ * @notice Calculates the integer square root of a uint256 rounded down.
+ * @param x The number to take the square root of.
+ */
+ function sqrt(uint256 x) internal pure returns (uint256 z) {
+ // from https://github.com/transmissions11/solmate/blob/e8f96f25d48fe702117ce76c79228ca4f20206cb/src/utils/FixedPointMathLib.sol
+ assembly ("memory-safe") {
+ let y := x
+ z := 181
+
+ if iszero(lt(y, 0x10000000000000000000000000000000000)) {
+ y := shr(128, y)
+ z := shl(64, z)
+ }
+ if iszero(lt(y, 0x1000000000000000000)) {
+ y := shr(64, y)
+ z := shl(32, z)
+ }
+ if iszero(lt(y, 0x10000000000)) {
+ y := shr(32, y)
+ z := shl(16, z)
+ }
+ if iszero(lt(y, 0x1000000)) {
+ y := shr(16, y)
+ z := shl(8, z)
+ }
+
+ z := shr(18, mul(z, add(y, 65536)))
+
+ z := shr(1, add(z, div(x, z)))
+ z := shr(1, add(z, div(x, z)))
+ z := shr(1, add(z, div(x, z)))
+ z := shr(1, add(z, div(x, z)))
+ z := shr(1, add(z, div(x, z)))
+ z := shr(1, add(z, div(x, z)))
+ z := shr(1, add(z, div(x, z)))
+
+ z := sub(z, lt(div(x, z), z))
+ }
+ }
+
+ /**
+ * @notice Computes the floor of a D8-scaled number as an int32, ignoring
+ * potential overflow in the cast.
+ * @param val The D8-scaled number.
+ */
+ function floorD8Unchecked(int256 val) internal pure returns (int32) {
+ int32 val32;
+ bool check;
+ unchecked {
+ val32 = int32(val / INT_ONE_D8);
+ check = (val < 0 && val % INT_ONE_D8 != 0);
+ }
+ return check ? val32 - 1 : val32;
+ }
+}
\ No newline at end of file
diff --git a/contracts/lib/rooster/v2-common/libraries/PoolLib.sol b/contracts/lib/rooster/v2-common/libraries/PoolLib.sol
new file mode 100644
index 0000000000..1cd973af06
--- /dev/null
+++ b/contracts/lib/rooster/v2-common/libraries/PoolLib.sol
@@ -0,0 +1,150 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// As the copyright holder of this work, Ubiquity Labs retains
+// the right to distribute, use, and modify this code under any license of
+// their choosing, in addition to the terms of the GPL-v2 or later.
+pragma solidity ^0.8.25;
+
+import {SafeCast as Cast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
+import {IMaverickV2Pool} from "../interfaces/IMaverickV2Pool.sol";
+import {TickMath} from "./TickMath.sol";
+import {Math} from "./Math.sol";
+
+/**
+ * @notice Library of pool functions.
+ */
+library PoolLib {
+ using Cast for uint256;
+
+ struct AddLiquidityInfo {
+ uint256 deltaA;
+ uint256 deltaB;
+ bool tickLtActive;
+ uint256 tickSpacing;
+ int32 tick;
+ }
+
+ /**
+ * @notice Check to ensure that the ticks are in ascending order and amount
+ * array is same length as tick array.
+ * @param ticks An array of int32 values representing ticks to be checked.
+ * @param amountsLength Amount array length.
+ */
+ function uniqueOrderedTicksCheck(int32[] memory ticks, uint256 amountsLength) internal pure {
+ unchecked {
+ if (ticks.length != amountsLength)
+ revert IMaverickV2Pool.PoolTicksAmountsLengthMismatch(ticks.length, amountsLength);
+ int32 lastTick = type(int32).min;
+ for (uint256 i; i < ticks.length; ) {
+ if (ticks[i] <= lastTick) revert IMaverickV2Pool.PoolTicksNotSorted(i, lastTick, ticks[i]);
+ lastTick = ticks[i];
+ i = i + 1;
+ }
+ }
+ }
+
+ /**
+ * @notice Compute bin reserves assuming the bin is not merged; not accurate
+ * reflection of reserves for merged bins.
+ * @param bin The storage reference to the state for this bin.
+ * @param tick The memory reference to the state for this tick.
+ * @return reserveA The reserve amount for token A.
+ * @return reserveB The reserve amount for token B.
+ */
+ function binReserves(
+ IMaverickV2Pool.BinState storage bin,
+ IMaverickV2Pool.TickState memory tick
+ ) internal view returns (uint128 reserveA, uint128 reserveB) {
+ return binReserves(bin.tickBalance, tick.reserveA, tick.reserveB, tick.totalSupply);
+ }
+
+ /**
+ * @notice Compute bin reserves assuming the bin is not merged; not accurate
+ * reflection of reserves for merged bins.
+ * @param tickBalance Bin's balance in the tick.
+ * @param tickReserveA Tick's tokenA reserves.
+ * @param tickReserveB Tick's tokenB reserves.
+ * @param tickTotalSupply Tick total supply of bin balances.
+ */
+ function binReserves(
+ uint128 tickBalance,
+ uint128 tickReserveA,
+ uint128 tickReserveB,
+ uint128 tickTotalSupply
+ ) internal pure returns (uint128 reserveA, uint128 reserveB) {
+ if (tickTotalSupply != 0) {
+ reserveA = reserveValue(tickReserveA, tickBalance, tickTotalSupply);
+ reserveB = reserveValue(tickReserveB, tickBalance, tickTotalSupply);
+ }
+ }
+
+ /**
+ * @notice Reserves of a bin in a tick.
+ * @param tickReserve Tick reserve amount in a given token.
+ * @param tickBalance Bin's balance in the tick.
+ * @param tickTotalSupply Tick total supply of bin balances.
+ */
+ function reserveValue(
+ uint128 tickReserve,
+ uint128 tickBalance,
+ uint128 tickTotalSupply
+ ) internal pure returns (uint128 reserve) {
+ reserve = Math.mulDivFloor(tickReserve, tickBalance, tickTotalSupply).toUint128();
+ reserve = Math.min128(tickReserve, reserve);
+ }
+
+ /**
+ * @notice Calculate delta A, delta B, and delta Tick Balance based on delta
+ * LP balance and the Tick/Bin state.
+ */
+ function deltaTickBalanceFromDeltaLpBalance(
+ uint256 binTickBalance,
+ uint256 binTotalSupply,
+ IMaverickV2Pool.TickState memory tickState,
+ uint128 deltaLpBalance,
+ AddLiquidityInfo memory addLiquidityInfo
+ ) internal pure returns (uint256 deltaTickBalance) {
+ unchecked {
+ if (tickState.reserveA != 0 || tickState.reserveB != 0) {
+ // if there are already reserves, then we just contribute pro rata
+ // deltaLiquidity = deltaBinLP / binTS * binTickBalance / tickTS * tickL
+ uint256 numerator = Math.max(1, binTickBalance) * uint256(deltaLpBalance);
+ uint256 denominator = Math.max(1, tickState.totalSupply) * Math.max(1, binTotalSupply);
+ addLiquidityInfo.deltaA = Math.mulDivCeil(tickState.reserveA, numerator, denominator);
+ addLiquidityInfo.deltaB = Math.mulDivCeil(tickState.reserveB, numerator, denominator);
+ } else {
+ _setRequiredDeltaReservesForEmptyTick(deltaLpBalance, addLiquidityInfo);
+ }
+
+ // round down the amount credited to the tick; this could lead to a
+ // small add amount getting zero reserves credit.
+ deltaTickBalance = tickState.totalSupply == 0
+ ? deltaLpBalance
+ : Math.mulDivDown(deltaLpBalance, Math.max(1, binTickBalance), binTotalSupply);
+ }
+ }
+
+ /**
+ * @notice Calculates deltaA = liquidity * (sqrt(upper) - sqrt(lower))
+ * @notice Calculates deltaB = liquidity / sqrt(lower) - liquidity / sqrt(upper),
+ * @notice i.e. liquidity * (sqrt(upper) - sqrt(lower)) / (sqrt(upper) * sqrt(lower))
+ * @notice we set liquidity = deltaLpBalance / (1.0001^(tick * tickspacing) - 1)
+ * @notice which simplifies the A/B amounts to:
+ * @notice deltaA = deltaLpBalance * sqrt(lower)
+ * @notice deltaB = deltaLpBalance / sqrt(upper)
+ */
+ function _setRequiredDeltaReservesForEmptyTick(
+ uint128 deltaLpBalance,
+ AddLiquidityInfo memory addLiquidityInfo
+ ) internal pure {
+ // No reserves, so we will use deltaLpBalance as liquidity to be added.
+ // In this logic branch, the tick is empty, so we know the tick will be
+ // a one-asset add.
+ (uint256 sqrtLowerTickPrice, uint256 sqrtUpperTickPrice) = TickMath.tickSqrtPrices(
+ addLiquidityInfo.tickSpacing,
+ addLiquidityInfo.tick
+ );
+
+ addLiquidityInfo.deltaA = addLiquidityInfo.tickLtActive ? Math.mulCeil(deltaLpBalance, sqrtLowerTickPrice) : 0;
+ addLiquidityInfo.deltaB = addLiquidityInfo.tickLtActive ? 0 : Math.divCeil(deltaLpBalance, sqrtUpperTickPrice);
+ }
+}
\ No newline at end of file
diff --git a/contracts/lib/rooster/v2-common/libraries/TickMath.sol b/contracts/lib/rooster/v2-common/libraries/TickMath.sol
new file mode 100644
index 0000000000..86b7d40f40
--- /dev/null
+++ b/contracts/lib/rooster/v2-common/libraries/TickMath.sol
@@ -0,0 +1,211 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// As the copyright holder of this work, Ubiquity Labs retains
+// the right to distribute, use, and modify this code under any license of
+// their choosing, in addition to the terms of the GPL-v2 or later.
+pragma solidity ^0.8.25;
+
+import {Math as OzMath} from "../../openzeppelin-custom/contracts/utils/math/Math.sol";
+import {Math} from "./Math.sol";
+import {MAX_TICK, ONE} from "./Constants.sol";
+
+/**
+ * @notice Math functions related to tick operations.
+ */
+// slither-disable-start divide-before-multiply
+library TickMath {
+ using Math for uint256;
+
+ error TickMaxExceeded(int256 tick);
+
+ /**
+ * @notice Compute the lower and upper sqrtPrice of a tick.
+ * @param tickSpacing The tick spacing used for calculations.
+ * @param _tick The input tick value.
+ */
+ function tickSqrtPrices(
+ uint256 tickSpacing,
+ int32 _tick
+ ) internal pure returns (uint256 sqrtLowerPrice, uint256 sqrtUpperPrice) {
+ unchecked {
+ sqrtLowerPrice = tickSqrtPrice(tickSpacing, _tick);
+ sqrtUpperPrice = tickSqrtPrice(tickSpacing, _tick + 1);
+ }
+ }
+
+ /**
+ * @notice Compute the base tick value from the pool tick and the
+ * tickSpacing. Revert if base tick is beyond the max tick boundary.
+ * @param tickSpacing The tick spacing used for calculations.
+ * @param _tick The input tick value.
+ */
+ function subTickIndex(uint256 tickSpacing, int32 _tick) internal pure returns (uint32 subTick) {
+ subTick = Math.abs32(_tick);
+ subTick *= uint32(tickSpacing);
+ if (subTick > MAX_TICK) {
+ revert TickMaxExceeded(_tick);
+ }
+ }
+
+ /**
+ * @notice Calculate the square root price for a given tick and tick spacing.
+ * @param tickSpacing The tick spacing used for calculations.
+ * @param _tick The input tick value.
+ * @return _result The square root price.
+ */
+ function tickSqrtPrice(uint256 tickSpacing, int32 _tick) internal pure returns (uint256 _result) {
+ unchecked {
+ uint256 tick = subTickIndex(tickSpacing, _tick);
+
+ uint256 ratio = tick & 0x1 != 0 ? 0xfffcb933bd6fad9d3af5f0b9f25db4d6 : 0x100000000000000000000000000000000;
+ if (tick & 0x2 != 0) ratio = (ratio * 0xfff97272373d41fd789c8cb37ffcaa1c) >> 128;
+ if (tick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656ac9229c67059486f389) >> 128;
+ if (tick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e81259b3cddc7a064941) >> 128;
+ if (tick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f67b19e8887e0bd251eb7) >> 128;
+ if (tick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98cd2e57b660be99eb2c4a) >> 128;
+ if (tick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c9838804e327cb417cafcb) >> 128;
+ if (tick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99d51e2cc356c2f617dbe0) >> 128;
+ if (tick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900aecf64236ab31f1f9dcb5) >> 128;
+ if (tick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac4d9194200696907cf2e37) >> 128;
+ if (tick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b88206f8abe8a3b44dd9be) >> 128;
+ if (tick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c578ef4f1d17b2b235d480) >> 128;
+ if (tick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd254ee83bdd3f248e7e785e) >> 128;
+ if (tick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d8f7dd10e744d913d033333) >> 128;
+ if (tick & 0x4000 != 0) ratio = (ratio * 0x70d869a156ddd32a39e257bc3f50aa9b) >> 128;
+ if (tick & 0x8000 != 0) ratio = (ratio * 0x31be135f97da6e09a19dc367e3b6da40) >> 128;
+ if (tick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7e5a9780b0cc4e25d61a56) >> 128;
+ if (tick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedbcb3a6ccb7ce618d14225) >> 128;
+ if (tick & 0x40000 != 0) ratio = (ratio * 0x2216e584f630389b2052b8db590e) >> 128;
+ if (_tick > 0) ratio = type(uint256).max / ratio;
+ _result = (ratio * ONE) >> 128;
+ }
+ }
+
+ /**
+ * @notice Calculate liquidity of a tick.
+ * @param reserveA Tick reserve of token A.
+ * @param reserveB Tick reserve of token B.
+ * @param sqrtLowerTickPrice The square root price of the lower tick edge.
+ * @param sqrtUpperTickPrice The square root price of the upper tick edge.
+ */
+ function getTickL(
+ uint256 reserveA,
+ uint256 reserveB,
+ uint256 sqrtLowerTickPrice,
+ uint256 sqrtUpperTickPrice
+ ) internal pure returns (uint256 liquidity) {
+ // known:
+ // - sqrt price values are different
+ // - reserveA and reserveB fit in 128 bit
+ // - sqrt price is in (1e-7, 1e7)
+ // - D18 max for uint256 is 1.15e59
+ // - D18 min is 1e-18
+
+ unchecked {
+ // diff is in (5e-12, 4e6); max tick spacing is 10_000
+ uint256 diff = sqrtUpperTickPrice - sqrtLowerTickPrice;
+
+ // Need to maximize precision by shifting small values A and B up so
+ // that they use more of the available bit range. Two constraints to
+ // consider: we need A * B * diff / sqrtPrice to be bigger than 1e-18
+ // when the bump is not in play. This constrains the threshold for
+ // bumping to be at least 77 bit; ie, either a or b needs 2^77 which
+ // means that term A * B * diff / sqrtPrice > 1e-18.
+ //
+ // At the other end, the second constraint is that b^2 needs to fit in
+ // a 256-bit number, so, post bump, the max reserve value needs to be
+ // less than 6e22. With a 78-bit threshold and a 57-bit bump, we have A
+ // and B are in (1.4e-1, 4.4e22 (2^(78+57))) with bump, and one of A or
+ // B is at least 2^78 without the bump, but the other reserve value may
+ // be as small as 1 wei.
+ uint256 precisionBump = 0;
+ if ((reserveA >> 78) == 0 && (reserveB >> 78) == 0) {
+ precisionBump = 57;
+ reserveA <<= precisionBump;
+ reserveB <<= precisionBump;
+ }
+
+ if (reserveB == 0) return Math.divDown(reserveA, diff) >> precisionBump;
+ if (reserveA == 0)
+ return Math.mulDivDown(reserveB.mulDown(sqrtLowerTickPrice), sqrtUpperTickPrice, diff) >> precisionBump;
+
+ // b is in (7.2e-9 (2^57 / 1e7 / 2), 2.8e29 (2^(78+57) * 1e7 / 2)) with bump
+ // b is in a subset of the same range without bump
+ uint256 b = (reserveA.divDown(sqrtUpperTickPrice) + reserveB.mulDown(sqrtLowerTickPrice)) >> 1;
+
+ // b^2 is in (5.1e-17, 4.8e58); and will not overflow on either end;
+ // A*B is in (3e-13 (2^78 / 1e18 * 1e-18), 1.9e45) without bump and is in a subset range with bump
+ // A*B*diff/sqrtUpper is in (1.5e-17 (3e-13 * 5e-12 * 1e7), 7.6e58);
+
+ // Since b^2 is at the upper edge of the precision range, we are not
+ // able to multiply the argument of the sqrt by 1e18, instead, we move
+ // this factor outside of the sqrt. The resulting loss of precision
+ // means that this liquidity value is a lower bound on the tick
+ // liquidity
+ return
+ OzMath.mulDiv(
+ b +
+ Math.sqrt(
+ (OzMath.mulDiv(b, b, ONE) +
+ OzMath.mulDiv(reserveB.mulFloor(reserveA), diff, sqrtUpperTickPrice))
+ ) *
+ 1e9,
+ sqrtUpperTickPrice,
+ diff
+ ) >> precisionBump;
+ }
+ }
+
+ /**
+ * @notice Calculate square root price of a tick. Returns left edge of the
+ * tick if the tick has no reserves.
+ * @param reserveA Tick reserve of token A.
+ * @param reserveB Tick reserve of token B.
+ * @param sqrtLowerTickPrice The square root price of the lower tick edge.
+ * @param sqrtUpperTickPrice The square root price of the upper tick edge.
+ * @return sqrtPrice The calculated square root price.
+ */
+ function getSqrtPrice(
+ uint256 reserveA,
+ uint256 reserveB,
+ uint256 sqrtLowerTickPrice,
+ uint256 sqrtUpperTickPrice,
+ uint256 liquidity
+ ) internal pure returns (uint256 sqrtPrice) {
+ unchecked {
+ if (reserveA == 0) {
+ return sqrtLowerTickPrice;
+ }
+ if (reserveB == 0) {
+ return sqrtUpperTickPrice;
+ }
+ sqrtPrice = Math.sqrt(
+ ONE *
+ (reserveA + liquidity.mulDown(sqrtLowerTickPrice)).divDown(
+ reserveB + liquidity.divDown(sqrtUpperTickPrice)
+ )
+ );
+ sqrtPrice = Math.boundValue(sqrtPrice, sqrtLowerTickPrice, sqrtUpperTickPrice);
+ }
+ }
+
+ /**
+ * @notice Calculate square root price of a tick. Returns left edge of the
+ * tick if the tick has no reserves.
+ * @param reserveA Tick reserve of token A.
+ * @param reserveB Tick reserve of token B.
+ * @param sqrtLowerTickPrice The square root price of the lower tick edge.
+ * @param sqrtUpperTickPrice The square root price of the upper tick edge.
+ * @return sqrtPrice The calculated square root price.
+ * @return liquidity The calculated liquidity.
+ */
+ function getTickSqrtPriceAndL(
+ uint256 reserveA,
+ uint256 reserveB,
+ uint256 sqrtLowerTickPrice,
+ uint256 sqrtUpperTickPrice
+ ) internal pure returns (uint256 sqrtPrice, uint256 liquidity) {
+ liquidity = getTickL(reserveA, reserveB, sqrtLowerTickPrice, sqrtUpperTickPrice);
+ sqrtPrice = getSqrtPrice(reserveA, reserveB, sqrtLowerTickPrice, sqrtUpperTickPrice, liquidity);
+ }
+}
+// slither-disable-end divide-before-multiply
\ No newline at end of file
diff --git a/contracts/lib/rooster/v2-common/libraries/TransferLib.sol b/contracts/lib/rooster/v2-common/libraries/TransferLib.sol
new file mode 100644
index 0000000000..c56f0a181d
--- /dev/null
+++ b/contracts/lib/rooster/v2-common/libraries/TransferLib.sol
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// As the copyright holder of this work, Ubiquity Labs retains
+// the right to distribute, use, and modify this code under any license of
+// their choosing, in addition to the terms of the GPL-v2 or later.
+pragma solidity ^0.8.25;
+
+import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+
+/**
+ * @notice Low-gas transfer functions.
+ */
+library TransferLib {
+ error TransferFailed(IERC20 token, address to, uint256 amount);
+ error TransferFromFailed(IERC20 token, address from, address to, uint256 amount);
+
+ // implementation adapted from
+ // https://github.com/transmissions11/solmate/blob/e8f96f25d48fe702117ce76c79228ca4f20206cb/src/utils/SafeTransferLib.sol
+
+ /**
+ * @notice Transfer token amount. Amount is sent from caller address to `to` address.
+ */
+ function transfer(IERC20 token, address to, uint256 amount) internal {
+ bool success;
+ assembly ("memory-safe") {
+ // We'll write our calldata to this slot below, but restore it later.
+ let memPointer := mload(0x40)
+
+ // Write the abi-encoded calldata into memory, beginning with the function selector.
+ mstore(memPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
+ // Append arguments. Addresses are assumed clean. Transfer will fail otherwise.
+ mstore(add(memPointer, 0x4), to)
+ mstore(add(memPointer, 0x24), amount) // Append the "amount" argument.
+ // 68 bytes total
+
+ // fail if reverted; only allocate 32 bytes for return to ensure we
+ // only use mem slot 0 which is scatch space and memory safe to use.
+ success := call(gas(), token, 0, memPointer, 68, 0, 32)
+ // handle transfers that return 1/true and ensure the value is from
+ // the return and not dirty bits left in the scratch space.
+ let returnedOne := and(eq(mload(0), 1), gt(returndatasize(), 31))
+ // handle transfers that return nothing
+ let noReturn := iszero(returndatasize())
+ // good if didn't revert and the return is either empty or true
+ success := and(success, or(returnedOne, noReturn))
+ }
+
+ if (!success) revert TransferFailed(token, to, amount);
+ }
+
+ /**
+ * @notice Transfer token amount. Amount is sent from `from` address to `to` address.
+ */
+ function transferFrom(IERC20 token, address from, address to, uint256 amount) internal {
+ bool success;
+
+ assembly ("memory-safe") {
+ // We'll write our calldata to this slot below, but restore it later.
+ let memPointer := mload(0x40)
+
+ // Write the abi-encoded calldata into memory, beginning with the function selector.
+ mstore(memPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
+ // Append arguments. Addresses are assumed clean. Transfer will fail otherwise.
+ mstore(add(memPointer, 0x4), from) // Append the "from" argument.
+ mstore(add(memPointer, 0x24), to) // Append the "to" argument.
+ mstore(add(memPointer, 0x44), amount) // Append the "amount" argument.
+ // 100 bytes total
+
+ // fail if reverted; only allocate 32 bytes for return to ensure we
+ // only use mem slot 0 which is scatch space and memory safe to use.
+ success := call(gas(), token, 0, memPointer, 100, 0, 32)
+ // handle transfers that return 1/true and ensure the value is from
+ // the return and not dirty bits left in the scratch space.
+ let returnedOne := and(eq(mload(0), 1), gt(returndatasize(), 31))
+ // handle transfers that return nothing
+ let noReturn := iszero(returndatasize())
+ // good if didn't revert and the return is either empty or true
+ success := and(success, or(returnedOne, noReturn))
+ }
+
+ if (!success) revert TransferFromFailed(token, from, to, amount);
+ }
+}
\ No newline at end of file
diff --git a/contracts/test/_fixture-plume.js b/contracts/test/_fixture-plume.js
index 02b0129a0f..57fec1bd5c 100644
--- a/contracts/test/_fixture-plume.js
+++ b/contracts/test/_fixture-plume.js
@@ -4,6 +4,10 @@ const mocha = require("mocha");
const { isFork, isPlumeFork, oethUnits } = require("./helpers");
const { impersonateAndFund } = require("../utils/signers");
const { nodeRevert, nodeSnapshot } = require("./_fixture");
+const { deployWithConfirmation } = require("../utils/deploy");
+const {
+ deployPlumeMockRoosterAMOStrategyImplementation,
+} = require("../deploy/deployActions.js");
const addresses = require("../utils/addresses");
const hhHelpers = require("@nomicfoundation/hardhat-network-helpers");
const log = require("../utils/logger")("test:fixtures-plume");
@@ -14,7 +18,49 @@ const BURNER_ROLE =
"0x3c11d16cbaffd01df69ce1c404f6340ee057498f5f00246190ea54220576a848";
let snapshotId;
-const defaultPlumeFixture = deployments.createFixture(async () => {
+
+const baseFixtureWithMockedVaultAdminConfig = async () => {
+ const fixture = await defaultFixture();
+
+ const cOETHVaultProxy = await ethers.getContract("OETHPlumeVaultProxy");
+ const cOETHVaultAdmin = await ethers.getContractAt(
+ "IVault",
+ cOETHVaultProxy.address
+ );
+ await deployWithConfirmation("MockOETHVaultAdmin", [fixture.weth.address]);
+
+ const mockVaultAdmin = await ethers.getContract("MockOETHVaultAdmin");
+ await cOETHVaultAdmin
+ .connect(fixture.governor)
+ .setAdminImpl(mockVaultAdmin.address);
+
+ fixture.oethpVault = await ethers.getContractAt(
+ "IMockVault",
+ fixture.oethpVault.address
+ );
+
+ const mockImplementation =
+ await deployPlumeMockRoosterAMOStrategyImplementation(
+ addresses.plume.OethpWETHRoosterPool
+ );
+
+ const roosterAmoStrategyProxy = await ethers.getContract(
+ "RoosterAMOStrategyProxy"
+ );
+
+ await roosterAmoStrategyProxy
+ .connect(fixture.governor)
+ .upgradeTo(mockImplementation.address);
+
+ fixture.roosterAmoStrategy = await ethers.getContractAt(
+ "MockRoosterAMOStrategy",
+ roosterAmoStrategyProxy.address
+ );
+
+ return fixture;
+};
+
+const defaultFixture = async () => {
if (!snapshotId && !isFork) {
snapshotId = await nodeSnapshot();
}
@@ -62,7 +108,6 @@ const defaultPlumeFixture = deployments.createFixture(async () => {
const governor = isFork ? timelock : await ethers.getSigner(governorAddr);
const strategist = await ethers.getSigner(strategistAddr);
- // WETH
const weth = await ethers.getContractAt("MockWETH", addresses.plume.WETH);
// OETHp
@@ -130,11 +175,26 @@ const defaultPlumeFixture = deployments.createFixture(async () => {
}
};
+ let roosterAmoStrategy, roosterOETHpWETHpool;
if (isFork) {
// Allow governor to mint WETH
const wethOwner = "0xb8ce2bE5c3c13712b4da61722EAd9d64bB57AbC9";
const ownerSigner = await impersonateAndFund(wethOwner);
await wethMintableContract.connect(ownerSigner).addMinter(governor.address);
+
+ // Aerodrome AMO Strategy
+ const roosterAmoStrategyProxy = await ethers.getContract(
+ "RoosterAMOStrategyProxy"
+ );
+ roosterAmoStrategy = await ethers.getContractAt(
+ "RoosterAMOStrategy",
+ roosterAmoStrategyProxy.address
+ );
+
+ roosterOETHpWETHpool = await ethers.getContractAt(
+ "IMaverickV2Pool",
+ addresses.plume.OethpWETHRoosterPool
+ );
}
for (const signer of [rafael, daniel, nick, domen, clement]) {
@@ -142,7 +202,7 @@ const defaultPlumeFixture = deployments.createFixture(async () => {
await hhHelpers.setBalance(signer.address, oethUnits("100000000"));
// And WETH
- _mintWETH(signer, oethUnits("10000000"));
+ await _mintWETH(signer, oethUnits("10000000"));
// Set allowance on the vault
await weth
@@ -167,7 +227,8 @@ const defaultPlumeFixture = deployments.createFixture(async () => {
oethp,
wOETHp,
oethpVault,
-
+ roosterAmoStrategy,
+ roosterOETHpWETHpool,
// Bridged wOETH
woeth,
woethProxy,
@@ -177,7 +238,12 @@ const defaultPlumeFixture = deployments.createFixture(async () => {
// Helpers
_mintWETH,
};
-});
+};
+
+const defaultPlumeFixture = deployments.createFixture(defaultFixture);
+const plumeFixtureWithMockedVaultAdmin = deployments.createFixture(
+ baseFixtureWithMockedVaultAdminConfig
+);
mocha.after(async () => {
if (snapshotId) {
@@ -187,4 +253,5 @@ mocha.after(async () => {
module.exports = {
defaultPlumeFixture,
+ plumeFixtureWithMockedVaultAdmin,
};
diff --git a/contracts/test/strategies/plume/rooster-amo.plume.fork-test.js b/contracts/test/strategies/plume/rooster-amo.plume.fork-test.js
new file mode 100644
index 0000000000..61d0e908a1
--- /dev/null
+++ b/contracts/test/strategies/plume/rooster-amo.plume.fork-test.js
@@ -0,0 +1,1136 @@
+const hre = require("hardhat");
+const { createFixtureLoader } = require("../../_fixture");
+
+const addresses = require("../../../utils/addresses");
+const { plumeFixtureWithMockedVaultAdmin } = require("../../_fixture-plume");
+const { expect } = require("chai");
+const { oethUnits } = require("../../helpers");
+const ethers = hre.ethers;
+const { impersonateAndFund } = require("../../../utils/signers");
+const { BigNumber } = ethers;
+
+const plumeFixtureWithMockedVault = createFixtureLoader(
+ plumeFixtureWithMockedVaultAdmin
+);
+
+const { setERC20TokenBalance } = require("../../_fund");
+
+describe("ForkTest: Rooster AMO Strategy (Plume)", async function () {
+ let fixture,
+ oethpVault,
+ oethVaultSigner,
+ oethp,
+ weth,
+ roosterAmoStrategy,
+ roosterOETHpWETHpool,
+ governor,
+ strategist,
+ rafael;
+
+ beforeEach(async () => {
+ fixture = await plumeFixtureWithMockedVault();
+ weth = fixture.weth;
+ oethp = fixture.oethp;
+ oethpVault = fixture.oethpVault;
+ roosterAmoStrategy = fixture.roosterAmoStrategy;
+ roosterOETHpWETHpool = fixture.roosterOETHpWETHpool;
+ governor = fixture.governor;
+ strategist = fixture.strategist;
+ rafael = fixture.rafael;
+ oethVaultSigner = await impersonateAndFund(oethpVault.address);
+
+ await setup();
+ });
+
+ const configureAutomaticDepositOnMint = async (vaultBuffer) => {
+ await oethpVault.connect(governor).setVaultBuffer(vaultBuffer);
+
+ const totalValue = await oethpVault.totalValue();
+
+ // min mint to trigger deposits
+ return totalValue.mul(vaultBuffer).div(oethUnits("1"));
+ };
+
+ describe("ForkTest: Initial state (Plume)", function () {
+ it("Should have the correct initial state", async function () {
+ // correct pool weth share interval
+ expect(await roosterAmoStrategy.allowedWethShareStart()).to.equal(
+ oethUnits("0.10")
+ );
+
+ expect(await roosterAmoStrategy.allowedWethShareEnd()).to.equal(
+ oethUnits("0.25")
+ );
+
+ expect(await roosterAmoStrategy.tickDominance()).to.gt(0);
+ expect(await roosterAmoStrategy.tickDominance()).to.lte(oethUnits("1"));
+
+ await verifyEndConditions();
+ });
+
+ it("Should revert setting ptoken address", async function () {
+ await expect(
+ roosterAmoStrategy
+ .connect(governor)
+ .setPTokenAddress(weth.address, weth.address)
+ ).to.be.revertedWith("Unsupported method");
+ });
+
+ it("Should revert setting ptoken address", async function () {
+ await expect(
+ roosterAmoStrategy.connect(governor).removePToken(weth.address)
+ ).to.be.revertedWith("Unsupported method");
+ });
+
+ it("Should revert calling safe approve all tokens", async function () {
+ await expect(
+ roosterAmoStrategy.connect(governor).safeApproveAllTokens()
+ ).to.be.revertedWith("Unsupported method");
+ });
+
+ it("Should allow calling getWethShare", async function () {
+ await expect(
+ await roosterAmoStrategy.connect(governor).getWETHShare()
+ ).to.be.lte(oethUnits("1"));
+ });
+
+ it("Should support WETH", async function () {
+ await expect(
+ await roosterAmoStrategy.supportsAsset(weth.address)
+ ).to.equal(true);
+
+ await expect(
+ await roosterAmoStrategy.supportsAsset(oethp.address)
+ ).to.equal(false);
+ });
+
+ it("Should not revert calling public views", async function () {
+ await roosterAmoStrategy.getPoolSqrtPrice();
+ await roosterAmoStrategy.getCurrentTradingTick();
+ await roosterAmoStrategy.getPositionPrincipal();
+ await roosterAmoStrategy.tickDominance();
+ });
+ });
+
+ describe("Configuration", function () {
+ it("Governor can set the allowed pool weth share interval", async () => {
+ const { roosterAmoStrategy } = fixture;
+ const gov = await roosterAmoStrategy.governor();
+
+ await roosterAmoStrategy
+ .connect(await impersonateAndFund(gov))
+ .setAllowedPoolWethShareInterval(oethUnits("0.19"), oethUnits("0.23"));
+
+ expect(await roosterAmoStrategy.allowedWethShareStart()).to.equal(
+ oethUnits("0.19")
+ );
+
+ expect(await roosterAmoStrategy.allowedWethShareEnd()).to.equal(
+ oethUnits("0.23")
+ );
+ });
+
+ it("Only the governor can set the pool weth share", async () => {
+ const { rafael, roosterAmoStrategy } = fixture;
+
+ await expect(
+ roosterAmoStrategy
+ .connect(rafael)
+ .setAllowedPoolWethShareInterval(oethUnits("0.19"), oethUnits("0.23"))
+ ).to.be.revertedWith("Caller is not the Governor");
+ });
+
+ it("Can not mint initial position twice", async () => {
+ const { governor, roosterAmoStrategy } = fixture;
+
+ await expect(
+ roosterAmoStrategy.connect(governor).mintInitialPosition()
+ ).to.be.revertedWith("Initial position already minted");
+ });
+
+ it("Only the governor can mint the initial position", async () => {
+ const { rafael, roosterAmoStrategy } = fixture;
+
+ await expect(
+ roosterAmoStrategy.connect(rafael).mintInitialPosition()
+ ).to.be.revertedWith("Caller is not the Governor");
+ });
+
+ it("Can not set incorrect pool WETH share intervals", async () => {
+ const { roosterAmoStrategy } = fixture;
+ const gov = await roosterAmoStrategy.governor();
+
+ await expect(
+ roosterAmoStrategy
+ .connect(await impersonateAndFund(gov))
+ .setAllowedPoolWethShareInterval(oethUnits("0.5"), oethUnits("0.4"))
+ ).to.be.revertedWith("Invalid interval");
+
+ await expect(
+ roosterAmoStrategy
+ .connect(await impersonateAndFund(gov))
+ .setAllowedPoolWethShareInterval(
+ oethUnits("0.0001"),
+ oethUnits("0.5")
+ )
+ ).to.be.revertedWith("Invalid interval start");
+
+ await expect(
+ roosterAmoStrategy
+ .connect(await impersonateAndFund(gov))
+ .setAllowedPoolWethShareInterval(oethUnits("0.2"), oethUnits("0.96"))
+ ).to.be.revertedWith("Invalid interval end");
+ });
+ });
+
+ describe("Withdraw", function () {
+ it("Should allow withdraw when the pool is 80:20 balanced", async () => {
+ const { oethpVault, roosterAmoStrategy, weth } = fixture;
+
+ const impersonatedVaultSigner = await impersonateAndFund(
+ oethpVault.address
+ );
+
+ const balanceBefore = await weth.balanceOf(oethpVault.address);
+
+ const poolPrice = await roosterAmoStrategy.getPoolSqrtPrice();
+
+ // setup() moves the pool closer to 80:20
+ const [amountWETH, amountOETHb] =
+ await roosterAmoStrategy.getPositionPrincipal();
+
+ // Try withdrawing an amount
+ await roosterAmoStrategy
+ .connect(impersonatedVaultSigner)
+ .withdraw(oethpVault.address, weth.address, oethUnits("1"));
+
+ // Make sure that 1 WETH and 4 OETHb were burned
+ const [amountWETHAfter, amountOETHbAfter] =
+ await roosterAmoStrategy.getPositionPrincipal();
+
+ expect(amountWETHAfter).to.approxEqualTolerance(
+ amountWETH.sub(oethUnits("1"))
+ );
+
+ expect(amountOETHbAfter).to.approxEqualTolerance(
+ amountOETHb.sub(oethUnits("4")),
+ 3
+ );
+
+ // Make sure there's no price movement
+ expect(await roosterAmoStrategy.getPoolSqrtPrice()).to.eq(poolPrice);
+
+ // And recipient has got it
+ expect(await weth.balanceOf(oethpVault.address)).to.eq(
+ balanceBefore.add(oethUnits("1"))
+ );
+
+ // There may remain some WETH left on the strategy contract because of the rounding when
+ // removing the liquidity
+ expect(await weth.balanceOf(roosterAmoStrategy.address)).to.lte(
+ BigNumber.from("1000")
+ );
+
+ await verifyEndConditions();
+ });
+
+ it("Should allow withdrawAll when the pool is 80:20 balanced", async () => {
+ const { oethpVault, roosterAmoStrategy, weth, oethp } = fixture;
+
+ const impersonatedVaultSigner = await impersonateAndFund(
+ oethpVault.address
+ );
+
+ const balanceBefore = await weth.balanceOf(oethpVault.address);
+ const supplyBefore = await oethp.totalSupply();
+
+ // setup() moves the pool closer to 80:20
+ const [amountWETHBefore, amountOETHpBefore] =
+ await roosterAmoStrategy.getPositionPrincipal();
+
+ // Try withdrawing an amount
+ await roosterAmoStrategy.connect(impersonatedVaultSigner).withdrawAll();
+
+ // // Make sure pool is empty
+ const [amountWETH, amountOETHb] =
+ await roosterAmoStrategy.getPositionPrincipal();
+ expect(amountOETHb).to.eq(0);
+ expect(amountWETH).to.eq(0);
+
+ // And recipient has got it
+ expect(await weth.balanceOf(oethpVault.address)).to.approxEqualTolerance(
+ balanceBefore.add(amountWETHBefore)
+ );
+
+ // And supply has gone down
+ expect(await oethp.totalSupply()).to.eq(
+ supplyBefore.sub(amountOETHpBefore)
+ );
+
+ // There should be no WETH on the strategy contract
+ expect(await weth.balanceOf(roosterAmoStrategy.address)).to.eq(
+ oethUnits("0")
+ );
+ });
+
+ it("Should allow double withdrawAll", async () => {
+ const { oethpVault, roosterAmoStrategy } = fixture;
+
+ const impersonatedVaultSigner = await impersonateAndFund(
+ oethpVault.address
+ );
+
+ // Try withdrawing an amount
+ await roosterAmoStrategy.connect(impersonatedVaultSigner).withdrawAll();
+ await roosterAmoStrategy.connect(impersonatedVaultSigner).withdrawAll();
+ });
+
+ it("Should withdraw when there's little WETH in the pool", async () => {
+ const { oethpVault, roosterAmoStrategy, weth } = fixture;
+
+ const impersonatedVaultSigner = await impersonateAndFund(
+ oethpVault.address
+ );
+
+ // Drain out most of WETH
+ await rebalanceThePoolToWETHRatio("0.01");
+
+ const balanceBefore = await weth.balanceOf(oethpVault.address);
+
+ const [amountWETH, amountOETHb] =
+ await roosterAmoStrategy.getPositionPrincipal();
+
+ // Try withdrawing an amount
+ await roosterAmoStrategy
+ .connect(impersonatedVaultSigner)
+ .withdraw(oethpVault.address, weth.address, oethUnits("0.01"));
+
+ // Make sure that 1 WETH was burned and pool composition remains the same
+ const [amountWETHAfter, amountOETHbAfter] =
+ await roosterAmoStrategy.getPositionPrincipal();
+ expect(amountWETHAfter).to.approxEqualTolerance(
+ amountWETH.sub(oethUnits("0.01"))
+ );
+ expect(amountOETHbAfter.div(amountWETHAfter)).to.approxEqualTolerance(
+ amountOETHb.div(amountWETH)
+ );
+
+ // And recipient has got it
+ expect(await weth.balanceOf(oethpVault.address)).to.approxEqualTolerance(
+ balanceBefore.add(oethUnits("0.01"))
+ );
+
+ // There may remain some WETH left on the strategy contract because of the rounding
+ // when removing the liquidity
+ expect(await weth.balanceOf(roosterAmoStrategy.address)).to.lte(
+ BigNumber.from("1000")
+ );
+
+ await verifyEndConditions();
+ });
+
+ it("Should withdrawAll when there's little WETH in the pool", async () => {
+ const { oethpVault, roosterAmoStrategy, weth } = fixture;
+
+ const impersonatedVaultSigner = await impersonateAndFund(
+ oethpVault.address
+ );
+
+ // setup() moves the pool closer to 80:20
+
+ // Drain out most of WETH
+ await rebalanceThePoolToWETHRatio("0.01");
+
+ const balanceBefore = await weth.balanceOf(oethpVault.address);
+
+ const [amountWETH] = await roosterAmoStrategy.getPositionPrincipal();
+
+ // Try withdrawing an amount
+ await roosterAmoStrategy.connect(impersonatedVaultSigner).withdrawAll();
+
+ // And recipient has got it
+ expect(await weth.balanceOf(oethpVault.address)).to.approxEqualTolerance(
+ balanceBefore.add(amountWETH)
+ );
+
+ // There should be no WETH on the strategy contract
+ expect(await weth.balanceOf(roosterAmoStrategy.address)).to.eq(
+ oethUnits("0")
+ );
+
+ await verifyEndConditions();
+ });
+
+ it("Should withdraw when there's little OETHp in the pool", async () => {
+ const { oethpVault, roosterAmoStrategy, weth } = fixture;
+
+ const impersonatedVaultSigner = await impersonateAndFund(
+ oethpVault.address
+ );
+
+ // setup() moves the pool closer to 80:20
+ await rebalanceThePoolToWETHRatio("0.97");
+
+ const balanceBefore = await weth.balanceOf(oethpVault.address);
+
+ const [amountWETH, amountOETHb] =
+ await roosterAmoStrategy.getPositionPrincipal();
+
+ // Try withdrawing an amount
+ await roosterAmoStrategy
+ .connect(impersonatedVaultSigner)
+ .withdraw(oethpVault.address, weth.address, oethUnits("1"));
+
+ // Make sure that 1 WETH was burned and pool composition remains the same
+ const [amountWETHAfter, amountOETHbAfter] =
+ await roosterAmoStrategy.getPositionPrincipal();
+ expect(amountWETHAfter).to.approxEqualTolerance(
+ amountWETH.sub(oethUnits("1"))
+ );
+ expect(amountOETHbAfter.div(amountWETHAfter)).to.approxEqualTolerance(
+ amountOETHb.div(amountWETH)
+ );
+
+ // And recipient has got it
+ expect(await weth.balanceOf(oethpVault.address)).to.approxEqualTolerance(
+ balanceBefore.add(oethUnits("1"))
+ );
+
+ // There may remain some WETH left on the strategy contract because of the rounding
+ // when removing the liquidity
+ expect(await weth.balanceOf(roosterAmoStrategy.address)).to.lte(
+ BigNumber.from("10000")
+ );
+
+ await verifyEndConditions();
+ });
+
+ it("Should withdrawAll when there's little OETHp in the pool", async () => {
+ const { oethpVault, roosterAmoStrategy, weth } = fixture;
+
+ const impersonatedVaultSigner = await impersonateAndFund(
+ oethpVault.address
+ );
+
+ // setup() moves the pool closer to 80:20
+ await rebalanceThePoolToWETHRatio("0.97");
+
+ const balanceBefore = await weth.balanceOf(oethpVault.address);
+
+ const [amountWETH] = await roosterAmoStrategy.getPositionPrincipal();
+
+ // Try withdrawing an amount
+ await roosterAmoStrategy.connect(impersonatedVaultSigner).withdrawAll();
+
+ // And recipient has got it
+ expect(await weth.balanceOf(oethpVault.address)).to.approxEqualTolerance(
+ balanceBefore.add(amountWETH)
+ );
+
+ // There should be no WETH on the strategy contract
+ expect(await weth.balanceOf(roosterAmoStrategy.address)).to.eq(
+ oethUnits("0")
+ );
+
+ await verifyEndConditions();
+ });
+ });
+
+ describe("Deposit and rebalance", function () {
+ it("Should be able to deposit to the strategy", async () => {
+ await mintAndDepositToStrategy();
+
+ await verifyEndConditions();
+ });
+
+ it("Should revert when not depositing WETH or amount is 0, or withdrawing WETH", async () => {
+ await expect(
+ roosterAmoStrategy
+ .connect(oethVaultSigner)
+ .deposit(oethp.address, BigNumber.from("1"))
+ ).to.be.revertedWith("Unsupported asset");
+
+ await expect(
+ roosterAmoStrategy
+ .connect(oethVaultSigner)
+ .withdraw(oethpVault.address, oethp.address, oethUnits("1"))
+ ).to.be.revertedWith("Unsupported asset");
+
+ await expect(
+ roosterAmoStrategy
+ .connect(oethVaultSigner)
+ .withdraw(oethpVault.address, weth.address, 0)
+ ).to.be.revertedWith("Must withdraw something");
+
+ await expect(
+ roosterAmoStrategy
+ .connect(oethVaultSigner)
+ .withdraw(weth.address, weth.address, oethUnits("1"))
+ ).to.be.revertedWith("Only withdraw to vault allowed");
+
+ await expect(
+ roosterAmoStrategy
+ .connect(oethVaultSigner)
+ .deposit(weth.address, BigNumber.from("0"))
+ ).to.be.revertedWith("Must deposit something");
+ });
+
+ it("Should check that add liquidity in difference cases leaves little weth on the contract", async () => {
+ const amount = oethUnits("5");
+
+ await weth.connect(rafael).approve(oethpVault.address, amount);
+ await oethpVault.connect(rafael).mint(weth.address, amount, amount);
+
+ const gov = await roosterAmoStrategy.governor();
+ await oethpVault
+ .connect(await impersonateAndFund(gov))
+ .depositToStrategy(
+ roosterAmoStrategy.address,
+ [weth.address],
+ [amount]
+ );
+
+ // Rooster LENS contracts have issues where they over-calculate the amount of tokens that need to be
+ // deposited
+ expect(await weth.balanceOf(roosterAmoStrategy.address)).to.lte(
+ oethUnits("10000000000")
+ );
+
+ await verifyEndConditions();
+ });
+
+ it("Should revert when there is not enough WETH to perform a swap", async () => {
+ await rebalanceThePoolToWETHRatio("0.02");
+ await expect(
+ rebalance(
+ oethUnits("1000000000"),
+ true, // _swapWETH
+ oethUnits("0.009")
+ )
+ ).to.be.revertedWithCustomError(
+ "NotEnoughWethLiquidity(uint256,uint256)"
+ );
+ });
+
+ it("Should have the correct balance within some tolerance", async () => {
+ const balance = await roosterAmoStrategy.checkBalance(weth.address);
+ const amountToDeposit = oethUnits("6");
+ await mintAndDepositToStrategy({ amount: amountToDeposit });
+
+ // just add liquidity don't move the active trading position
+ await rebalance(BigNumber.from("0"), true, BigNumber.from("0"));
+
+ const wethShare = await roosterAmoStrategy.getCurrentWethShare();
+
+ await expect(
+ await roosterAmoStrategy.checkBalance(weth.address)
+ ).to.approxEqualTolerance(
+ balance.add(amountToDeposit.div(wethShare).mul(oethUnits("1"))),
+ 1.5
+ );
+
+ await verifyEndConditions();
+ });
+
+ it("Current trading price shouldn't affect checkBalance", async () => {
+ const amountToDeposit = oethUnits("6");
+ await mintAndDepositToStrategy({ amount: amountToDeposit });
+ const balanceBefore = await roosterAmoStrategy.checkBalance(weth.address);
+
+ // perform a swap
+ await swap({
+ amount: oethUnits("1"),
+ swapWeth: false,
+ });
+
+ expect(await roosterAmoStrategy.checkBalance(weth.address)).to.equal(
+ balanceBefore
+ );
+
+ await verifyEndConditions();
+ });
+
+ it("Should be able to rebalance removing half of liquidity", async () => {
+ const balance = await roosterAmoStrategy.checkBalance(weth.address);
+ const amountToDeposit = oethUnits("6");
+ await mintAndDepositToStrategy({ amount: amountToDeposit });
+
+ // just add liquidity don't move the active trading position
+ await rebalance(BigNumber.from("0"), true, BigNumber.from("0"), "0.5");
+
+ const wethShare = await roosterAmoStrategy.getCurrentWethShare();
+
+ await expect(
+ await roosterAmoStrategy.checkBalance(weth.address)
+ ).to.approxEqualTolerance(
+ balance.add(amountToDeposit.div(wethShare).mul(oethUnits("1"))),
+ 1.5
+ );
+
+ await verifyEndConditions();
+ });
+
+ it("Should be able to rebalance removing all of liquidity", async () => {
+ const balance = await roosterAmoStrategy.checkBalance(weth.address);
+ const amountToDeposit = oethUnits("6");
+ await mintAndDepositToStrategy({ amount: amountToDeposit });
+
+ // just add liquidity don't move the active trading position
+ await rebalance(BigNumber.from("0"), true, BigNumber.from("0"), "1");
+
+ const wethShare = await roosterAmoStrategy.getCurrentWethShare();
+
+ await expect(
+ await roosterAmoStrategy.checkBalance(weth.address)
+ ).to.approxEqualTolerance(
+ balance.add(amountToDeposit.div(wethShare).mul(oethUnits("1"))),
+ 1.5
+ );
+
+ await verifyEndConditions();
+ });
+
+ it("Should revert when it fails the slippage check", async () => {
+ const amountToDeposit = oethUnits("6");
+ await mintAndDepositToStrategy({ amount: amountToDeposit });
+
+ await expect(
+ rebalance(oethUnits("1"), true, oethUnits("1.1"))
+ ).to.be.revertedWithCustomError("SlippageCheck(uint256)");
+
+ await verifyEndConditions();
+ });
+
+ it("Should revert on non WETH balance", async () => {
+ await expect(
+ roosterAmoStrategy.checkBalance(oethp.address)
+ ).to.be.revertedWith("Only WETH supported");
+ });
+
+ it("Should throw an exception if not enough WETH on rebalance to perform a swap", async () => {
+ // swap out most of the weth
+ await swap({
+ // Pool has 5 WETH
+ amount: oethUnits("4.99"),
+ swapWeth: false,
+ });
+
+ await expect(
+ rebalance(
+ (await weth.balanceOf(await roosterAmoStrategy.mPool())).mul("2"),
+ true,
+ oethUnits("4")
+ )
+ ).to.be.revertedWithCustomError(
+ "NotEnoughWethLiquidity(uint256,uint256)"
+ );
+
+ await verifyEndConditions();
+ });
+
+ it("Should not be able to rebalance when protocol is insolvent", async () => {
+ await mintAndDepositToStrategy({ amount: oethUnits("1000") });
+ await roosterAmoStrategy.connect(oethVaultSigner).withdrawAll();
+
+ // ensure there is a LP position
+ await mintAndDepositToStrategy({ amount: oethUnits("1") });
+
+ // transfer WETH out making the protocol insolvent
+ const swapBal = oethUnits("0.00001");
+ const addLiquidityBal = oethUnits("1");
+ const balRemaining = (await weth.balanceOf(oethpVault.address))
+ .sub(swapBal)
+ .sub(addLiquidityBal);
+
+ await weth
+ .connect(oethVaultSigner)
+ .transfer(roosterAmoStrategy.address, swapBal.add(addLiquidityBal));
+ await weth
+ .connect(oethVaultSigner)
+ .transfer(addresses.dead, balRemaining);
+
+ await expect(
+ rebalance(
+ swapBal,
+ true, // _swapWETHs
+ oethUnits("0.000009"),
+ "0"
+ )
+ ).to.be.revertedWith("Protocol insolvent");
+ });
+ });
+
+ describe("Perform multiple actions", function () {
+ it("LP token should stay staked with multiple deposit/withdraw actions", async () => {
+ // deposit into pool once
+ await mintAndDepositToStrategy({ amount: oethUnits("5") });
+ // prettier-ignore
+ const tx = await rebalance(
+ oethUnits("0.00001"),
+ true, // _swapWETHs
+ oethUnits("0.000009")
+ );
+ await expect(tx).to.emit(roosterAmoStrategy, "PoolRebalanced");
+ await verifyEndConditions();
+
+ // deposit into pool again
+ await mintAndDepositToStrategy({ amount: oethUnits("5") });
+ // prettier-ignore
+ const tx1 = await rebalance(
+ oethUnits("0"),
+ true, // _swapWETHs
+ oethUnits("0")
+ );
+ await expect(tx1).to.emit(roosterAmoStrategy, "PoolRebalanced");
+ await verifyEndConditions();
+
+ // Withdraw from the pool
+ await roosterAmoStrategy
+ .connect(oethVaultSigner)
+ .withdraw(oethpVault.address, weth.address, oethUnits("1"));
+ await verifyEndConditions();
+
+ // deposit into pool again
+ await mintAndDepositToStrategy({ amount: oethUnits("5") });
+ // prettier-ignore
+ const tx2 = await rebalance(
+ oethUnits("0"),
+ true, // _swapWETHs
+ oethUnits("0")
+ );
+ await expect(tx2).to.emit(roosterAmoStrategy, "PoolRebalanced");
+ await verifyEndConditions();
+
+ // Withdraw from the pool
+ await roosterAmoStrategy
+ .connect(oethVaultSigner)
+ .withdraw(oethpVault.address, weth.address, oethUnits("1"));
+ await verifyEndConditions();
+
+ // Withdraw from the pool
+ await roosterAmoStrategy.connect(oethVaultSigner).withdrawAll();
+
+ // deposit into pool again
+ await mintAndDepositToStrategy({ amount: oethUnits("5") });
+ // prettier-ignore
+ const tx3 = await rebalance(
+ oethUnits("0"),
+ true, // _swapWETHs
+ oethUnits("0")
+ );
+ await expect(tx3).to.emit(roosterAmoStrategy, "PoolRebalanced");
+ await verifyEndConditions();
+ });
+ });
+
+ describe("Deposit and rebalance with mocked Vault", async () => {
+ beforeEach(async () => {
+ fixture = await plumeFixtureWithMockedVault();
+ weth = fixture.weth;
+ oethpVault = fixture.oethpVault;
+ roosterAmoStrategy = fixture.roosterAmoStrategy;
+ governor = fixture.governor;
+ strategist = fixture.strategist;
+
+ await setup();
+ });
+
+ const depositAllVaultWeth = async ({ returnTransaction } = {}) => {
+ const wethAvailable = await oethpVault.wethAvailable();
+ const gov = await oethpVault.governor();
+ const tx = await oethpVault
+ .connect(await impersonateAndFund(gov))
+ .depositToStrategy(
+ roosterAmoStrategy.address,
+ [weth.address],
+ [wethAvailable]
+ );
+
+ if (returnTransaction) {
+ return tx;
+ }
+
+ await expect(tx).to.emit(roosterAmoStrategy, "PoolRebalanced");
+ };
+
+ const depositAllWethAndConfigure1pct = async () => {
+ // configure to leave no WETH on the vault
+ await configureAutomaticDepositOnMint(oethUnits("0"));
+ const outstandingWeth = await oethpVault.outstandingWithdrawalsAmount();
+
+ // send WETH to the vault that is outstanding to be claimed
+ await weth
+ .connect(fixture.clement)
+ .transfer(oethpVault.address, outstandingWeth);
+
+ await depositAllVaultWeth();
+
+ // configure to only keep 1bp of the Vault's totalValue in the Vault;
+ const minAmountReserved = await configureAutomaticDepositOnMint(
+ oethUnits("0.01")
+ );
+
+ return minAmountReserved;
+ };
+
+ it("Should not automatically deposit to strategy when below vault buffer threshold", async () => {
+ const minAmountReserved = await depositAllWethAndConfigure1pct();
+
+ const amountBelowThreshold = minAmountReserved.div(BigNumber.from("2"));
+
+ await mint({ amount: amountBelowThreshold });
+ // There is some WETH usually on the strategy contract because liquidity manager doesn't consume all.
+ // Also the strategy contract adjusts WETH supplied to pool down, to mitigate the PoolLens liquidity
+ // calculation.
+ await expect(await weth.balanceOf(roosterAmoStrategy.address)).to.lte(
+ BigNumber.from("1000")
+ );
+
+ await expect(await oethpVault.wethAvailable()).to.approxEqualTolerance(
+ amountBelowThreshold
+ );
+
+ await verifyEndConditions();
+ });
+
+ it("Should revert when pool rebalance is off target", async () => {
+ const { amount, swapWeth } = await estimateSwapAmountsToReachWethRatio(
+ oethUnits("0.91")
+ );
+ await swap({ amount, swapWeth });
+ await mintAndDepositToStrategy({ amount: oethUnits("1000") }, false);
+ const { amount: amount2, swapWeth: swapWeth2 } =
+ await estimateSwapAmountsToReachWethRatio(oethUnits("0.91"));
+
+ await expect(
+ rebalance(amount2, swapWeth2, 0, "0")
+ ).to.be.revertedWithCustomError(
+ "PoolRebalanceOutOfBounds(uint256,uint256,uint256)"
+ );
+
+ await expect(
+ rebalance(amount.add(amount), swapWeth2, 0, "0")
+ ).to.be.revertedWithCustomError("OutsideExpectedTickRange()");
+ });
+
+ it("Should be able to rebalance the pool when price pushed very close to 1:1", async () => {
+ const { amount: amountToSwap, swapWeth: swapWethToSwap } =
+ await estimateSwapAmountsToReachWethRatio(oethUnits("0.99"));
+ await swap({
+ amount: amountToSwap,
+ swapWeth: swapWethToSwap,
+ });
+
+ const { amount, swapWeth } =
+ await estimateSwapAmountsToGetToConfiguredInterval();
+
+ await rebalance(amount, swapWeth, 0, "0");
+ await verifyEndConditions();
+ });
+
+ it("Should be able to rebalance the pool when price pushed very close to OETHb costing 0.9999 WETH", async () => {
+ const { amount: amountToSwap, swapWeth: swapWethToSwap } =
+ await estimateSwapAmountsToReachWethRatio(oethUnits("0.01"));
+ await swap({
+ amount: amountToSwap,
+ swapWeth: swapWethToSwap,
+ });
+
+ const { amount, swapWeth } =
+ await estimateSwapAmountsToGetToConfiguredInterval();
+ await mintAndDepositToStrategy({ amount: oethUnits("1000") }, false);
+
+ await rebalance(amount, swapWeth, 0, "0");
+ await verifyEndConditions();
+ });
+
+ it("Should be able to deposit to the pool & rebalance", async () => {
+ await mintAndDepositToStrategy({ amount: oethUnits("5") });
+ let { amount, swapWeth } =
+ await estimateSwapAmountsToGetToConfiguredInterval();
+
+ const tx = await rebalance(amount, swapWeth, 0, "0");
+
+ await expect(tx).to.emit(roosterAmoStrategy, "PoolRebalanced");
+ await verifyEndConditions();
+ });
+
+ it("Should be able to rebalance when small amount of WETH needs to be removed as swap liquidity", async () => {
+ await mintAndDepositToStrategy({ amount: oethUnits("5") });
+
+ const { amount, swapWeth } =
+ await estimateSwapAmountsToGetToConfiguredInterval();
+ // rebalance to use up any WETH liquidity on the contract
+ await rebalance(amount, swapWeth, 0, "0");
+ // rebalance requiring small amount of WETH (increasing)
+ await rebalance(BigNumber.from("100"), true, 0, "0");
+ await rebalance(BigNumber.from("10000"), true, 0, "0");
+ await rebalance(BigNumber.from("1000000"), true, 0, "0");
+ await rebalance(BigNumber.from("100000000"), true, 0, "0");
+ await rebalance(BigNumber.from("10000000000"), true, 0, "0");
+ await rebalance(BigNumber.from("1000000000000"), true, 0, "0");
+
+ await verifyEndConditions();
+ });
+
+ it("Should be able to deposit to the pool & rebalance multiple times", async () => {
+ await mintAndDepositToStrategy({ amount: oethUnits("5") });
+ let { amount, swapWeth } =
+ await estimateSwapAmountsToGetToConfiguredInterval();
+ const tx = await rebalance(amount, swapWeth, 0, "0");
+ await expect(tx).to.emit(roosterAmoStrategy, "PoolRebalanced");
+ await verifyEndConditions();
+
+ await mintAndDepositToStrategy({ amount: oethUnits("5") });
+
+ let { amount: amount1, swapWeth: swapWeth1 } =
+ await estimateSwapAmountsToGetToConfiguredInterval();
+ const tx1 = await rebalance(amount1, swapWeth1, 0, "0");
+ await expect(tx1).to.emit(roosterAmoStrategy, "PoolRebalanced");
+ await verifyEndConditions();
+ });
+
+ const depositValues = [
+ "0.5",
+ "2.5",
+ "3.5",
+ "5",
+ "9",
+ "50",
+ "250",
+ "500",
+ "1500",
+ "2500",
+ ];
+ for (const depositValue of depositValues) {
+ it(`Should be able to deposit to the pool & rebalance multiple times with ${depositValue} deposit`, async () => {
+ await mintAndDepositToStrategy({ amount: oethUnits("5") });
+ let { amount, swapWeth } =
+ await estimateSwapAmountsToGetToConfiguredInterval();
+ const tx = await rebalance(amount, swapWeth, 0, "0");
+ await expect(tx).to.emit(roosterAmoStrategy, "PoolRebalanced");
+ await verifyEndConditions();
+
+ await mintAndDepositToStrategy({ amount: oethUnits("5") });
+
+ let { amount: amount1, swapWeth: swapWeth1 } =
+ await estimateSwapAmountsToGetToConfiguredInterval();
+ const tx1 = await rebalance(amount1, swapWeth1, 0, "0");
+ await expect(tx1).to.emit(roosterAmoStrategy, "PoolRebalanced");
+ await verifyEndConditions();
+ });
+ }
+ });
+
+ describe("Rewards", function () {
+ it("Should be able to claim rewards when available", async () => {
+ // Make sure the strategy has something
+ await mintAndDepositToStrategy({ amount: oethUnits("5") });
+
+ const wPlume = await ethers.getContractAt(
+ "IWETH9",
+ addresses.plume.WPLUME
+ );
+ const balanceBefore = await wPlume.balanceOf(strategist.address);
+ await roosterAmoStrategy.connect(strategist).collectRewardTokens();
+ const balanceAfter = await wPlume.balanceOf(strategist.address);
+
+ const balanceDiff = balanceAfter.sub(balanceBefore);
+ expect(balanceDiff).to.eq(oethUnits("1"));
+ });
+ });
+
+ const setup = async () => {
+ await mintAndDepositToStrategy({
+ amount: oethUnits("100"),
+ returnTransaction: true,
+ });
+
+ await oethpVault.connect(rafael).rebase();
+
+ let { amount, swapWeth } =
+ await estimateSwapAmountsToGetToConfiguredInterval();
+
+ await rebalance(amount, swapWeth, 0, "0");
+ };
+
+ const swap = async ({ amount, swapWeth }) => {
+ // Check if rafael as enough token to perform swap
+ // If not, mint some
+ const balanceOETHp = await oethp.balanceOf(rafael.address);
+ if (!swapWeth && balanceOETHp.lt(amount)) {
+ await weth.connect(rafael).approve(oethpVault.address, amount);
+ await oethpVault.connect(rafael).mint(weth.address, amount, amount);
+ }
+
+ if (swapWeth) {
+ await weth.connect(rafael).transfer(roosterOETHpWETHpool.address, amount);
+ } else {
+ await oethp
+ .connect(rafael)
+ .transfer(roosterOETHpWETHpool.address, amount);
+ }
+
+ await roosterOETHpWETHpool.connect(rafael).swap(
+ rafael.address,
+ {
+ amount: amount,
+ tokenAIn: swapWeth,
+ exactOutput: false,
+ tickLimit: swapWeth ? 2147483647 : -2147483648,
+ },
+ "0x"
+ );
+ };
+
+ // get the middle point of configured weth share interval
+ const getConfiguredWethShare = async () => {
+ const wethShareStart = await roosterAmoStrategy.allowedWethShareStart();
+ const wethShareEnd = await roosterAmoStrategy.allowedWethShareEnd();
+
+ return wethShareStart.add(wethShareEnd).div(BigNumber.from("2"));
+ };
+
+ const rebalanceThePoolToWETHRatio = async (wethRatio) => {
+ const { amount, swapWeth } = await estimateSwapAmountsToReachWethRatio(
+ oethUnits(wethRatio)
+ );
+ await swap({ amount, swapWeth });
+ };
+
+ const estimateSwapAmountsToGetToConfiguredInterval = async () => {
+ const configuredWethShare = await getConfiguredWethShare();
+ return await estimateSwapAmountsToReachWethRatio(configuredWethShare);
+ };
+
+ // the amount to swap (and token type) to reach desired WETH ratio
+ // Notice: this only works if the pools is already in the tick -1 where
+ // all the liquidity is deployed
+ const estimateSwapAmountsToReachWethRatio = async (wethRatio) => {
+ let currentTradingTick = parseInt((await roosterAmoStrategy.getCurrentTradingTick()).toString());
+ let totalAmount = BigNumber.from("0");
+
+ while(currentTradingTick < -1) {
+ totalAmount = totalAmount.add(await _getOETHInTick(currentTradingTick));
+ currentTradingTick += 1;
+ }
+
+ while(currentTradingTick > -1) {
+ totalAmount = totalAmount.add(await _getWETHInTick(currentTradingTick));
+ currentTradingTick -= 1;
+ }
+
+ let { amount, swapWeth } = await _estimateAmountsWithinTheAMOTick(wethRatio);
+ amount = amount.add(totalAmount);
+ return {
+ amount,
+ swapWeth
+ }
+ };
+
+ const _getWETHInTick = async (tradingTick) => {
+ const tickState = await roosterOETHpWETHpool.getTick(tradingTick);
+ return tickState.reserveA;
+ }
+
+ const _getOETHInTick = async (tradingTick) => {
+ const tickState = await roosterOETHpWETHpool.getTick(tradingTick);
+ return tickState.reserveB;
+ }
+
+ const _estimateAmountsWithinTheAMOTick = async (wethRatio) => {
+ const tickState = await roosterOETHpWETHpool.getTick(-1);
+
+ const wethAmount = tickState.reserveA;
+ const oethAmount = tickState.reserveB;
+
+ const total = wethAmount.add(oethAmount);
+ // 1e18 denominated
+ const currentWethRatio = wethAmount.mul(oethUnits("1")).div(total);
+
+ let diff, swapWeth;
+ if (wethRatio.gt(currentWethRatio)) {
+ diff = wethRatio.sub(currentWethRatio);
+ swapWeth = true;
+ } else {
+ diff = currentWethRatio.sub(wethRatio);
+ swapWeth = false;
+ }
+
+ return {
+ amount: diff.mul(total).div(oethUnits("1")),
+ swapWeth,
+ };
+ }
+
+ const rebalance = async (
+ amountToSwap,
+ swapWETH,
+ minTokenReceived,
+ liquidityToRemove = "1"
+ ) => {
+ return await roosterAmoStrategy
+ .connect(strategist)
+ .rebalance(
+ amountToSwap,
+ swapWETH,
+ minTokenReceived,
+ oethUnits(liquidityToRemove)
+ );
+ };
+
+ const mint = async ({ userOverride, amount } = {}) => {
+ const user = userOverride || rafael;
+ amount = amount || oethUnits("5");
+
+ const balance = weth.balanceOf(user.address);
+ if (balance < amount) {
+ await setERC20TokenBalance(user.address, weth, amount + balance, hre);
+ }
+ await weth.connect(user).approve(oethpVault.address, amount);
+ const tx = await oethpVault
+ .connect(user)
+ .mint(weth.address, amount, amount);
+ return tx;
+ };
+
+ const mintAndDepositToStrategy = async (
+ { userOverride, amount, returnTransaction } = {},
+ expectPoolRebalanced = true
+ ) => {
+ const user = userOverride || rafael;
+ amount = amount || oethUnits("5");
+
+ const balance = weth.balanceOf(user.address);
+ if (balance < amount) {
+ await setERC20TokenBalance(user.address, weth, amount + balance, hre);
+ }
+ await weth.connect(user).approve(oethpVault.address, amount);
+ await oethpVault.connect(user).mint(weth.address, amount, amount);
+
+ const gov = await oethpVault.governor();
+ const tx = await oethpVault
+ .connect(await impersonateAndFund(gov))
+ .depositToStrategy(roosterAmoStrategy.address, [weth.address], [amount]);
+
+ if (returnTransaction) {
+ return tx;
+ }
+
+ if (expectPoolRebalanced) {
+ await expect(tx).to.emit(roosterAmoStrategy, "PoolRebalanced");
+ }
+ };
+
+ /** When tests finish:
+ * - there should be no substantial amount of WETH / OETHp left on the strategy contract
+ */
+ const verifyEndConditions = async () => {
+ await expect(await weth.balanceOf(roosterAmoStrategy.address)).to.lte(
+ oethUnits("0.00001")
+ );
+ await expect(await oethp.balanceOf(roosterAmoStrategy.address)).to.lte(
+ oethUnits("0.000001")
+ );
+ };
+});
diff --git a/contracts/utils/addresses.js b/contracts/utils/addresses.js
index 113eca5174..f7c812e38a 100644
--- a/contracts/utils/addresses.js
+++ b/contracts/utils/addresses.js
@@ -599,8 +599,20 @@ addresses.plume.LayerZeroEndpointV2 =
"0xC1b15d3B262bEeC0e3565C11C9e0F6134BdaCB36";
addresses.plume.WOETHOmnichainAdapter =
"0x592CB6A596E7919930bF49a27AdAeCA7C055e4DB";
-
addresses.plume.timelock = "0x6C6f8F839A7648949873D3D2beEa936FC2932e5c";
+addresses.plume.WPLUME = "0xEa237441c92CAe6FC17Caaf9a7acB3f953be4bd1";
+addresses.plume.MaverickV2Factory =
+ "0x056A588AfdC0cdaa4Cab50d8a4D2940C5D04172E";
+addresses.plume.MaverickV2PoolLens =
+ "0x15B4a8cc116313b50C19BCfcE4e5fc6EC8C65793";
+addresses.plume.MaverickV2Quoter = "0xf245948e9cf892C351361d298cc7c5b217C36D82";
+addresses.plume.MaverickV2Router = "0x35e44dc4702Fd51744001E248B49CBf9fcc51f0C";
+addresses.plume.MaverickV2Position =
+ "0x0b452E8378B65FD16C0281cfe48Ed9723b8A1950";
+addresses.plume.MaverickV2LiquidityManager =
+ "0x28d79eddBF5B215cAccBD809B967032C1E753af7";
+addresses.plume.OethpWETHRoosterPool =
+ "0x3F86B564A9B530207876d2752948268b9Bf04F71";
addresses.plume.strategist = addresses.multichainStrategist;
addresses.plume.admin = "0x92A19381444A001d62cE67BaFF066fA1111d7202";